From e9967fa22781d922ba4eb8fb44fe72d200ac4b14 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Mon, 10 Nov 2025 21:04:10 +0000
Subject: [PATCH] =IconsManager.php update
---
inc/managers/ReferralManager.php | 1324 +++++++++++++++++++++++++++++++++++++++++++++++----------
1 files changed, 1,094 insertions(+), 230 deletions(-)
diff --git a/inc/managers/ReferralManager.php b/inc/managers/ReferralManager.php
index 90671f2..3ac4511 100644
--- a/inc/managers/ReferralManager.php
+++ b/inc/managers/ReferralManager.php
@@ -1,6 +1,10 @@
<?php
namespace JVBase\managers;
+use JVBase\managers\MagicLinkManager;
+use JVBase\integrations\Cloudflare;
+use JVBase\meta\MetaForm;
+use JVBase\utility\Features;
use WP_User;
use WP_Error;
@@ -17,8 +21,10 @@
class ReferralManager
{
protected $wpdb;
+ protected MagicLinkManager $magic_link;
protected CacheManager $cache;
protected string $referrals_table;
+ protected ?int $referralPage = null;
protected string $rewards_table;
// Default reward settings
@@ -31,16 +37,24 @@
'referee_reward_applies_to' => 'first_order' // 'first_order' or 'all_orders'
];
+ protected array $settings;
+
public function __construct()
{
global $wpdb;
$this->wpdb = $wpdb;
- $this->cache = new CacheManager('referrals');
+ $this->cache = CacheManager::for('referrals', WEEK_IN_SECONDS);
$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);
+ $this->referralPage = $this->getReferralPageId();
+ $this->settings = $this->getRewardSettings();
+
+
+ add_action('jvbUserRegistered', [$this, 'processRegistrationToken'], 10, 3);
+ add_action('jvb_add_token_inputs', [$this, 'addLoginInputs'], 10, 1);
+ add_action('jvbUserRegistered', [$this, 'processReferral'], 10, 1);
// Add meta boxes for admin to manage referrals
add_action('show_user_profile', [$this, 'displayUserReferralInfo']);
@@ -50,15 +64,81 @@
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('jvbNewUserEmail', [$this, 'addReferralToWelcomeEmail'], 99, 2);
- if (is_user_logged_in()) {
- add_action('wp_footer', [$this, 'outputShareWidget']);
- }
- add_action('template_redirect', [$this, 'trackReferralCode']);;
+ add_filter('jvbAdditionalActions', [$this, 'outputShareWidget']);
+
+ add_action('wp_enqueue_scripts', [$this, 'enqueueScripts']);
// Schedule cron jobs for reports
$this->registerCronJobs();
+
+ // Register admin subpage
+// add_filter('jvbAdminSubpages', [$this, 'addSubpage'], 10, 1);
+
+ // Add admin bar label for referral page
+ add_action('admin_bar_menu', [$this, 'addReferralPageLabel'], 999);
+
+ // Add admin notice to referral page edit screen
+ add_action('admin_notices', [$this, 'showReferralPageNotice']);
+
+
+ add_filter('jvbDashboardPage', [$this, 'renderDashPage'], 10, 2);
+
+ // Handle settings save
+ add_action('admin_init', [$this, 'registerSettings']);
+ }
+
+ public function addLoginInputs(string $action):void
+ {
+ if (array_key_exists('ref', $_GET)) {
+ echo '<input type="hidden" name="referral_code" value="'.$_GET['ref'].'">';
+ }
+ }
+
+ public function modifyLoginText(array $defaults):array
+ {
+ if (!array_key_exists('ref', $_GET)){
+ return $defaults;
+ }
+
+ $code = $_GET['ref'];
+ $user = $this->getUserByReferralCode($code);
+
+ $desc = ($user) ? strtok($user->display_name, ' ') . ' invited you ' : 'You\'ve been invited ';
+
+ $defaults['title'] = 'Register your Account. Get Your Reward.';
+ $defaults['description'] = [
+ $desc.' to see the difference with us.',
+ 'Oh, and you\'ll get 20% off your first treatment!',
+ 'Finish this account creation (you\'ll still need to make an account on Jane App to book), or let us know when you come in for your appointment.'
+ ];
+ $defaults['extra'] = [
+ 'Once you\'re in, you\'ll get your own code you can share.',
+ 'Get a $25 credit for <b>each</b> person you send our way who comes in for their first treatment!'
+ ];
+ return $defaults;
+ }
+
+ 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
+ );
}
/**
@@ -164,17 +244,47 @@
return !empty($users);
}
+ public function processRegistrationToken(int $user_id, string $email, array $data): void
+ {
+ // Check for referral code in data
+ $code = $data['referral_code'] ?? '';
+ if (empty($code)) {
+ return;
+ }
+
+ // Store in session/cookie for processReferral to pick up
+ if (session_status() === PHP_SESSION_NONE) {
+ session_start();
+ }
+ $_SESSION[BASE . 'referral_code'] = sanitize_text_field($code);
+ setcookie(
+ BASE . 'referral_code',
+ sanitize_text_field($code),
+ time() + (86400 * 30),
+ '/'
+ );
+ }
/**
* Track a new referral when user registers
*
* @param int $user_id
*/
- public function processReferral(int $user_id): void
+ public function processReferral(int $user_id, string $email, array $data): void
{
- // Check if there's a referral code in the session/cookie
- $referral_code = $this->getReferralCodeFromSession();
+ // Check if user was created via referral magic link
+ // Try to get code from multiple sources
+ $referral_code = $data['referral_code'] ??
+ get_user_meta($user_id, BASE . 'pending_referral_code', true);
- if (!$referral_code) {
+ // Check session/cookie if not in data
+ if (empty($referral_code)) {
+ if (session_status() === PHP_SESSION_NONE) {
+ session_start();
+ }
+ $referral_code = $_SESSION[BASE . 'referral_code'] ?? $_COOKIE[BASE . 'referral_code'] ?? '';
+ }
+
+ if (empty($referral_code)) {
return;
}
@@ -182,20 +292,27 @@
$referrer = $this->getUserByReferralCode($referral_code);
if (!$referrer) {
+ delete_user_meta($user_id, BASE . 'pending_referral_code');
return;
}
- // Check if this user was already referred (prevent duplicates)
+ // Check for duplicates
$existing = $this->getReferralByReferee($user_id);
if ($existing) {
+ delete_user_meta($user_id, BASE . 'pending_referral_code');
return;
}
// Create referral record
- $this->createReferral($referrer->ID, $user_id, $referral_code);
+ $result = $this->createReferral($referrer->ID, $user_id, $referral_code);
- // Clear the session
- $this->clearReferralSession();
+ 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);
+ }
}
/**
@@ -206,7 +323,7 @@
* @param string $code
* @return int|false
*/
- protected function createReferral(int $referrer_id, int $referee_id, string $code)
+ public function createReferral(int $referrer_id, int $referee_id, string $code)
{
$user = get_user_by('ID', $referee_id);
@@ -232,15 +349,19 @@
* @param string $code
* @return WP_User|null
*/
- protected function getUserByReferralCode(string $code): ?WP_User
+ public function getUserByReferralCode(string $code): ?WP_User
{
- $users = get_users([
- 'meta_key' => BASE . 'referral_code',
- 'meta_value' => $code,
- 'number' => 1
- ]);
-
- return $users[0] ?? null;
+ return $this->cache->remember(
+ $code,
+ function () use ($code) {
+ $users = get_users([
+ 'meta_key' => BASE . 'referral_code',
+ 'meta_value' => $code,
+ 'number' => 1
+ ]);
+ return $users[0] ?? null;
+ }
+ );
}
/**
@@ -305,8 +426,6 @@
return;
}
- $settings = $this->getRewardSettings();
-
// Create referrer reward
$this->wpdb->insert(
$this->rewards_table,
@@ -314,7 +433,7 @@
'referral_id' => $referral_id,
'user_id' => $referral->referrer_id,
'reward_type' => 'referrer',
- 'amount' => $settings['referrer_reward_amount'],
+ 'amount' => $this->settings['referrer_reward_amount'],
'status' => 'available',
'created_at' => current_time('mysql')
],
@@ -322,9 +441,9 @@
);
// 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
+ $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,
@@ -333,7 +452,7 @@
'user_id' => $referral->referee_id,
'reward_type' => 'referee',
'amount' => $referee_amount,
- 'reward_calculation' => $settings['referee_reward_type'],
+ 'reward_calculation' => $this->settings['referee_reward_type'],
'status' => 'available',
'created_at' => current_time('mysql')
],
@@ -465,40 +584,62 @@
*/
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"
- );
+ $yesterday = date('Y-m-d', strtotime('-1 day'));
- if (empty($referrals)) {
- return; // No referrals, no email
+ // 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;
}
- // Generate CSV
- $csv_content = $this->generateCSV($referrals);
- $csv_filename = 'referrals-' . date('Y-m-d') . '.csv';
+ // Build email content
+ $content = '<h2>Daily Referral Report</h2>';
+ $content .= '<p><strong>' . count($new_referrals) . '</strong> new referral' .
+ (count($new_referrals) !== 1 ? 's' : '') . ' yesterday (' . $yesterday . ')</p>';
- // Save CSV temporarily
- $upload_dir = wp_upload_dir();
- $csv_path = $upload_dir['basedir'] . '/' . $csv_filename;
- file_put_contents($csv_path, $csv_content);
+ $content .= '<table style="width:100%; border-collapse: collapse; margin: 20px 0;">';
+ $content .= '<thead><tr style="background: #f5f5f5;">';
+ $content .= '<th style="padding: 10px; text-align: left; border: 1px solid #ddd;">Referee</th>';
+ $content .= '<th style="padding: 10px; text-align: left; border: 1px solid #ddd;">Email</th>';
+ $content .= '<th style="padding: 10px; text-align: left; border: 1px solid #ddd;">Referrer</th>';
+ $content .= '<th style="padding: 10px; text-align: left; border: 1px solid #ddd;">Code</th>';
+ $content .= '</tr></thead><tbody>';
- // Send email with attachment
+ foreach ($new_referrals as $ref) {
+ $content .= '<tr>';
+ $content .= sprintf('<td style="padding: 10px; border: 1px solid #ddd;">%s</td>',
+ esc_html($ref->referee_name));
+ $content .= sprintf('<td style="padding: 10px; border: 1px solid #ddd;">%s</td>',
+ esc_html($ref->referee_email));
+ $content .= sprintf('<td style="padding: 10px; border: 1px solid #ddd;">%s</td>',
+ esc_html($ref->referrer_name));
+ $content .= sprintf('<td style="padding: 10px; border: 1px solid #ddd;">%s</td>',
+ esc_html($ref->referral_code));
+ $content .= '</tr>';
+ }
+
+ $content .= '</tbody></table>';
+
+ // Get admin email
$to = get_option('admin_email');
- $subject = '[' . get_bloginfo('name') . '] Daily Referral Report - ' . date('F j, Y');
+ $subject = sprintf('[%s] %d New Referral%s',
+ get_bloginfo('name'),
+ count($new_referrals),
+ count($new_referrals) !== 1 ? 's' : '');
- $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);
+ jvbMail($to, $subject, $content);
}
/**
@@ -644,35 +785,13 @@
*
* @return array
*/
- protected function getRewardSettings(): array
+ public 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
@@ -834,28 +953,6 @@
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
@@ -863,141 +960,233 @@
*
* @return string HTML output
*/
- public function outputShareWidget(): string
+ public function outputShareWidget(array $actions):array
{
- $user_id = get_current_user_id();
+ $user_id = get_current_user_id();
+ $content = '<aside class="jvb-referral right">';
if (!$user_id) {
+ $content .= $this->getUnloggedInReferral();
+ } else {
+ $content .= $this->getLoggedInReferral($user_id);
+ }
+ $content .= '</aside>';
+
+ $actions[] =[
+ 'button' => '<button type="button" class="attn toggle-referral row" title="Your Referrals" data-action="toggle-referral" aria-label="Open Referral Sidebar" aria-controls="referral" aria-expanded="false">
+ '.jvbIcon('hand-heart').'<span class="screen-reader-text"></span>
+ </button>',
+ 'content' => $content
+ ];
+
+ return $actions;
+ }
+
+ function getUnloggedInReferral(): string
+ {
+ ob_start();
+ JVB()->connect('cloudflare')->renderTurnstile();
+ $turnstile = ob_get_clean();
+ $meta = new MetaForm();
+ $codeForm = '<form id="referral-code-form">
+ '.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'
+ ]).'
+ <button type="submit">
+ Get Started
+ </button>
+
+ <p class="helper-text">
+ We\'ll send you a link to complete your registration.
+ </p>
+ '.$turnstile.'
+ </form><div class="success-content" hidden>
+ <h3>Check Your Email!</h3>
+ <p>We\'ve sent you a magic link to complete your registration. Click the link to activate your account and claim your reward!</p>
+ <p class="hint">Can\'t find it? Check your spam folder.</p>
+ </div>';
+
+ $loginForm = '<form id ="login-form">
+ '.jvbFormStatus().$meta->return('login_email', null, [
+ 'required' => true,
+ 'type' => 'email',
+ 'label' => 'Your Email',
+ 'autocomplete'=>'email'
+ ]).'
+ '.$turnstile.'
+ <button type="submit">Login With Magic Link</button>
+</form>
+ <div class="success-content" hidden>
+ <h3>Check Your Email!</h3>
+ <p>We\'ve sent you a magic link to log in - no password required! Click the link in your email to log in.</p>
+ <p class="hint">Can\'t find it? Check your spam folder.</p>
+ </div>';
+
+ $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 '';
}
+ $reward_amount = $this->settings['referee_reward_amount'] ?? 20;
+ $reward_type = $this->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();
+ ?>
+ <div class="success-icon">
+ ✓
+ </div>
+
+ <div class="success-content">
+ <h3>Success! Your Reward is Ready!</h3>
+
+ <div class="reward-highlight">
+ <p style="margin: 0 0 8px 0;">You'll receive:</p>
+ <p style="margin: 0;"><strong><?php echo esc_html($reward_text); ?> your first treatment!</strong></p>
+ </div>
+
+ <p>Your referral code <strong><?php echo esc_html($code); ?></strong> has been applied. Book your free consultation now to claim your reward!</p>
+
+ <a href="<?php echo esc_url($booking_url); ?>" class="cta-button">
+ Book Your Free Consultation
+ </a>
+
+ <div class="referred-by">
+ Referred by <strong><?php echo esc_html($referrer->display_name); ?></strong>
+ </div>
+ </div>
+ <?php
+ return ob_get_clean();
+ }
+
+ public function getLoggedInReferral(int $user_id):string
+ {
+ // Logged-in user widget
$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);
+ $referral_code = $this->getUserReferralCode($user_id);
+ if (is_wp_error($referral_code)) {
+ return '';
+ }
}
- $share_url = home_url('/?ref=' . $referral_code);
- $encoded_url = urlencode($share_url);
- $site_name = get_bloginfo('name');
+ $share_url = $this->getShareURL($referral_code);
ob_start();
?>
- <div class="jvb-referral-widget" style="background: #f9f9f9; padding: 20px; border-radius: 8px; margin: 20px 0;">
- <h3 style="margin-top: 0;">Share & Earn Rewards</h3>
- <p>Share your unique referral code with friends and earn rewards when they book!</p>
+ <header>
+ <h3>Share the ♡</h3>
+ <p>Invite your friends.</p>
+ <p>Earn rewards when they book!</p>
+ </header>
- <div class="referral-code-display" style="background: white; padding: 15px; border-radius: 4px; margin: 15px 0; text-align: center;">
- <label style="display: block; font-size: 12px; color: #666; margin-bottom: 5px;">Your Referral Code</label>
- <div style="font-size: 24px; font-weight: bold; letter-spacing: 2px; color: #2271b1;">
- <?php echo esc_html($referral_code); ?>
- </div>
- </div>
-
- <div class="referral-url" style="margin: 15px 0;">
- <label style="display: block; font-size: 12px; color: #666; margin-bottom: 5px;">Share Link</label>
- <div style="display: flex; gap: 10px;">
- <input type="text"
- readonly
- value="<?php echo esc_url($share_url); ?>"
- id="referral-url-<?php echo $user_id; ?>"
- style="flex: 1; padding: 8px; border: 1px solid #ddd; border-radius: 4px; font-family: monospace; font-size: 14px;">
- <button type="button"
- onclick="jvbCopyReferralUrl('referral-url-<?php echo $user_id; ?>')"
- style="padding: 8px 16px; background: #2271b1; color: white; border: none; border-radius: 4px; cursor: pointer;">
- Copy
- </button>
- </div>
- </div>
-
- <div class="referral-share-buttons" style="display: flex; gap: 10px; margin-top: 15px; flex-wrap: wrap;">
- <a href="mailto:?subject=Check out <?php echo esc_attr($site_name); ?>&body=I thought you might like <?php echo esc_url($share_url); ?>"
- class="share-button"
- style="padding: 10px 20px; background: #666; color: white; text-decoration: none; border-radius: 4px; display: inline-flex; align-items: center; gap: 8px;">
- 📧 Email
- </a>
- <a href="sms:?&body=Check out <?php echo esc_attr($site_name); ?>: <?php echo esc_url($share_url); ?>"
- class="share-button"
- style="padding: 10px 20px; background: #25D366; color: white; text-decoration: none; border-radius: 4px; display: inline-flex; align-items: center; gap: 8px;">
- 💬 Text
- </a>
- <a href="https://www.facebook.com/sharer/sharer.php?u=<?php echo $encoded_url; ?>"
- target="_blank"
- class="share-button"
- style="padding: 10px 20px; background: #1877f2; color: white; text-decoration: none; border-radius: 4px; display: inline-flex; align-items: center; gap: 8px;">
- f Facebook
- </a>
- <a href="https://twitter.com/intent/tweet?url=<?php echo $encoded_url; ?>&text=Check out <?php echo esc_attr($site_name); ?>"
- target="_blank"
- class="share-button"
- style="padding: 10px 20px; background: #1da1f2; color: white; text-decoration: none; border-radius: 4px; display: inline-flex; align-items: center; gap: 8px;">
- 𝕏 Twitter
- </a>
- </div>
-
- <div id="referral-stats-<?php echo $user_id; ?>" class="referral-stats" style="margin-top: 20px; padding-top: 20px; border-top: 1px solid #ddd;">
- <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 15px; text-align: center;">
- <div>
- <div style="font-size: 24px; font-weight: bold; color: #2271b1;" data-stat="total">-</div>
- <div style="font-size: 12px; color: #666;">Total Referrals</div>
- </div>
- <div>
- <div style="font-size: 24px; font-weight: bold; color: #00a32a;" data-stat="treated">-</div>
- <div style="font-size: 12px; color: #666;">Completed</div>
- </div>
- <div>
- <div style="font-size: 24px; font-weight: bold; color: #dba617;" data-stat="pending">-</div>
- <div style="font-size: 12px; color: #666;">Pending</div>
- </div>
- <div>
- <div style="font-size: 24px; font-weight: bold; color: #2271b1;" data-stat="rewards">$0</div>
- <div style="font-size: 12px; color: #666;">Earned</div>
- </div>
- </div>
- </div>
+ <div class="row even share-buttons">
+ <a href="mailto:?subject=<?php echo urlencode('Check out ' . get_bloginfo('name')); ?>&body=<?php echo urlencode('I thought you might be interested: ' . $share_url); ?>"
+ class="share-btn email-share">
+ <?php echo jvbIcon('envelope', ['size' => 20]); ?>
+ Email
+ </a>
+ <a href="https://www.facebook.com/sharer/sharer.php?u=<?php echo urlencode($share_url); ?>"
+ target="_blank"
+ rel="noopener noreferrer"
+ class="share-btn facebook-share">
+ <?php echo jvbIcon('facebook-logo', ['size' => 20]); ?>
+ Facebook
+ </a>
+ <a href="https://twitter.com/intent/tweet?url=<?php echo urlencode($share_url); ?>&text=<?php echo urlencode('Check this out!'); ?>"
+ target="_blank"
+ rel="noopener noreferrer"
+ class="share-btn twitter-share">
+ <?php echo jvbIcon('twitter-logo', ['size' => 20]); ?>
+ Twitter
+ </a>
</div>
- <script>
- function jvbCopyReferralUrl(elementId) {
- const input = document.getElementById(elementId);
- input.select();
- document.execCommand('copy');
+ <h4>Your Referral Link</h4>
+ <div class="row btw">
+ <code id="your-referral-link"><?= esc_url($share_url)?></code>
+ <button type="button" class="copy" data-target="your-referral-link">
+ Copy Link
+ </button>
+ </div>
- // Visual feedback
- const button = input.nextElementSibling;
- const originalText = button.textContent;
- button.textContent = 'Copied!';
- button.style.background = '#00a32a';
- setTimeout(() => {
- button.textContent = originalText;
- button.style.background = '#2271b1';
- }, 2000);
- }
+ <h4>Your Code</h4>
+ <div class="row btw">
+ <code id="your-referral-code"><?=esc_html($referral_code)?></code>
+ <button type="button" class="copy" data-target="your-referral-code">
+ Copy Code
+ </button>
+ </div>
- // Load stats via AJAX
- (function() {
- fetch('<?php echo rest_url(BASE . '/v1/referrals/stats'); ?>', {
- headers: {
- 'X-WP-Nonce': '<?php echo wp_create_nonce('wp_rest'); ?>'
- }
- })
- .then(response => response.json())
- .then(data => {
- if (data.success && data.stats) {
- const container = document.getElementById('referral-stats-<?php echo $user_id; ?>');
- container.querySelector('[data-stat="total"]').textContent = data.stats.total_referrals || 0;
- container.querySelector('[data-stat="treated"]').textContent = data.stats.treated_count || 0;
- container.querySelector('[data-stat="pending"]').textContent = data.stats.pending_count || 0;
- container.querySelector('[data-stat="rewards"]').textContent =
- '$' + parseFloat(data.stats.available_rewards || 0).toFixed(2);
- }
- })
- .catch(error => console.error('Error loading referral stats:', error));
- })();
- </script>
+ <div class="row btw referral-stats">
+ <div class="stat-item">
+ <span class="stat-value" data-stat="total">-</span>
+ <span class="stat-label">Total Referrals</span>
+ </div>
+ <div class="stat-item">
+ <span class="stat-value" data-stat="treated">-</span>
+ <span class="stat-label">Successful</span>
+ </div>
+ <div class="stat-item">
+ <span class="stat-value" data-stat="pending">-</span>
+ <span class="stat-label">Pending</span>
+ </div>
+ <div class="stat-item">
+ <span class="stat-value" data-stat="rewards">$0.00</span>
+ <span class="stat-label">Available Rewards</span>
+ </div>
+ </div>
+
<?php
return ob_get_clean();
}
@@ -1010,27 +1199,702 @@
return $content;
}
- $settings = get_option(BASE . 'referral_settings', []);
- $reward_amount = $settings['referee_reward_amount'] ?? 20;
- $reward_type = $settings['referee_reward_type'] ?? 'percentage';
+ $reward_amount = $this->settings['referee_reward_amount'] ?? 20;
+ $reward_type = $this->settings['referee_reward_type'] ?? 'percentage';
$reward_text = $reward_type === 'percentage'
? $reward_amount . '% off'
: '$' . number_format($reward_amount, 2) . ' off';
- $bonus_content = '<div style="background: #e7f5ff; padding: 20px; border-radius: 8px; margin: 20px 0;">';
- $bonus_content .= '<h3 style="margin-top: 0; color: #2271b1;">🎉 Welcome Bonus!</h3>';
- $bonus_content .= '<p>Since you were referred by a friend, you\'ve earned <strong>' . $reward_text . '</strong> your first booking!</p>';
- $bonus_content .= '<p>Your reward will be automatically applied when you book.</p>';
- $bonus_content .= '</div>';
+ $bonus_content = sprintf(
+ '
+ <h3>Thanks for the ♡</h3>
+ <p>Since you were referred by a friend, you\'ve earned <strong>%s</strong> your first booking!</p>
+ <p>Your reward will be automatically applied when you book.</p>',
+ $reward_text,
+ );
- // Insert bonus content after the first paragraph
- $parts = explode('</p>', $content, 2);
- if (count($parts) === 2) {
- return $parts[0] . '</p>' . $bonus_content . $parts[1];
+
+ $code = $this->getUserReferralCode($user->ID);
+ $yourCode = '';
+ if (!is_wp_error($code)) {
+ $share_url = $this->getShareURL($code);
+ $yourCode = sprintf(
+ '<div class="callout">
+ <h3>Share the ♡ with Friends</h3>
+ <p>If you find you love what we can do for you, you can share your own code!</p>
+ <p>Your Referral Code: <strong>%s</strong></p>
+ <p>Or click the button below:</p>
+ %s
+ </div>',
+ jvbEmailLink($code),
+ jvbMailButton($share_url, 'Share Your Code')
+ );
}
- return $content . $bonus_content;
+
+ return $content . $bonus_content . $yourCode;
+ }
+
+ public function getShareURL(string $code):string
+ {
+ return add_query_arg(
+ [
+ 'ref' => $code,
+ 'action' => 'register'
+ ],
+ wp_login_url()
+ );
+ }
+
+ /**
+ * 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
+ $reward_text = $this->settings['referee_reward_type'] === 'percentage'
+ ? "Get {$this->settings['referee_reward_amount']}% off your first treatment!"
+ : "Get \${$this->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;
+ }
+
+ /**
+ * Add referral settings subpage to admin menu
+ *
+ * @param array $subpages
+ * @return array
+ */
+ public function addSubpage(array $subpages): array
+ {
+ $subpages[] = [
+ 'page_title' => 'Referral Settings',
+ 'menu_title' => 'Referrals',
+ 'capability' => 'manage_options',
+ 'menu_slug' => 'jvb-referrals',
+ 'callback' => [$this, 'renderAdminPage'],
+ 'icon' => 'users',
+ ];
+
+ return $subpages;
+ }
+
+ /**
+ * Register settings
+ */
+ public function registerSettings(): void
+ {
+ register_setting(
+ BASE . 'referral_settings',
+ BASE . 'referral_page_id',
+ [
+ 'type' => 'integer',
+ 'sanitize_callback' => 'absint',
+ 'default' => 0
+ ]
+ );
+
+ register_setting(
+ BASE . 'referral_settings',
+ BASE . 'referral_reward_settings',
+ [
+ 'type' => 'array',
+ 'sanitize_callback' => [$this, 'sanitizeRewardSettings'],
+ 'default' => $this->default_settings
+ ]
+ );
+ }
+
+ /**
+ * Sanitize reward settings
+ */
+ 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']
+ : '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']
+ : 'fixed',
+ 'referee_reward_type' => in_array($settings['referee_reward_type'] ?? '', ['percentage', 'fixed'])
+ ? $settings['referee_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']
+ : 'first_order',
+ ];
+ }
+
+ /**
+ * Render the admin settings page
+ */
+ public function renderAdminPage(): void
+ {
+ // Handle form submission
+ if (isset($_POST['submit']) && check_admin_referer(BASE . 'referral_settings_nonce')) {
+ update_option(BASE . 'referral_page_id', absint($_POST[BASE . 'referral_page_id'] ?? 0));
+
+ $reward_settings = [
+ 'referrer_reward_applies_to' => sanitize_text_field($_POST['referrer_reward_applies_to'] ?? 'per_user'),
+ 'referrer_reward_amount' => floatval($_POST['referrer_reward_amount'] ?? 25.00),
+ 'referrer_reward_type' => sanitize_text_field($_POST['referrer_reward_type'] ?? 'fixed'),
+ 'referee_reward_type' => sanitize_text_field($_POST['referee_reward_type'] ?? 'percentage'),
+ 'referee_reward_amount' => floatval($_POST['referee_reward_amount'] ?? 20),
+ 'referee_reward_applies_to' => sanitize_text_field($_POST['referee_reward_applies_to'] ?? 'first_order'),
+ ];
+
+ update_option(BASE . 'referral_reward_settings', $this->sanitizeRewardSettings($reward_settings));
+
+ echo '<div class="notice notice-success is-dismissible"><p>Settings saved successfully.</p></div>';
+ }
+
+ echo $this->renderAdminHTML();
+ }
+
+ protected function renderAdminHTML():string
+ {
+ ob_start();
+ ?>
+ <div class="wrap">
+ <h1>Referral Settings</h1>
+
+ <form method="post" action="">
+ <?php wp_nonce_field(BASE . 'referral_settings_nonce'); ?>
+
+ <div class="card">
+ <h2>Referral Page</h2>
+ <p>Select the page where users can access their referral dashboard.</p>
+
+ <table class="form-table">
+ <tr>
+ <th scope="row">
+ <label for="<?= BASE ?>referral_page_id">Referral Page</label>
+ </th>
+ <td>
+ <?php
+ wp_dropdown_pages([
+ 'name' => BASE . 'referral_page_id',
+ 'id' => BASE . 'referral_page_id',
+ 'selected' => $this->referralPage,
+ 'show_option_none' => __('— Select —', 'jvbase'),
+ 'option_none_value' => '0'
+ ]);
+ ?>
+ <p class="description">
+ This page will show "Referral Page" in the admin bar when editing.
+ </p>
+ </td>
+ </tr>
+ </table>
+ </div>
+
+ <div class="card">
+ <h2>Reward Settings</h2>
+
+ <table class="form-table">
+ <tr>
+ <th colspan="2"><h3>Referrer Rewards</h3></th>
+ </tr>
+ <tr>
+ <th scope="row">
+ <label for="referrer_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>
+ </td>
+ </tr>
+ <tr>
+ <th scope="row">
+ <label for="referrer_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']) ?>"
+ step="0.01"
+ min="0">
+ <p class="description">Amount in dollars or percentage</p>
+ </td>
+ </tr>
+ <tr>
+ <th scope="row">
+ <label for="referrer_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>
+ </td>
+ </tr>
+
+ <tr>
+ <th colspan="2"><h3>Referee (New User) Rewards</h3></th>
+ </tr>
+ <tr>
+ <th scope="row">
+ <label for="referee_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>
+ </td>
+ </tr>
+ <tr>
+ <th scope="row">
+ <label for="referee_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']) ?>"
+ step="0.01"
+ min="0">
+ <p class="description">Amount in dollars or percentage</p>
+ </td>
+ </tr>
+ <tr>
+ <th scope="row">
+ <label for="referee_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>
+ </td>
+ </tr>
+ </table>
+ </div>
+
+ <p class="submit">
+ <button type="submit" name="submit" class="button button-primary">Save Settings</button>
+ </p>
+ </form>
+
+ <?= $this->renderReferralStats(true) ?>
+ </div>
+ <?php
+ return ob_get_clean();
+ }
+
+ /**
+ * Render referral statistics
+ */
+ protected function renderReferralStats(bool $wrapCard = false):string
+ {
+ ob_start();
+ global $wpdb;
+
+ $total_referrals = $wpdb->get_var("SELECT COUNT(*) FROM {$this->referrals_table}");
+ $pending_referrals = $wpdb->get_var("SELECT COUNT(*) FROM {$this->referrals_table} WHERE status = 'pending'");
+ $treated_referrals = $wpdb->get_var("SELECT COUNT(*) FROM {$this->referrals_table} WHERE status = 'treated'");
+
+ ?>
+ <table class="widefat">
+ <tr>
+ <th>Total Referrals</th>
+ <td><?= esc_html($total_referrals) ?></td>
+ </tr>
+ <tr>
+ <th>Pending</th>
+ <td><?= esc_html($pending_referrals) ?></td>
+ </tr>
+ <tr>
+ <th>Treated</th>
+ <td><?= esc_html($treated_referrals) ?></td>
+ </tr>
+ </table>
+ <?php
+ $table = ob_get_clean();
+ if ($wrapCard) {
+ $table = '<div class="card">
+ <h2>Referral Statistics</h2>
+ '.$table.'
+ </div>';
+ }
+ return $table;
+ }
+
+ /**
+ * Add "Referral Page" label to admin bar
+ *
+ * @param WP_Admin_Bar $wp_admin_bar
+ */
+ public function addReferralPageLabel($wp_admin_bar): void
+ {
+ if (!is_admin()) {
+ return;
+ }
+
+ if (!$this->referralPage) {
+ return;
+ }
+
+ global $pagenow, $post;
+
+ // Check if we're editing the referral page
+ if ('post.php' === $pagenow && $post && $post->ID === $this->referralPage) {
+ $wp_admin_bar->add_node([
+ 'id' => 'referral-page',
+ 'parent' => 'top-secondary',
+ 'title' => __('Referral Page', 'jvbase'),
+ 'meta' => [
+ 'class' => 'referral-page-notice'
+ ]
+ ]);
+ }
+ }
+
+ /**
+ * Get the referral page ID
+ *
+ * @return int|null
+ */
+ public function getReferralPageId(): ?int
+ {
+ $page_id = get_option(BASE . 'referral_page_id');
+ return $page_id ? (int) $page_id : null;
+ }
+
+ /**
+ * Show admin notice on referral page edit screen
+ */
+ public function showReferralPageNotice(): void
+ {
+ global $pagenow, $post;
+
+ if ('post.php' !== $pagenow || !$post) {
+ return;
+ }
+
+ if ($post->ID === $this->referralPage) {
+ echo '<div class="notice notice-info">';
+ echo '<p>' . __('This page is designated as the <strong>Referral Page</strong>.', 'jvbase') . '</p>';
+ echo '</div>';
+ }
+ }
+
+ public function renderDashPage(string $content, string $page):string
+ {
+ if ($page !== 'referrals') {
+ return $content;
+ }
+ $out = '';
+ if (current_user_can('manage_options')) {
+ $out .= $this->renderAdminHTML();
+ } else {
+ $out .= $this->renderReferralStats(true);
+ }
+ return ($out === '') ? $content : '<form id="referrals" class="col" data-save="referrals">'.$out.'</form>';
}
}
--
Gitblit v1.10.0