Test your pricing by entering an LOI. Uses the saved rates above and live exchange rates.
INR Price
—
USD Price
—
EUR Price
—
query("SELECT p.id, p.project_id as project_code, p.client_id FROM projects p LEFT JOIN invoices i ON i.project_id=p.id WHERE p.status='Closed' AND i.id IS NULL");
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $m) {
if (function_exists('generateProjectInvoice')) generateProjectInvoice($pdo, $m['id'], $m['project_code'], $m['client_id']);
}
} catch (Exception $e) {}
// Update statuses
if (function_exists('updateInvoiceStatuses')) updateInvoiceStatuses($pdo);
// Handle invoice POST actions
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
$invAction = $_POST['action'];
$invId = intval($_POST['invoice_id'] ?? 0);
if ($invAction === 'mark_paid' && $invId) {
$pdo->prepare("UPDATE invoices SET status='paid', paid_date=?, payment_reference=?, remarks=?, updated_at=NOW() WHERE id=?")->execute([$_POST['paid_date'] ?? date('Y-m-d'), trim($_POST['payment_reference'] ?? ''), trim($_POST['remarks'] ?? ''), $invId]);
$success = 'Invoice marked as paid.';
logActivity($_SESSION['admin_id'], 'update', "Marked invoice #$invId as paid", 'invoice', $invId);
}
if ($invAction === 'save_remarks' && $invId) {
$pdo->prepare("UPDATE invoices SET remarks=?, updated_at=NOW() WHERE id=?")->execute([trim($_POST['remarks'] ?? ''), $invId]);
$success = 'Remarks saved.';
}
if ($invAction === 'revert_unpaid' && $invId) {
$pdo->prepare("UPDATE invoices SET status='invoiced', paid_date=NULL, payment_reference=NULL, updated_at=NOW() WHERE id=?")->execute([$invId]);
if (function_exists('updateInvoiceStatuses')) updateInvoiceStatuses($pdo);
$success = 'Invoice reverted to unpaid.';
}
}
// Ban enforcement
try {
$bannedClients = $pdo->query("SELECT DISTINCT client_id FROM invoices WHERE status != 'paid' AND due_date < DATE_SUB(CURDATE(), INTERVAL 90 DAY)")->fetchAll(PDO::FETCH_COLUMN);
if (!empty($bannedClients)) {
$ph = implode(',', array_fill(0, count($bannedClients), '?'));
$pdo->prepare("UPDATE projects SET status='On hold' WHERE client_id IN ($ph) AND status='Live'")->execute($bannedClients);
}
} catch (Exception $e) {}
// Invoice filters
$fStatus=$_GET['inv_status']??''; $fClient=$_GET['inv_client']??''; $fDateFrom=$_GET['inv_from']??'';
$fDateTo=$_GET['inv_to']??''; $fSearch=trim($_GET['inv_search']??''); $fCurrency=$_GET['inv_currency']??'';
$fDueFrom=$_GET['inv_due_from']??''; $fDueTo=$_GET['inv_due_to']??'';
$invSort=$_GET['inv_sort']??'invoice_date'; $invDir=($_GET['inv_dir']??'desc')==='asc'?'ASC':'DESC';
$invAllowed=['invoice_number','invoice_date','due_date','total_amount','status','project_code','client_name'];
if (!in_array($invSort,$invAllowed)) $invSort='invoice_date';
$invWhere=["1=1"]; $invParams=[];
if($fStatus){$invWhere[]="i.status=?";$invParams[]=$fStatus;}
if($fClient){$invWhere[]="i.client_id=?";$invParams[]=(int)$fClient;}
if($fCurrency){$invWhere[]="i.currency=?";$invParams[]=$fCurrency;}
if($fDateFrom){$invWhere[]="i.invoice_date>=?";$invParams[]=$fDateFrom;}
if($fDateTo){$invWhere[]="i.invoice_date<=?";$invParams[]=$fDateTo;}
if($fDueFrom){$invWhere[]="i.due_date>=?";$invParams[]=$fDueFrom;}
if($fDueTo){$invWhere[]="i.due_date<=?";$invParams[]=$fDueTo;}
if($fSearch){$invWhere[]="(i.invoice_number LIKE ? OR p.project_name LIKE ? OR i.project_code LIKE ? OR c.company_name LIKE ?)";$s="%$fSearch%";$invParams=array_merge($invParams,[$s,$s,$s,$s]);}
$invWc=implode(' AND ',$invWhere);
$stmt=$pdo->prepare("SELECT i.*,p.project_name,p.industry,c.company_name as client_company,c.contact_person FROM invoices i INNER JOIN projects p ON i.project_id=p.id INNER JOIN clients c ON i.client_id=c.id WHERE $invWc ORDER BY $invSort $invDir");
$stmt->execute($invParams);
$allInvoices=$stmt->fetchAll(PDO::FETCH_ASSOC);
$invSumAll=$invSumPaid=$invSumOut=0;
$invSc=['invoiced'=>0,'paid'=>0,'due'=>0,'overdue'=>0,'critical'=>0,'banned'=>0];
foreach($allInvoices as $inv){
$invSumAll+=$inv['total_amount'];
if($inv['status']==='paid')$invSumPaid+=$inv['total_amount'];else $invSumOut+=$inv['total_amount'];
$invSc[$inv['status']]=($invSc[$inv['status']]??0)+1;
}
$allClients=$pdo->query("SELECT id,company_name FROM clients ORDER BY company_name")->fetchAll(PDO::FETCH_ASSOC);
?>
Default: every member earns ₹5 per verified-email referral. Enable below to give this member tiered bonuses instead. Tier 1 (first 100) is always ₹5 and cannot be changed.
Current verified referrals by this member:
·
Next referral falls in:
Account Information
Email Verified
✓ Yes' : '✗ No'; ?>
Onboarding Completed
✓ Yes' : '⏳ No'; ?>
Member Since
Last Updated
-------------------- END OF FILE --------------------
### FILE 22: RR SHOP/member_view.php
- Type: PHP
- Size: 91.14 KB
- Path: RR SHOP
- Name: member_view.php
------------------------------------------------------------
prepare("
SELECT
u.*,
up.points as current_points,
up.total_earned,
up.total_redeemed,
mv.mobile_number,
mv.is_verified as mobile_verified,
mv.verified_at as mobile_verified_at
FROM users u
LEFT JOIN user_points up ON u.id = up.user_id
LEFT JOIN mobile_verifications mv ON u.id = mv.user_id
WHERE u.id = ?
");
$stmt->execute([$member_id]);
$member = $stmt->fetch();
if (!$member) {
header('Location: panel.php');
exit;
}
// Fetch profiler completion
$completionStmt = $panelPdo->prepare("
SELECT * FROM profiler_completion WHERE user_id = ?
");
$completionStmt->execute([$member_id]);
$completionData = $completionStmt->fetchAll();
// Fetch actual profiler responses for this member
$profilerResponses = [];
try {
$respStmt = $panelPdo->prepare("SELECT section, question_id, response FROM user_profiler WHERE user_id = ? ORDER BY section, question_id");
$respStmt->execute([$member_id]);
while ($row = $respStmt->fetch()) {
if (!isset($profilerResponses[$row['section']])) $profilerResponses[$row['section']] = [];
$profilerResponses[$row['section']][$row['question_id']] = json_decode($row['response'], true);
}
} catch (Exception $e) { $profilerResponses = []; }
// Extract PAN & UPI details from profile section
$memberPanNumber = $profilerResponses['profile']['pan_number'] ?? '';
$memberPanName = $profilerResponses['profile']['pan_name'] ?? '';
$memberPanStatus = $profilerResponses['profile']['pan_status'] ?? '';
if (!empty($memberPanNumber) && empty($memberPanStatus)) $memberPanStatus = 'pending';
$memberUpiId = $profilerResponses['profile']['upi_id'] ?? '';
// Profiler question definitions for human-readable labels
$profilerDefs = [
'personal_background' => [
'name' => 'Personal Background',
'questions' => [
'education_level' => ['q'=>'Highest level of education','opts'=>['below_10th'=>'Below 10th Standard','10th_pass'=>'10th Standard','12th_pass'=>'12th Standard/Intermediate','diploma'=>'Diploma/ITI','graduation'=>'Graduation','post_graduation'=>'Post Graduation','professional'=>'Professional Degree','doctorate'=>'Doctorate/PhD']],
'employment_status' => ['q'=>'Current employment status','opts'=>['student'=>'Student','employed_private'=>'Employed - Private Sector','employed_government'=>'Employed - Government/Public Sector','self_employed'=>'Self Employed/Business Owner','freelancer'=>'Freelancer/Consultant','homemaker'=>'Homemaker','retired'=>'Retired','unemployed'=>'Currently Unemployed']],
'occupation_sector' => ['q'=>'Work sector','opts'=>['it_software'=>'IT/Software','banking_finance'=>'Banking & Finance','healthcare'=>'Healthcare','education'=>'Education','manufacturing'=>'Manufacturing','retail'=>'Retail','media'=>'Media & Entertainment','government'=>'Government','telecom'=>'Telecommunications','fmcg'=>'FMCG','auto'=>'Automotive','real_estate'=>'Real Estate','agriculture'=>'Agriculture','legal'=>'Legal','other'=>'Other']],
'monthly_income' => ['q'=>'Monthly household income range','opts'=>['below_15000'=>'Below ₹15,000','15000_25000'=>'₹15,000 - ₹25,000','25000_50000'=>'₹25,000 - ₹50,000','50000_75000'=>'₹50,000 - ₹75,000','75000_100000'=>'₹75,000 - ₹1,00,000','100000_200000'=>'₹1,00,000 - ₹2,00,000','200000_500000'=>'₹2,00,000 - ₹5,00,000','above_500000'=>'Above ₹5,00,000','prefer_not_to_say'=>'Prefer not to say']],
'marital_status' => ['q'=>'Marital status','opts'=>['single'=>'Single','married'=>'Married','divorced'=>'Divorced','widowed'=>'Widowed','separated'=>'Separated','live_in'=>'Live-in Relationship']],
'language_primary' => ['q'=>'Primary language spoken at home','opts'=>['hindi'=>'Hindi','english'=>'English','tamil'=>'Tamil','telugu'=>'Telugu','marathi'=>'Marathi','bengali'=>'Bengali','gujarati'=>'Gujarati','kannada'=>'Kannada','malayalam'=>'Malayalam','punjabi'=>'Punjabi','odia'=>'Odia','urdu'=>'Urdu','assamese'=>'Assamese','other'=>'Other']],
'languages_known' => ['q'=>'Languages you can read/write fluently','opts'=>['hindi'=>'Hindi','english'=>'English','tamil'=>'Tamil','telugu'=>'Telugu','marathi'=>'Marathi','bengali'=>'Bengali','gujarati'=>'Gujarati','kannada'=>'Kannada','malayalam'=>'Malayalam','punjabi'=>'Punjabi','odia'=>'Odia','urdu'=>'Urdu','assamese'=>'Assamese','other'=>'Other']],
'area_type' => ['q'=>'Area type','opts'=>['metro'=>'Metro City','tier1'=>'Tier 1 City','tier2'=>'Tier 2 City','tier3'=>'Tier 3/Small Town','semi_urban'=>'Semi-Urban','rural'=>'Rural/Village']],
]
],
'household_family' => [
'name' => 'Household & Family',
'questions' => [
'household_size' => ['q'=>'People living in household','opts'=>['1'=>'Just me','2'=>'2 people','3'=>'3 people','4'=>'4 people','5'=>'5 people','6_plus'=>'6 or more']],
'children_count' => ['q'=>'Number of children','opts'=>['none'=>'No children','1'=>'1 child','2'=>'2 children','3'=>'3 children','4_plus'=>'4 or more']],
'children_age_group' => ['q'=>'Age groups of children','opts'=>['infant'=>'0-2 years (Infant)','toddler'=>'3-5 years (Toddler)','child'=>'6-12 years (Child)','teen'=>'13-17 years (Teen)','adult'=>'18+ years (Adult)','not_applicable'=>'Not Applicable']],
'home_ownership' => ['q'=>'Home ownership status','opts'=>['own_house'=>'Own House','rented'=>'Rented','family_owned'=>'Family Owned','company_provided'=>'Company Provided','paying_guest'=>'Paying Guest/Hostel']],
'vehicle_ownership' => ['q'=>'Vehicles owned','opts'=>['two_wheeler'=>'Two Wheeler (Bike/Scooter)','car_hatchback'=>'Car - Hatchback','car_sedan'=>'Car - Sedan','car_suv'=>'Car - SUV/MUV','ev_two_wheeler'=>'EV Two Wheeler','ev_car'=>'Electric Car','bicycle'=>'Bicycle','none'=>'No Vehicle','commercial'=>'Commercial Vehicle']],
'domestic_help' => ['q'=>'Domestic help employed','opts'=>['full_time'=>'Full-time','part_time'=>'Part-time','cook'=>'Cook only','driver'=>'Driver','none'=>'No domestic help']],
'decision_maker' => ['q'=>'Primary decision maker for purchases','opts'=>['self'=>'Self','spouse'=>'Spouse/Partner','parents'=>'Parents','joint'=>'Joint Decision','other'=>'Other Family Member']],
'pet_ownership' => ['q'=>'Pets owned','opts'=>['dog'=>'Dog','cat'=>'Cat','bird'=>'Bird','fish'=>'Fish','other_pet'=>'Other','no_pets'=>'No Pets']],
'household_income' => ['q'=>'Total monthly household income','opts'=>['below_15k'=>'Below ₹15,000','15k_25k'=>'₹15,000 - ₹25,000','25k_50k'=>'₹25,000 - ₹50,000','50k_75k'=>'₹50,000 - ₹75,000','75k_1l'=>'₹75,000 - ₹1,00,000','1l_2l'=>'₹1,00,000 - ₹2,00,000','2l_5l'=>'₹2,00,000 - ₹5,00,000','above_5l'=>'Above ₹5,00,000','prefer_not'=>'Prefer not to say']],
]
],
'shopping_lifestyle' => [
'name' => 'Shopping & Lifestyle',
'questions' => [
'online_shopping_frequency' => ['q'=>'Online shopping frequency','opts'=>['daily'=>'Almost daily','weekly'=>'Weekly','biweekly'=>'Every 2 weeks','monthly'=>'Monthly','quarterly'=>'Every few months','rarely'=>'Rarely','never'=>'Never']],
'preferred_ecommerce' => ['q'=>'Preferred e-commerce platforms','opts'=>['amazon'=>'Amazon','flipkart'=>'Flipkart','myntra'=>'Myntra','meesho'=>'Meesho','ajio'=>'AJIO','nykaa'=>'Nykaa','tatacliq'=>'Tata CLiQ','jiomart'=>'JioMart','snapdeal'=>'Snapdeal','bigbasket'=>'BigBasket','other'=>'Other']],
'monthly_online_spend' => ['q'=>'Monthly online shopping spend','opts'=>['below_500'=>'Below ₹500','500_2000'=>'₹500 - ₹2,000','2000_5000'=>'₹2,000 - ₹5,000','5000_10000'=>'₹5,000 - ₹10,000','10000_25000'=>'₹10,000 - ₹25,000','above_25000'=>'Above ₹25,000']],
'shopping_categories' => ['q'=>'Frequently purchased categories','opts'=>['electronics'=>'Electronics & Gadgets','fashion'=>'Fashion & Clothing','beauty'=>'Beauty & Personal Care','home_kitchen'=>'Home & Kitchen','grocery'=>'Groceries & Essentials','books'=>'Books & Stationery','sports'=>'Sports & Fitness','baby'=>'Baby & Kids','health'=>'Health & Wellness','jewelry'=>'Jewelry & Accessories','automotive'=>'Automotive Accessories']],
'brand_preference' => ['q'=>'Brand preference','opts'=>['premium'=>'Prefer premium brands','mid_range'=>'Prefer mid-range brands','value'=>'Prefer value/budget brands','no_preference'=>'No brand preference','mix'=>'Mix of all']],
'deal_influence' => ['q'=>'Influence of deals on purchases','opts'=>['always'=>'Always wait for deals','often'=>'Often look for deals','sometimes'=>'Sometimes check','rarely'=>'Rarely care about deals','never'=>'Never influenced by deals']],
]
],
'technology_digital' => [
'name' => 'Technology & Digital',
'questions' => [
'smartphone_brand' => ['q'=>'Current smartphone brand','opts'=>['samsung'=>'Samsung','apple'=>'Apple','xiaomi'=>'Xiaomi/Redmi','realme'=>'Realme','oppo'=>'OPPO','vivo'=>'Vivo','oneplus'=>'OnePlus','nothing'=>'Nothing','google'=>'Google Pixel','motorola'=>'Motorola','other'=>'Other']],
'smartphone_price' => ['q'=>'Smartphone price range','opts'=>['below_10000'=>'Below ₹10,000','10000_15000'=>'₹10,000 - ₹15,000','15000_25000'=>'₹15,000 - ₹25,000','25000_40000'=>'₹25,000 - ₹40,000','40000_60000'=>'₹40,000 - ₹60,000','above_60000'=>'Above ₹60,000']],
'internet_provider' => ['q'=>'Primary internet provider','opts'=>['jio'=>'Jio','airtel'=>'Airtel','vi'=>'Vi (Vodafone Idea)','bsnl'=>'BSNL','act'=>'ACT Fibernet','tataplay'=>'Tata Play Fiber','local_isp'=>'Local ISP','other'=>'Other']],
'daily_internet_hours' => ['q'=>'Daily internet usage hours','opts'=>['less_1'=>'Less than 1 hour','1_3'=>'1-3 hours','3_5'=>'3-5 hours','5_8'=>'5-8 hours','above_8'=>'More than 8 hours']],
'social_media_usage' => ['q'=>'Social media platforms used regularly','opts'=>['whatsapp'=>'WhatsApp','instagram'=>'Instagram','facebook'=>'Facebook','youtube'=>'YouTube','twitter'=>'Twitter/X','linkedin'=>'LinkedIn','snapchat'=>'Snapchat','telegram'=>'Telegram','koo'=>'Koo','pinterest'=>'Pinterest','reddit'=>'Reddit','threads'=>'Threads']],
'smart_devices' => ['q'=>'Smart devices owned','opts'=>['smart_tv'=>'Smart TV','smart_watch'=>'Smartwatch/Band','smart_speaker'=>'Smart Speaker','tablet'=>'Tablet/iPad','laptop'=>'Laptop','desktop'=>'Desktop PC','gaming_console'=>'Gaming Console','smart_home'=>'Smart Home Devices','none'=>'None of these']],
'streaming_services' => ['q'=>'Streaming services subscribed to','opts'=>['netflix'=>'Netflix','prime_video'=>'Amazon Prime Video','hotstar'=>'Disney+ Hotstar','sonyliv'=>'SonyLIV','zee5'=>'ZEE5','jiocinema'=>'JioCinema','youtube_premium'=>'YouTube Premium','spotify'=>'Spotify','apple_music'=>'Apple Music','none'=>'None']],
]
],
'travel_transportation' => [
'name' => 'Travel & Transportation',
'questions' => [
'daily_commute' => ['q'=>'Primary daily commute mode','opts'=>['own_car'=>'Own Car','own_bike'=>'Own Bike/Scooter','public_bus'=>'Public Bus','metro_train'=>'Metro/Local Train','auto_rickshaw'=>'Auto Rickshaw','cab_ola_uber'=>'Cab (Ola/Uber)','bicycle'=>'Bicycle','walk'=>'Walk','work_from_home'=>'Work from Home']],
'travel_frequency' => ['q'=>'Domestic travel frequency (per year)','opts'=>['never'=>'Rarely/Never','1_2'=>'1-2 times','3_5'=>'3-5 times','6_10'=>'6-10 times','above_10'=>'More than 10 times']],
'international_travel' => ['q'=>'International travel experience','opts'=>['never'=>'Never traveled abroad','once'=>'Once','2_5'=>'2-5 times','6_plus'=>'More than 5 times','frequent'=>'Frequent international traveler']],
'travel_booking' => ['q'=>'Travel booking platforms used','opts'=>['makemytrip'=>'MakeMyTrip','goibibo'=>'Goibibo','irctc'=>'IRCTC','booking_com'=>'Booking.com','cleartrip'=>'Cleartrip','yatra'=>'Yatra','airbnb'=>'Airbnb','oyo'=>'OYO','travel_agent'=>'Travel Agent','direct'=>'Direct with airline/hotel']],
'travel_preference' => ['q'=>'Travel accommodation preference','opts'=>['budget'=>'Budget Hotels','mid_range'=>'Mid-range Hotels','luxury'=>'Luxury Hotels','homestay'=>'Homestays','hostel'=>'Hostels/Backpacker','resort'=>'Resorts','relative'=>"Relative's/Friend's place"]],
'cab_usage' => ['q'=>'Ride-hailing usage frequency','opts'=>['daily'=>'Daily','weekly'=>'Few times a week','monthly'=>'Few times a month','rarely'=>'Rarely','never'=>'Never use']],
]
],
'health_fitness' => [
'name' => 'Health & Fitness',
'questions' => [
'exercise_frequency' => ['q'=>'Exercise frequency','opts'=>['daily'=>'Daily','4_6'=>'4-6 times a week','2_3'=>'2-3 times a week','once_week'=>'Once a week','rarely'=>'Rarely','never'=>'Never']],
'fitness_activities' => ['q'=>'Fitness activities','opts'=>['gym'=>'Gym/Weight Training','yoga'=>'Yoga','running'=>'Running/Jogging','walking'=>'Walking','swimming'=>'Swimming','cycling'=>'Cycling','sports'=>'Sports','dance'=>'Dance/Zumba','home_workout'=>'Home Workout','martial_arts'=>'Martial Arts','none'=>'None']],
'health_insurance' => ['q'=>'Health insurance coverage','opts'=>['employer'=>'Employer-provided','individual'=>'Individual Policy','family_floater'=>'Family Floater','government'=>'Government Scheme','no_insurance'=>'No Health Insurance']],
'diet_type' => ['q'=>'Dietary preference','opts'=>['vegetarian'=>'Vegetarian','non_vegetarian'=>'Non-Vegetarian','eggetarian'=>'Eggetarian','vegan'=>'Vegan','jain'=>'Jain','flexitarian'=>'Flexitarian']],
'health_conditions' => ['q'=>'Health conditions managed','opts'=>['diabetes'=>'Diabetes','hypertension'=>'Hypertension/BP','thyroid'=>'Thyroid','heart'=>'Heart Condition','asthma'=>'Asthma/Respiratory','allergies'=>'Allergies','obesity'=>'Obesity','none'=>'None','prefer_not'=>'Prefer not to say']],
'wellness_spend' => ['q'=>'Monthly health/wellness spend','opts'=>['below_500'=>'Below ₹500','500_2000'=>'₹500 - ₹2,000','2000_5000'=>'₹2,000 - ₹5,000','5000_10000'=>'₹5,000 - ₹10,000','above_10000'=>'Above ₹10,000']],
'health_tracking' => ['q'=>'Health/fitness tracking tools used','opts'=>['fitness_band'=>'Fitness Band/Smartwatch','phone_app'=>'Phone App','manual'=>'Manual Tracking','health_checkup'=>'Regular Health Checkups','none'=>'None']],
]
],
'entertainment_media' => [
'name' => 'Entertainment & Media',
'questions' => [
'content_language' => ['q'=>'Content consumption languages','opts'=>['hindi'=>'Hindi','english'=>'English','tamil'=>'Tamil','telugu'=>'Telugu','malayalam'=>'Malayalam','kannada'=>'Kannada','bengali'=>'Bengali','marathi'=>'Marathi','punjabi'=>'Punjabi','other'=>'Other']],
'entertainment_preference' => ['q'=>'Entertainment preferences','opts'=>['movies'=>'Movies','web_series'=>'Web Series','tv_shows'=>'TV Shows','music'=>'Music','podcasts'=>'Podcasts','gaming'=>'Gaming','reading'=>'Reading/Books','live_events'=>'Live Events/Concerts','sports_viewing'=>'Watching Sports']],
'news_source' => ['q'=>'Primary news sources','opts'=>['tv_news'=>'TV News Channels','newspaper'=>'Newspaper','news_apps'=>'News Apps','social_media'=>'Social Media','youtube'=>'YouTube','radio'=>'Radio','word_of_mouth'=>'Word of Mouth']],
'gaming_platform' => ['q'=>'Gaming platforms used','opts'=>['mobile'=>'Mobile Gaming','pc'=>'PC Gaming','playstation'=>'PlayStation','xbox'=>'Xbox','nintendo'=>'Nintendo','no_gaming'=>'Do not play games']],
'music_preference' => ['q'=>'Music listening platforms','opts'=>['spotify'=>'Spotify','youtube_music'=>'YouTube Music','jiosaavn'=>'JioSaavn','gaana'=>'Gaana','apple_music'=>'Apple Music','wynk'=>'Wynk','amazon_music'=>'Amazon Music','radio'=>'FM Radio','none'=>'None']],
'cinema_frequency' => ['q'=>'Movie theatre visits per month','opts'=>['never'=>'Never','once'=>'Once a month','2_3'=>'2-3 times a month','weekly'=>'Weekly','more'=>'More than weekly']],
]
],
'food_dining' => [
'name' => 'Food & Dining',
'questions' => [
'dining_out_frequency' => ['q'=>'Dining out/ordering frequency','opts'=>['daily'=>'Almost Daily','3_5_week'=>'3-5 times a week','1_2_week'=>'1-2 times a week','few_month'=>'Few times a month','rarely'=>'Rarely','never'=>'Never']],
'food_delivery_apps' => ['q'=>'Food delivery apps used','opts'=>['zomato'=>'Zomato','swiggy'=>'Swiggy','eatsure'=>'EatSure','dominos'=>'Domino\'s App','mcdonalds'=>'McDonald\'s App','other'=>'Other App','none'=>'Don\'t use delivery apps']],
'cuisine_preference' => ['q'=>'Favourite cuisines','opts'=>['north_indian'=>'North Indian','south_indian'=>'South Indian','chinese'=>'Chinese/Indo-Chinese','italian'=>'Italian/Pizza/Pasta','mughlai'=>'Mughlai/Biryani','street_food'=>'Street Food','continental'=>'Continental','japanese'=>'Japanese/Sushi','thai'=>'Thai','fast_food'=>'Fast Food/Burgers','healthy'=>'Healthy/Salads']],
'cooking_frequency' => ['q'=>'Home cooking frequency','opts'=>['all_meals'=>'All meals at home','mostly'=>'Most meals at home','half'=>'About half','rarely'=>'Rarely cook','never'=>'Never cook']],
'monthly_food_spend' => ['q'=>'Monthly food/dining spend','opts'=>['below_2000'=>'Below ₹2,000','2000_5000'=>'₹2,000 - ₹5,000','5000_10000'=>'₹5,000 - ₹10,000','10000_20000'=>'₹10,000 - ₹20,000','above_20000'=>'Above ₹20,000']],
'beverage_preference' => ['q'=>'Daily beverages','opts'=>['tea'=>'Tea (Chai)','coffee'=>'Coffee','green_tea'=>'Green Tea','juice'=>'Fresh Juice','soft_drinks'=>'Soft Drinks/Cola','energy_drinks'=>'Energy Drinks','water_only'=>'Plain Water Only','milk'=>'Milk/Flavored Milk','alcohol'=>'Alcoholic Beverages']],
]
],
'financial_services' => [
'name' => 'Financial Services',
'questions' => [
'bank_account' => ['q'=>'Primary bank','opts'=>['sbi'=>'SBI','hdfc'=>'HDFC Bank','icici'=>'ICICI Bank','axis'=>'Axis Bank','kotak'=>'Kotak Mahindra','pnb'=>'PNB','bob'=>'Bank of Baroda','idfc'=>'IDFC First','yes'=>'Yes Bank','indusind'=>'IndusInd','other_private'=>'Other Private Bank','other_psu'=>'Other PSU Bank','payment_bank'=>'Payment Bank (Paytm/Airtel)']],
'credit_card' => ['q'=>'Credit card ownership','opts'=>['yes_multiple'=>'Yes, multiple cards','yes_one'=>'Yes, one card','no_debit'=>'No, use debit card only','no_card'=>'No cards']],
'credit_products' => ['q'=>'Credit products used','opts'=>['credit_card'=>'Credit card','personal_loan'=>'Personal loan','home_loan'=>'Home loan','car_loan'=>'Car loan','education_loan'=>'Education loan','bnpl'=>'Buy Now Pay Later','gold_loan'=>'Gold loan','none'=>'None']],
'investment_types' => ['q'=>'Investment instruments used','opts'=>['fd'=>'Fixed Deposits','mutual_funds'=>'Mutual Funds','stocks'=>'Direct Stocks','ppf'=>'PPF/EPF','gold'=>'Gold/Gold Bonds','real_estate'=>'Real Estate','nps'=>'NPS','crypto'=>'Cryptocurrency','insurance'=>'Insurance Plans','none'=>'None']],
'upi_usage' => ['q'=>'UPI/Digital payment usage','opts'=>['primary'=>'Primary payment method','frequent'=>'Frequently use','sometimes'=>'Sometimes use','rarely'=>'Rarely use','never'=>'Never use']],
'upi_apps' => ['q'=>'UPI apps used','opts'=>['gpay'=>'Google Pay','phonepe'=>'PhonePe','paytm'=>'Paytm','amazon_pay'=>'Amazon Pay','bhim'=>'BHIM','cred'=>'CRED','whatsapp_pay'=>'WhatsApp Pay','bank_app'=>'Bank\'s own app','other'=>'Other']],
]
],
'communication_payments' => [
'name' => 'Communication & Payments',
'questions' => [
'mobile_operator' => ['q'=>'Primary mobile operator','opts'=>['jio'=>'Jio','airtel'=>'Airtel','vi'=>'Vi (Vodafone Idea)','bsnl'=>'BSNL','mtnl'=>'MTNL','other'=>'Other']],
'mobile_plan_spend' => ['q'=>'Monthly mobile plan spend','opts'=>['below_200'=>'Below ₹200','200_500'=>'₹200 - ₹500','500_1000'=>'₹500 - ₹1,000','1000_2000'=>'₹1,000 - ₹2,000','above_2000'=>'Above ₹2,000']],
'sim_type' => ['q'=>'SIM type','opts'=>['prepaid'=>'Prepaid','postpaid'=>'Postpaid','both'=>'Both']],
'communication_apps' => ['q'=>'Communication apps used daily','opts'=>['whatsapp'=>'WhatsApp','telegram'=>'Telegram','signal'=>'Signal','messenger'=>'Facebook Messenger','teams'=>'Microsoft Teams','zoom'=>'Zoom','google_meet'=>'Google Meet','discord'=>'Discord','slack'=>'Slack']],
'digital_wallet_balance' => ['q'=>'Average digital wallet balance maintained','opts'=>['below_500'=>'Below ₹500','500_2000'=>'₹500 - ₹2,000','2000_5000'=>'₹2,000 - ₹5,000','above_5000'=>'Above ₹5,000','dont_maintain'=>'Don\'t maintain balance']],
'online_purchase_category' => ['q'=>'Categories of online purchases in last 3 months','opts'=>['electronics'=>'Electronics','fashion'=>'Fashion & Clothing','grocery'=>'Groceries','food_delivery'=>'Food Delivery','travel'=>'Travel/Booking','entertainment'=>'Entertainment/Subscriptions','education'=>'Education/Courses','health'=>'Health/Pharmacy','financial'=>'Financial/Insurance','products'=>'Product reviews and feedback','none'=>'None of these']],
]
],
'household_classification' => [
'name' => 'Household Classification (ISEC)',
'questions' => [
'cwe_occupation' => ['q'=>'Occupation of Chief Wage Earner (CWE)','opts'=>['unskilled'=>'Unskilled Worker','skilled'=>'Skilled Worker','petty_trader'=>'Petty Trader','shop_owner'=>'Shop Owner / Businessman (No employees)','businessman_employer'=>'Businessman / Industrialist (1+ employees)','self_employed'=>'Self Employed Professional (Doctor, Lawyer, CA etc.)']],
'male_education_21plus' => ['q'=>'Education of highest educated male aged 21+','opts'=>['illiterate'=>'Illiterate','literate_no_formal'=>'Literate but no formal schooling','school_5_9'=>'School — 5th to 9th Standard','ssc_hsc'=>'SSC / HSC (10th–12th Standard)','some_college'=>'Some College (incl. Diploma) but not Graduate','graduate'=>'Graduate: General','graduate_professional'=>'Graduate: Professional / Post Graduate']],
'female_education_21plus' => ['q'=>'Education of highest educated female aged 21+','opts'=>['illiterate'=>'Illiterate','literate_no_formal'=>'Literate but no formal schooling','school_5_9'=>'School — 5th to 9th Standard','ssc_hsc'=>'SSC / HSC (10th–12th Standard)','some_college'=>'Some College (incl. Diploma) but not Graduate','graduate'=>'Graduate: General','graduate_professional'=>'Graduate: Professional / Post Graduate']],
]
]
];
// Fetch point transactions
$transactionsStmt = $panelPdo->prepare("
SELECT * FROM point_transactions
WHERE user_id = ?
ORDER BY created_at DESC
LIMIT 50
");
$transactionsStmt->execute([$member_id]);
$transactions = $transactionsStmt->fetchAll();
// Fetch redemption requests
$redemptionsStmt = $panelPdo->prepare("
SELECT * FROM redemption_requests
WHERE user_id = ?
ORDER BY created_at DESC
");
$redemptionsStmt->execute([$member_id]);
$redemptions = $redemptionsStmt->fetchAll();
// Fetch support tickets
$ticketsStmt = $panelPdo->prepare("
SELECT * FROM support_tickets
WHERE user_id = ?
ORDER BY created_at DESC
LIMIT 10
");
$ticketsStmt->execute([$member_id]);
$tickets = $ticketsStmt->fetchAll();
// Fetch survey data for this member from admin DB
$adminPdo = getDBConnection();
// 1. Selected: in selection_members but NO survey URL assigned for that project
$selectedStmt = $adminPdo->prepare("
SELECT sm.selection_id, sm.sample_status, sm.assigned_at,
ps.selection_name, ps.status as selection_status, ps.incentive_amount, ps.reward_per_complete,
p.project_id, p.project_name, p.status as project_status, p.eloi,
c.company_name as client_name
FROM selection_members sm
JOIN project_selections ps ON sm.selection_id = ps.id
JOIN projects p ON ps.project_id = p.id
JOIN clients c ON ps.client_id = c.id
LEFT JOIN survey_urls su ON su.sent_to_user_id = sm.user_id AND su.project_id = p.project_id
WHERE sm.user_id = ? AND su.id IS NULL
ORDER BY sm.assigned_at DESC
");
$selectedStmt->execute([$member_id]);
$selectedSurveys = $selectedStmt->fetchAll();
// 2. Assigned: URL assigned but NOT yet sent (is_sent=0 and status='assigned')
$assignedStmt = $adminPdo->prepare("
SELECT su.id as url_id, su.unique_identifier, su.status as url_status, su.created_at as url_created,
ps.selection_name, ps.status as selection_status,
p.project_id, p.project_name, p.status as project_status, p.eloi,
c.company_name as client_name
FROM survey_urls su
JOIN projects p ON su.project_id = p.project_id
JOIN clients c ON p.client_id = c.id
LEFT JOIN project_selections ps ON su.assigned_to_selection_id = ps.id
WHERE su.sent_to_user_id = ? AND su.is_sent = 0 AND su.status = 'assigned'
ORDER BY su.created_at DESC
");
$assignedStmt->execute([$member_id]);
$assignedSurveys = $assignedStmt->fetchAll();
// 3. Sent: URL was sent (is_sent=1) but member has NOT interacted (no click/terminal status)
$sentStmt = $adminPdo->prepare("
SELECT su.id as url_id, su.unique_identifier, su.status as url_status, su.sent_at,
ps.selection_name, ps.status as selection_status,
p.project_id, p.project_name, p.status as project_status, p.eloi,
c.company_name as client_name
FROM survey_urls su
JOIN projects p ON su.project_id = p.project_id
JOIN clients c ON p.client_id = c.id
LEFT JOIN project_selections ps ON su.assigned_to_selection_id = ps.id
WHERE su.sent_to_user_id = ? AND su.is_sent = 1
AND su.status NOT IN ('clicked','complete','partial','earlyscreenout','latescreenout','quotafull','timeout')
ORDER BY su.sent_at DESC
");
$sentStmt->execute([$member_id]);
$sentSurveys = $sentStmt->fetchAll();
// 4. Taken: member actually interacted (clicked or has a terminal survey status)
// Differentiate: "From Mail" (is_sent=1) vs "From Portal" (is_sent=0, took via Take Survey button)
$takenStmt = $adminPdo->prepare("
SELECT su.id as url_id, su.unique_identifier, su.status as url_status, su.is_sent,
su.clicked_at, su.completed_at, su.actual_loi_seconds, su.quality_flag, su.quality_notes,
CASE WHEN su.is_sent = 1 THEN 'mail' ELSE 'portal' END as taken_source,
ps.selection_name, ps.status as selection_status, ps.reward_per_complete,
p.project_id, p.project_name, p.status as project_status, p.eloi,
c.company_name as client_name
FROM survey_urls su
JOIN projects p ON su.project_id = p.project_id
JOIN clients c ON p.client_id = c.id
LEFT JOIN project_selections ps ON su.assigned_to_selection_id = ps.id
WHERE su.sent_to_user_id = ? AND su.status IN ('clicked','complete','partial','earlyscreenout','latescreenout','quotafull','timeout')
ORDER BY su.clicked_at DESC
");
$takenStmt->execute([$member_id]);
$takenSurveys = $takenStmt->fetchAll();
// Build taken status breakdown
$takenByStatus = [];
$takenBySource = ['mail' => 0, 'portal' => 0];
foreach ($takenSurveys as $t) {
$s = $t['url_status'];
if (!isset($takenByStatus[$s])) $takenByStatus[$s] = [];
$takenByStatus[$s][] = $t;
$takenBySource[$t['taken_source']]++;
}
} catch (Exception $e) {
error_log("Member view error: " . $e->getMessage());
header('Location: panel.php');
exit;
}
include 'includes/header.php';
?>
-------------------- END OF FILE --------------------
### FILE 24: RR SHOP/members.php
- Type: PHP
- Size: 29.45 KB
- Path: RR SHOP
- Name: members.php
------------------------------------------------------------
0) {
try {
$panelPdo = getPanelDBConnection();
$panelPdo->beginTransaction();
// Delete related records first
$panelPdo->prepare("DELETE FROM point_transactions WHERE user_id = ?")->execute([$member_id]);
$panelPdo->prepare("DELETE FROM user_points WHERE user_id = ?")->execute([$member_id]);
$panelPdo->prepare("DELETE FROM redemption_requests WHERE user_id = ?")->execute([$member_id]);
$panelPdo->prepare("DELETE FROM support_tickets WHERE user_id = ?")->execute([$member_id]);
$panelPdo->prepare("DELETE FROM support_messages WHERE sender_type = 'user' AND sender_id = ?")->execute([$member_id]);
$panelPdo->prepare("DELETE FROM user_profiler WHERE user_id = ?")->execute([$member_id]);
$panelPdo->prepare("DELETE FROM profiler_completion WHERE user_id = ?")->execute([$member_id]);
$panelPdo->prepare("DELETE FROM mobile_verifications WHERE user_id = ?")->execute([$member_id]);
$panelPdo->prepare("DELETE FROM email_verifications WHERE user_id = ?")->execute([$member_id]);
// Delete the user
$panelPdo->prepare("DELETE FROM users WHERE id = ?")->execute([$member_id]);
$panelPdo->commit();
logActivity($_SESSION['admin_id'], 'delete_member', "Deleted member #$member_id", 'member', $member_id);
$success_message = 'Member deleted successfully!';
} catch (Exception $e) {
$panelPdo->rollBack();
error_log("Delete member error: " . $e->getMessage());
$error_message = 'Error deleting member. Please try again.';
}
}
}
// Fetch all panel members with their points
try {
$panelPdo = getPanelDBConnection();
$stmt = $panelPdo->query("
SELECT
u.*,
up.points as current_points,
up.total_earned,
up.total_redeemed,
mv.mobile_number,
mv.is_verified as mobile_verified,
ev.token as verify_token
FROM users u
LEFT JOIN user_points up ON u.id = up.user_id
LEFT JOIN mobile_verifications mv ON u.id = mv.user_id
LEFT JOIN email_verifications ev ON ev.id = (
SELECT id FROM email_verifications WHERE user_id = u.id ORDER BY created_at DESC LIMIT 1
)
ORDER BY u.created_at DESC
");
$members = $stmt->fetchAll();
} catch (Exception $e) {
$members = [];
error_log("Fetch members error: " . $e->getMessage());
}
include 'includes/header.php';
?>
✅
⚠
Panel Members
View and manage all registered survey panel members
';
$payload = [
'personalizations' => [['to' => [['email' => $user['email']]], 'subject' => $subject]],
'from' => ['email' => SHOP_SENDER_EMAIL, 'name' => SHOP_SENDER_NAME],
'content' => [['type' => 'text/html', 'value' => $htmlBody]]
];
$ch = curl_init('https://api.sendgrid.com/v3/mail/send');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . SHOP_SENDGRID_API_KEY,
'Content-Type: application/json'
],
CURLOPT_TIMEOUT => 20,
CURLOPT_SSL_VERIFYPEER => true
]);
$response = curl_exec($ch);
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($statusCode === 202) {
logActivity($_SESSION['admin_id'], 'resend_verification', "Resent verification email to member #$member_id ({$user['email']})");
echo json_encode(['success' => true, 'message' => 'Verification email sent to ' . $user['email']]);
} else {
$decoded = json_decode($response, true);
$msg = $decoded['errors'][0]['message'] ?? $decoded['message'] ?? $response;
error_log("Resend verification failed for {$user['email']}: HTTP $statusCode — $response");
echo json_encode(['success' => false, 'message' => "Failed (HTTP $statusCode): $msg — (raw: " . substr($response, 0, 200) . ")"]);
}
} catch (Exception $e) {
error_log("Resend verification error: " . $e->getMessage());
echo json_encode(['success' => false, 'message' => 'Server error: ' . $e->getMessage()]);
}
exit;
}
try {
$panelPdo = getPanelDBConnection();
$stmt = $panelPdo->query("
SELECT
u.*,
up.points as current_points,
up.total_earned,
up.total_redeemed,
mv.mobile_number,
mv.is_verified as mobile_verified,
ev.token as verify_token
FROM users u
LEFT JOIN user_points up ON u.id = up.user_id
LEFT JOIN mobile_verifications mv ON u.id = mv.user_id
LEFT JOIN email_verifications ev ON ev.id = (
SELECT id FROM email_verifications WHERE user_id = u.id ORDER BY created_at DESC LIMIT 1
)
ORDER BY u.created_at DESC
");
$members = $stmt->fetchAll();
} catch (Exception $e) {
$members = [];
error_log("Fetch members error: " . $e->getMessage());
}
// =========================================================
// REFERRAL COUNTS (for stats cards)
// - Member referrals: rrpanel.member_referral_clicks (panel DB)
// - Affiliate referrals: rrshop.affiliate_signups (shop DB)
// Both filtered by email_verified = 1 (signup completed AND email confirmed)
// =========================================================
$referrals_by_members_verified = 0;
$referrals_by_affiliates_verified = 0;
try {
if (isset($panelPdo) && $panelPdo) {
$stmt = $panelPdo->query("
SELECT COUNT(*) FROM member_referral_clicks
WHERE email_verified = 1
");
$referrals_by_members_verified = (int)$stmt->fetchColumn();
}
} catch (Exception $e) {
error_log("Member referral count error: " . $e->getMessage());
}
try {
$shopPdo = getDBConnection();
$stmt = $shopPdo->query("
SELECT COUNT(*) FROM affiliate_signups
WHERE email_verified = 1
");
$referrals_by_affiliates_verified = (int)$stmt->fetchColumn();
} catch (Exception $e) {
error_log("Affiliate referral count error: " . $e->getMessage());
}
include 'includes/header.php';
?>
✅
⚠
ℹ
Panel Management
Manage all registered survey panel members
Total Members
$m['status'] === 'active')); ?>
Active Members
$m['email_verified'] == 1)); ?>
Email Verified
$m['mobile_verified'] == 1)); ?>
Mobile Verified
$m['onboarding_completed'] == 1)); ?>
Onboarded
!empty($m['isec_class']))); ?>
SEC Classified
Total Points
Referrals by Members (Email Verified)
Referrals by Affiliates (Email Verified)
✉ Send Verification Emails to Unverified Members
Sends a fresh 48-hour verification link to all members who registered in the selected date range and have not yet verified their email.
Send WhatsApp Verification Reminders to Unverified Members
Sends a fresh 48-hour verification link via WhatsApp (AiSensy template account_verification) to all members who registered in the selected date range, have not yet verified their email, and have confirmed their phone is a WhatsApp number. Members without a WhatsApp number, or who unchecked the "this is my WhatsApp" box at signup, are skipped automatically.
This action cannot be undone and will permanently delete:
Member profile and account data
All points and transaction history
Redemption requests
Support tickets and messages
Profiler data
✉
Confirm Send Verification Emails
You are about to send verification emails to all unverified members who registered between:
📅
Each member will receive a fresh verification link valid for 48 hours. Members who are already verified will not receive an email.
0 / …
emails sent
Do not close this window.
Confirm Send WhatsApp Reminders
You are about to send WhatsApp verification reminders to all unverified members with a WhatsApp number who registered between:
📅
Each member will receive a fresh verification link valid for 48 hours via WhatsApp using the account_verification template. Members already verified or without a WhatsApp number will be skipped.
0 / …
WhatsApp messages sent
Do not close this window.
-------------------- END OF FILE --------------------
### FILE 27: RR SHOP/panelbook-generate.php
- Type: PHP
- Size: 35.82 KB
- Path: RR SHOP
- Name: panelbook-generate.php
------------------------------------------------------------
TCPDF not found');
}
require_once $tcpdfPath;
// ═══════════════════════════════════════════════
// 1. COLLECT ALL DATA
// ═══════════════════════════════════════════════
try {
$panelPdo = new PDO(
'mysql:host=localhost;dbname=u752449863_rrpanel;charset=utf8mb4',
'u752449863_rrpaneladmin', 'S@n@h2016',
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]
);
$shopPdo = getDBConnection();
} catch (Exception $e) {
error_log("PanelBook DB: " . $e->getMessage());
header('Location: index.php?error=panelbook_db'); exit;
}
$d = [];
$d['ts'] = date('F d, Y \a\t h:i A T');
$yr = date('Y');
$d['total'] = (int)$panelPdo->query("SELECT COUNT(*) FROM users")->fetchColumn();
$d['active'] = (int)$panelPdo->query("SELECT COUNT(*) FROM users WHERE status='active'")->fetchColumn();
$d['verified'] = (int)$panelPdo->query("SELECT COUNT(*) FROM users WHERE email_verified=1 AND status='active'")->fetchColumn();
$d['gender'] = $panelPdo->query("SELECT gender, COUNT(*) as c FROM users WHERE status='active' AND email_verified=1 GROUP BY gender ORDER BY c DESC")->fetchAll();
$s = $panelPdo->prepare("
SELECT CASE
WHEN (?-YEAR(date_of_birth)) BETWEEN 18 AND 24 THEN '18-24'
WHEN (?-YEAR(date_of_birth)) BETWEEN 25 AND 34 THEN '25-34'
WHEN (?-YEAR(date_of_birth)) BETWEEN 35 AND 44 THEN '35-44'
WHEN (?-YEAR(date_of_birth)) BETWEEN 45 AND 54 THEN '45-54'
WHEN (?-YEAR(date_of_birth)) BETWEEN 55 AND 64 THEN '55-64'
WHEN (?-YEAR(date_of_birth)) >= 65 THEN '65+'
ELSE 'Unknown' END as ag, COUNT(*) as c
FROM users WHERE status='active' AND email_verified=1 AND date_of_birth IS NOT NULL
GROUP BY ag ORDER BY MIN(?-YEAR(date_of_birth))
");
$s->execute(array_fill(0, 7, $yr));
$d['age'] = $s->fetchAll();
// SEC Class (ISEC) distribution
$d['sec'] = [];
try {
$d['sec'] = $panelPdo->query("
SELECT COALESCE(isec_class, 'Unknown') as sec_class, COUNT(*) as c
FROM users WHERE status='active' AND email_verified=1
GROUP BY sec_class
ORDER BY FIELD(COALESCE(isec_class, 'Unknown'), 'A','B','C','D','E','Unknown')
")->fetchAll();
} catch(Exception $e){}
// Geographic: Map PIN codes to Indian states
$PIN_TO_STATE = [
// Delhi
'110'=>'Delhi',
// Haryana
'12'=>'Haryana','13'=>'Haryana',
// Punjab
'14'=>'Punjab','15'=>'Punjab','16'=>'Punjab',
// Chandigarh (override 3-digit)
'160'=>'Chandigarh',
// Himachal Pradesh
'17'=>'Himachal Pradesh',
// Jammu & Kashmir / Ladakh
'18'=>'Jammu & Kashmir','19'=>'Jammu & Kashmir',
'194'=>'Ladakh',
// Uttar Pradesh
'20'=>'Uttar Pradesh','21'=>'Uttar Pradesh','22'=>'Uttar Pradesh','23'=>'Uttar Pradesh',
'24'=>'Uttar Pradesh','25'=>'Uttar Pradesh','27'=>'Uttar Pradesh','28'=>'Uttar Pradesh',
// Uttarakhand
'244'=>'Uttarakhand','245'=>'Uttarakhand','246'=>'Uttarakhand','247'=>'Uttarakhand',
'248'=>'Uttarakhand','249'=>'Uttarakhand','26'=>'Uttarakhand',
// Rajasthan
'30'=>'Rajasthan','31'=>'Rajasthan','32'=>'Rajasthan','33'=>'Rajasthan','34'=>'Rajasthan',
// Gujarat
'36'=>'Gujarat','37'=>'Gujarat','38'=>'Gujarat','39'=>'Gujarat',
// Goa
'403'=>'Goa',
// Maharashtra
'40'=>'Maharashtra','41'=>'Maharashtra','42'=>'Maharashtra','43'=>'Maharashtra',
'44'=>'Maharashtra','45'=>'Maharashtra',
// Madhya Pradesh
'46'=>'Madhya Pradesh','47'=>'Madhya Pradesh','48'=>'Madhya Pradesh',
// Chhattisgarh
'49'=>'Chhattisgarh',
// Telangana
'50'=>'Telangana','51'=>'Telangana',
// Andhra Pradesh
'52'=>'Andhra Pradesh','53'=>'Andhra Pradesh',
// Karnataka
'56'=>'Karnataka','57'=>'Karnataka','58'=>'Karnataka','59'=>'Karnataka',
// Tamil Nadu
'60'=>'Tamil Nadu','61'=>'Tamil Nadu','62'=>'Tamil Nadu','63'=>'Tamil Nadu','64'=>'Tamil Nadu',
// Puducherry
'605'=>'Puducherry',
// Kerala
'67'=>'Kerala','68'=>'Kerala','69'=>'Kerala',
// West Bengal
'70'=>'West Bengal','71'=>'West Bengal','72'=>'West Bengal','73'=>'West Bengal','74'=>'West Bengal',
// Odisha
'75'=>'Odisha','76'=>'Odisha',
// Assam
'78'=>'Assam',
// NE States
'790'=>'Manipur','791'=>'Manipur','795'=>'Manipur',
'793'=>'Meghalaya',
'796'=>'Mizoram',
'797'=>'Nagaland',
'799'=>'Tripura',
'791'=>'Arunachal Pradesh','792'=>'Arunachal Pradesh',
'737'=>'Sikkim',
'77'=>'Assam',
// Bihar
'80'=>'Bihar','81'=>'Bihar','84'=>'Bihar','85'=>'Bihar',
// Jharkhand
'82'=>'Jharkhand','83'=>'Jharkhand',
// Andaman & Nicobar
'744'=>'Andaman & Nicobar',
];
function pinToState($postcode, $map) {
$pc = preg_replace('/\s+/', '', $postcode);
if (strlen($pc) < 2) return 'Unknown';
// Try 3-digit match first (more specific), then 2-digit
$p3 = substr($pc, 0, 3);
$p2 = substr($pc, 0, 2);
if (isset($map[$p3])) return $map[$p3];
if (isset($map[$p2])) return $map[$p2];
return 'Other';
}
// Fetch all postcodes and aggregate by state
$geoStmt = $panelPdo->query("
SELECT postcode FROM users
WHERE status='active' AND email_verified=1 AND postcode IS NOT NULL AND postcode!=''
");
$stateCount = [];
while ($row = $geoStmt->fetch()) {
$state = pinToState($row['postcode'], $PIN_TO_STATE);
$stateCount[$state] = ($stateCount[$state] ?? 0) + 1;
}
arsort($stateCount);
$d['geo'] = [];
foreach ($stateCount as $state => $cnt) {
$d['geo'][] = ['r' => $state, 'c' => $cnt];
}
$d['mob'] = 0;
try { $d['mob'] = (int)$panelPdo->query("SELECT COUNT(*) FROM mobile_verifications WHERE is_verified=1")->fetchColumn(); } catch(Exception $e){}
$d['avgp'] = 0;
try { $d['avgp'] = round((float)$panelPdo->query("SELECT AVG(completion_percentage) FROM profiler_completion")->fetchColumn(),1); } catch(Exception $e){}
$d['pan'] = 0;
try { $d['pan'] = (int)$panelPdo->query("SELECT COUNT(DISTINCT user_id) FROM user_profiler WHERE section='profile' AND question_id='pan_status' AND response='\"approved\"'")->fetchColumn(); } catch(Exception $e){}
$d['mwp'] = 0;
try { $d['mwp'] = (int)$panelPdo->query("SELECT COUNT(DISTINCT user_id) FROM profiler_completion WHERE is_completed=1")->fetchColumn(); } catch(Exception $e){}
$d['proj']=0; $d['sent']=0; $d['comp']=0;
try {
$d['proj'] = (int)$shopPdo->query("SELECT COUNT(*) FROM projects")->fetchColumn();
$d['sent'] = (int)$shopPdo->query("SELECT COUNT(*) FROM survey_urls WHERE is_sent=1")->fetchColumn();
$d['comp'] = (int)$shopPdo->query("SELECT COUNT(*) FROM survey_urls WHERE status='complete'")->fetchColumn();
} catch(Exception $e){}
$SECTIONS = [
'personal_background'=>'Personal Background','household_family'=>'Household & Family',
'shopping_lifestyle'=>'Shopping & Lifestyle','technology_digital'=>'Technology & Digital',
'travel_transportation'=>'Travel & Transportation','health_fitness'=>'Health & Fitness',
'entertainment_media'=>'Entertainment & Media','food_dining'=>'Food & Dining',
'financial_services'=>'Financial Services','communication_payments'=>'Communication & Payments',
'household_classification'=>'Household Classification (ISEC)'
];
$pcomp = [];
foreach ($SECTIONS as $k => $l) {
$s1 = $panelPdo->prepare("SELECT COUNT(DISTINCT user_id) FROM profiler_completion WHERE section=? AND is_completed=1"); $s1->execute([$k]);
$s2 = $panelPdo->prepare("SELECT COUNT(DISTINCT user_id) FROM user_profiler WHERE section=?"); $s2->execute([$k]);
$pcomp[$k] = ['done'=>(int)$s1->fetchColumn(), 'start'=>(int)$s2->fetchColumn()];
}
$pdata = [];
$qs = $panelPdo->query("SELECT DISTINCT section, question_id FROM user_profiler ORDER BY section, question_id")->fetchAll();
foreach ($qs as $q) {
$sec = $q['section']; $qid = $q['question_id'];
$rs = $panelPdo->prepare("SELECT response, COUNT(*) as c FROM user_profiler WHERE section=? AND question_id=? GROUP BY response ORDER BY c DESC");
$rs->execute([$sec, $qid]);
$agg = [];
while ($r = $rs->fetch()) {
$dec = json_decode($r['response'], true);
$c = (int)$r['c'];
if (is_array($dec)) { foreach ($dec as $v) { $v=trim($v); if($v!=='') $agg[$v]=($agg[$v]??0)+$c; } }
else { $v=trim($r['response'],'" '); if($v!=='') $agg[$v]=($agg[$v]??0)+$c; }
}
arsort($agg);
$dist=[]; $i=0; $oth=0;
foreach ($agg as $lb=>$ct) { if($i<12){$dist[]=['l'=>$lb,'c'=>$ct];$i++;}else{$oth+=$ct;} }
if ($oth>0) $dist[]=['l'=>'Others','c'=>$oth];
$uc = $panelPdo->prepare("SELECT COUNT(DISTINCT user_id) FROM user_profiler WHERE section=? AND question_id=?");
$uc->execute([$sec,$qid]);
if (!isset($pdata[$sec])) $pdata[$sec]=[];
$pdata[$sec][$qid] = ['dist'=>$dist, 'n'=>(int)$uc->fetchColumn()];
}
// ═══════════════════════════════════════════════
// 2. FIND TTF FONT + GD CHART FUNCTIONS
// ═══════════════════════════════════════════════
// Find a usable TrueType font — auto-download if needed
function findFont($bold = false) {
$tcpdfFonts = __DIR__ . '/tcpdf/fonts';
$cacheDir = __DIR__ . '/tcpdf/fonts';
$targetFile = $bold
? $cacheDir . '/DejaVuSans-Bold.ttf'
: $cacheDir . '/DejaVuSans.ttf';
// 1. Check if we already have the font
if (file_exists($targetFile) && filesize($targetFile) > 10000) {
return $targetFile;
}
// 2. Search common system paths
$systemPaths = $bold ? [
'/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf',
'/usr/share/fonts/dejavu-sans-fonts/DejaVuSans-Bold.ttf',
'/usr/share/fonts/truetype/freefont/FreeSansBold.ttf',
] : [
'/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf',
'/usr/share/fonts/dejavu-sans-fonts/DejaVuSans.ttf',
'/usr/share/fonts/truetype/freefont/FreeSans.ttf',
];
foreach ($systemPaths as $p) {
if (file_exists($p)) return $p;
}
// 3. Scan TCPDF fonts dir for any .ttf
if (is_dir($tcpdfFonts)) {
$files = glob($tcpdfFonts . '/*.ttf');
if (!empty($files)) return $files[0];
}
// 4. Auto-download DejaVu Sans from GitHub (one-time)
$url = $bold
? 'https://github.com/dejavu-fonts/dejavu-fonts/raw/master/ttf/DejaVuSans-Bold.ttf'
: 'https://github.com/dejavu-fonts/dejavu-fonts/raw/master/ttf/DejaVuSans.ttf';
$ctx = stream_context_create(['http' => [
'timeout' => 15,
'follow_location' => true,
'user_agent' => 'Mozilla/5.0'
]]);
$fontData = @file_get_contents($url, false, $ctx);
if ($fontData && strlen($fontData) > 10000) {
if (is_writable($cacheDir)) {
@file_put_contents($targetFile, $fontData);
if (file_exists($targetFile)) return $targetFile;
}
// If cache dir not writable, write to /tmp
$tmpFont = sys_get_temp_dir() . '/' . ($bold ? 'DejaVuSans-Bold.ttf' : 'DejaVuSans.ttf');
@file_put_contents($tmpFont, $fontData);
if (file_exists($tmpFont)) return $tmpFont;
}
return null; // Will trigger fallback to built-in fonts
}
$FONT = findFont(false);
$FONTB = findFont(true) ?: $FONT;
// Wrapper: draws text using TTF if available, falls back to imagestring
function drawText($img, $size, $x, $y, $text, $color, $font=null) {
if ($font && function_exists('imagettftext')) {
// imagettftext y is baseline, so offset
imagettftext($img, $size, 0, $x, $y + $size + 2, $color, $font, $text);
} else {
// Fallback: use largest built-in font
imagestring($img, 5, $x, $y, $text, $color);
}
}
// Measure text width
function textWidth($size, $text, $font=null) {
if ($font && function_exists('imagettfbbox')) {
$box = imagettfbbox($size, 0, $font, $text);
return abs($box[2] - $box[0]);
}
return strlen($text) * 9; // approx for built-in font 5
}
$CLRS = [
[5,150,105],[13,148,136],[8,145,178],[2,132,199],[79,70,229],
[124,58,237],[192,38,211],[225,29,72],[234,88,12],[217,119,6],
[101,163,13],[22,163,74],[148,163,184],[100,116,139],[71,85,105]
];
function gd_hbar($labels, $values, $clrs, $bcolor=null, $w=2000) {
global $FONT, $FONTB;
$n = count($labels); if(!$n) return null;
$fontSize = 26; // label text size — larger to match pie legend
$valSize = 24; // value text size
$bH = 64; // bar height
$gap = 16; // gap between bars
$lPad = 520; // left padding for labels
$rPad = 320; // right padding for values
$tPad = 20;
$botP = 14;
$h = $tPad + $n*($bH+$gap) + $botP;
$cW = $w-$lPad-$rPad;
$mx = max($values)?:1;
$tot = array_sum($values)?:1;
$img = imagecreatetruecolor($w, $h);
$wh = imagecolorallocate($img, 255, 255, 255);
$tc = imagecolorallocate($img, 35, 45, 60);
$mc = imagecolorallocate($img, 75, 90, 110);
imagefill($img, 0, 0, $wh);
$bc = $bcolor ?: $clrs[0];
$bar = imagecolorallocate($img, $bc[0], $bc[1], $bc[2]);
for($i=0; $i<$n; $i++){
$y = $tPad + $i*($bH+$gap);
$lb = mb_strlen($labels[$i])>32 ? mb_substr($labels[$i],0,30).'..' : $labels[$i];
// Label (right-aligned to left padding)
$tw = textWidth($fontSize, $lb, $FONTB);
drawText($img, $fontSize, max(10, $lPad - $tw - 20), $y + (int)(($bH-$fontSize)/2) - 4, $lb, $tc, $FONTB);
// Bar
$bW = max(5, (int)($values[$i]/$mx*$cW));
imagefilledrectangle($img, $lPad, $y+8, $lPad+$bW, $y+$bH-8, $bar);
// Value
$pct = round($values[$i]/$tot*100,1);
$valText = number_format($values[$i])." ({$pct}%)";
drawText($img, $valSize, $lPad+$bW+14, $y + (int)(($bH-$valSize)/2) - 2, $valText, $mc, $FONT);
}
ob_start(); imagepng($img); $data=ob_get_clean(); imagedestroy($img);
return $data;
}
function gd_vbar($labels, $values, $clrs, $w=1800, $h=600) {
global $FONT, $FONTB;
$n = count($labels); if(!$n) return null;
$fontSize = 26;
$valSize = 24;
$lP=110; $rP=50; $tP=45; $bP=80;
$cW=$w-$lP-$rP; $cH=$h-$tP-$bP; $mx=max($values)?:1;
$bW=max(40, (int)($cW/$n*0.55));
$gap=(int)(($cW-$bW*$n)/($n+1));
$img = imagecreatetruecolor($w, $h);
$wh = imagecolorallocate($img, 255, 255, 255); imagefill($img, 0, 0, $wh);
$tc = imagecolorallocate($img, 35, 45, 60);
$mc = imagecolorallocate($img, 130, 140, 160);
$lc = imagecolorallocate($img, 226, 232, 240);
$c = $clrs[0]; $bar = imagecolorallocate($img, $c[0], $c[1], $c[2]);
// Grid lines
for($g=0; $g<=4; $g++){
$gy = $tP + (int)($cH*(1-$g/4));
imageline($img, $lP, $gy, $w-$rP, $gy, $lc);
$gLabel = number_format((int)($mx*$g/4));
drawText($img, 20, 8, $gy-14, $gLabel, $mc, $FONT);
}
for($i=0; $i<$n; $i++){
$x = $lP + $gap + $i*($bW+$gap);
$bH2 = max(3, (int)($values[$i]/$mx*$cH));
$y = $tP+$cH-$bH2;
imagefilledrectangle($img, $x, $y, $x+$bW, $tP+$cH, $bar);
// Value on top
$vS = number_format($values[$i]);
$tw = textWidth($valSize, $vS, $FONTB);
drawText($img, $valSize, $x+(int)($bW/2)-(int)($tw/2), $y-28, $vS, $tc, $FONTB);
// Label below
$lw = textWidth($fontSize, $labels[$i], $FONT);
drawText($img, $fontSize, $x+(int)($bW/2)-(int)($lw/2), $tP+$cH+12, $labels[$i], $tc, $FONT);
}
ob_start(); imagepng($img); $data=ob_get_clean(); imagedestroy($img);
return $data;
}
function gd_pie($labels, $values, $clrs, $sz=600) {
global $FONT, $FONTB;
$n = count($labels); if(!$n) return null;
$tot = array_sum($values)?:1;
$fontSize = 24;
$legItemH = 58; // legend row height
$legW = 780;
$w = $sz + $legW + 70;
$h = max($sz, $n*$legItemH + 60);
$cx = (int)($sz/2)+30;
$cy = (int)($h/2);
$r = (int)($sz*0.42);
$ir = (int)($r*0.52);
$img = imagecreatetruecolor($w, $h);
$wh = imagecolorallocate($img, 255, 255, 255); imagefill($img, 0, 0, $wh);
$tc = imagecolorallocate($img, 35, 45, 60);
imageantialias($img, true);
// Draw pie slices
$sa = -90; $sc = [];
for($i=0; $i<$n; $i++){
$c = $clrs[$i % count($clrs)];
$sc[$i] = imagecolorallocate($img, $c[0], $c[1], $c[2]);
$ang = ($values[$i]/$tot)*360;
if($ang > 0.5) imagefilledarc($img, $cx, $cy, $r*2, $r*2, (int)round($sa), (int)round($sa+$ang), $sc[$i], IMG_ARC_PIE);
$sa += $ang;
}
// Donut hole
imagefilledellipse($img, $cx, $cy, $ir*2, $ir*2, $wh);
// Legend
$lx = $sz + 60;
for($i=0; $i<$n; $i++){
$ly = 40 + $i*$legItemH;
imagefilledrectangle($img, $lx, $ly, $lx+28, $ly+28, $sc[$i]);
$pct = round($values[$i]/$tot*100, 1);
$legText = $labels[$i]." (".number_format($values[$i])." - {$pct}%)";
drawText($img, $fontSize, $lx+40, $ly-2, $legText, $tc, $FONT);
}
ob_start(); imagepng($img); $data=ob_get_clean(); imagedestroy($img);
return $data;
}
function fmtQ($q) { return ucwords(str_replace('_',' ',$q)); }
// ═══════════════════════════════════════════════
// 3. BUILD PDF — 16:9 WIDESCREEN SLIDE
// ═══════════════════════════════════════════════
define('SLIDE_W', 338.67);
define('SLIDE_H', 190.5);
class RRPDF extends TCPDF {
public $isCover = true;
public function Header() {
if ($this->isCover) return;
$pw = SLIDE_W;
$this->SetY(6);
$this->SetFont('helvetica','B',11);
$this->SetTextColor(6,78,59);
$this->SetXY(18, 6);
$this->Cell(160, 8, 'RELEVANT REFLEX CONSULTING', 0, 0, 'L');
$this->SetFont('helvetica','',10);
$this->SetTextColor(100,116,139);
$this->SetXY($pw-18-120, 6);
$this->Cell(120, 8, 'India Panel Book', 0, 0, 'R');
$this->SetDrawColor(5,150,105);
$this->SetLineWidth(0.7);
$this->Line(18, 16, $pw-18, 16);
}
public function Footer() {
if ($this->isCover) return;
$pw = SLIDE_W;
$this->SetY(-14);
$this->SetDrawColor(180,190,200);
$this->SetLineWidth(0.25);
$this->Line(18, $this->GetY(), $pw-18, $this->GetY());
$this->SetFont('helvetica','',8);
$this->SetTextColor(100,116,139);
$this->SetY(-12);
$hw = ($pw-36)/2;
$this->Cell($hw, 5, 'Confidential — Relevant Reflex Consulting', 0, 0, 'L');
$this->Cell($hw, 5, 'Page '.$this->getAliasNumPage(), 0, 0, 'R');
}
}
$pdf = new RRPDF('L', 'mm', array(SLIDE_W, SLIDE_H), true, 'UTF-8', false);
$pdf->SetCreator('Relevant Reflex Consulting');
$pdf->SetAuthor('Relevant Reflex Consulting');
$pdf->SetTitle('Relevant Reflex Consulting — India Panel Book');
$pdf->SetMargins(22, 22, 22);
$pdf->SetAutoPageBreak(true, 18);
$PW = SLIDE_W - 44;
$PH = SLIDE_H - 40;
$LM = 22;
$CX = SLIDE_W / 2;
function insertChart($pdf, $chartData, $x, $y, $w, $h=0) {
if (!$chartData) return;
$tmp = tempnam(sys_get_temp_dir(),'rrchart_').'.png';
file_put_contents($tmp, $chartData);
$pdf->Image($tmp, $x, $y, $w, $h, 'PNG');
@unlink($tmp);
}
// ══════════════════════════════════════════
// COVER PAGE
// ══════════════════════════════════════════
$pdf->isCover = true;
$pdf->AddPage();
$pdf->Ln(18);
$pdf->SetFillColor(5,150,105);
$pdf->RoundedRect($CX-24, $pdf->GetY(), 48, 30, 6, '1111', 'F');
$pdf->SetFont('helvetica','B',26);
$pdf->SetTextColor(255,255,255);
$pdf->SetXY($CX-24, $pdf->GetY()+3);
$pdf->Cell(48, 24, 'RR', 0, 0, 'C');
$pdf->Ln(38);
$pdf->SetFont('helvetica','B',38);
$pdf->SetTextColor(6,78,59);
$pdf->Cell(0, 15, 'Relevant Reflex Consulting', 0, 1, 'C');
$pdf->Ln(1);
$pdf->SetFont('helvetica','',26);
$pdf->SetTextColor(100,116,139);
$pdf->Cell(0, 11, 'India Panel Book', 0, 1, 'C');
$pdf->Ln(3);
$pdf->SetFillColor(5,150,105);
$pdf->RoundedRect($CX-22, $pdf->GetY(), 44, 1.5, 0.75, '1111', 'F');
$pdf->Ln(8);
$pdf->SetFont('helvetica','',13);
$pdf->SetTextColor(51,65,85);
$pdf->Cell(0, 7, 'Generated on '.$d['ts'], 0, 1, 'C');
$pdf->Ln(10);
$pdf->SetFillColor(248,250,252);
$pdf->SetFont('helvetica','',11);
$pdf->SetTextColor(51,65,85);
$disc = "All the data in this panel book are 100% based on the actual counts of the panel and not added/edited by human. This is a real-time snapshot generated at the date and time mentioned above.";
$pdf->SetX(55);
$pdf->MultiCell(SLIDE_W - 110, 6.5, $disc, 0, 'C', true);
$pdf->Ln(10);
$pdf->SetFont('helvetica','',11);
$pdf->SetTextColor(100,116,139);
$pdf->Cell(0, 5, 'www.relevantreflex.com', 0, 1, 'C');
// ══════════════════════════════════════════
// PANEL OVERVIEW
// ══════════════════════════════════════════
$pdf->isCover = false;
$pdf->AddPage();
$pdf->SetFont('helvetica','B',24);
$pdf->SetTextColor(15,23,42);
$pdf->Cell(0, 11, 'Panel Overview', 0, 1, 'L');
$pdf->SetFont('helvetica','',12);
$pdf->SetTextColor(100,116,139);
$pdf->Cell(0, 6, 'Key quality metrics and panel health indicators — India panel.', 0, 1, 'L');
$pdf->Ln(6);
$cw = $PW/3;
$panPct = $d['verified'] > 0 ? round($d['pan'] / $d['verified'] * 100, 1) : 0;
$mets = [
[[number_format($d['total']),'Total Registered'],[number_format($d['active']),'Active Members'],[number_format($d['verified']),'Email Verified']],
[[number_format($d['mwp']),'Profiler Completed'],[number_format($d['mob']),'Mobile Verified'],[$panPct.'%','PAN Verified ('.number_format($d['pan']).')']],
];
foreach ($mets as $row) {
$y0=$pdf->GetY();
foreach ($row as $ci=>$m) {
if ($m[0] === '' && $m[1] === '') continue;
$x=$LM+$ci*$cw;
$pdf->SetFillColor(248,250,252);
$pdf->RoundedRect($x, $y0, $cw-4, 28, 3, '1111', 'DF');
$pdf->SetFont('helvetica','B',28);
$pdf->SetTextColor(5,150,105);
$pdf->SetXY($x, $y0+1);
$pdf->Cell($cw-4, 15, $m[0], 0, 0, 'C');
$pdf->SetFont('helvetica','',11);
$pdf->SetTextColor(100,116,139);
$pdf->SetXY($x, $y0+16);
$pdf->Cell($cw-4, 9, $m[1], 0, 0, 'C');
}
$pdf->SetY($y0+32);
}
$pdf->Ln(3);
if ($d['proj']>0) {
$pdf->SetFont('helvetica','B',15);
$pdf->SetTextColor(6,78,59);
$pdf->Cell(0, 8, 'Research Activity', 0, 1, 'L');
$pdf->Ln(2);
$pdf->SetFillColor(6,78,59);
$pdf->SetTextColor(255,255,255);
$pdf->SetFont('helvetica','B',11);
$pdf->Cell($cw, 9, 'Total Projects', 1, 0, 'C', true);
$pdf->Cell($cw, 9, 'Invitations Sent', 1, 0, 'C', true);
$pdf->Cell($cw, 9, 'Completed Surveys', 1, 1, 'C', true);
$pdf->SetTextColor(51,65,85);
$pdf->SetFont('helvetica','B',14);
$pdf->Cell($cw, 11, number_format($d['proj']), 1, 0, 'C');
$pdf->Cell($cw, 11, number_format($d['sent']), 1, 0, 'C');
$pdf->Cell($cw, 11, number_format($d['comp']), 1, 1, 'C');
}
// ══════════════════════════════════════════
// PROFILER COMPLETION RATES
// ══════════════════════════════════════════
$pdf->AddPage();
$pdf->SetFont('helvetica','B',24);
$pdf->SetTextColor(15,23,42);
$pdf->Cell(0, 11, 'Profiler Completion Rates', 0, 1, 'L');
$pdf->SetFont('helvetica','',12);
$pdf->SetTextColor(100,116,139);
$popBase = $d['verified'] > 0 ? $d['verified'] : $d['active'];
$pdf->Cell(0, 6, 'Percentage based on '.number_format($popBase).' verified active panel members.', 0, 1, 'L');
$pdf->Ln(5);
$pdf->SetFillColor(6,78,59);
$pdf->SetTextColor(255,255,255);
$pdf->SetFont('helvetica','B',12);
$pdf->Cell($PW*0.46, 10, ' Section', 1, 0, 'L', true);
$pdf->Cell($PW*0.18, 10, 'Completed', 1, 0, 'C', true);
$pdf->Cell($PW*0.18, 10, '% of Panel', 1, 0, 'C', true);
$pdf->Cell($PW*0.18, 10, 'Status', 1, 1, 'C', true);
$pdf->SetFont('helvetica','',11);
$pdf->SetTextColor(51,65,85);
$ev=false;
foreach ($SECTIONS as $k=>$lb) {
$pc=$pcomp[$k];
$pct = $popBase > 0 ? round($pc['done'] / $popBase * 100, 1) : 0;
$status = $pct >= 50 ? 'Strong' : ($pct >= 20 ? 'Growing' : ($pct > 0 ? 'Emerging' : '—'));
if($ev) $pdf->SetFillColor(248,250,252); else $pdf->SetFillColor(255,255,255);
$pdf->Cell($PW*0.46, 8, ' '.$lb, 'LR', 0, 'L', true);
$pdf->Cell($PW*0.18, 8, number_format($pc['done']), 'LR', 0, 'C', true);
$pdf->SetFont('helvetica','B',11);
if ($pct >= 50) $pdf->SetTextColor(5,150,105);
elseif ($pct >= 20) $pdf->SetTextColor(217,119,6);
else $pdf->SetTextColor(100,116,139);
$pdf->Cell($PW*0.18, 8, $pct.'%', 'LR', 0, 'C', true);
$pdf->SetFont('helvetica','',11);
$pdf->SetTextColor(51,65,85);
$pdf->Cell($PW*0.18, 8, $status, 'LR', 1, 'C', true);
$ev=!$ev;
}
$pdf->SetDrawColor(203,213,225);
$pdf->Line($LM, $pdf->GetY(), $LM+$PW, $pdf->GetY());
// ══════════════════════════════════════════
// DEMOGRAPHICS
// ══════════════════════════════════════════
$pdf->AddPage();
$pdf->SetFont('helvetica','B',24);
$pdf->SetTextColor(15,23,42);
$pdf->Cell(0, 11, 'Demographics', 0, 1, 'L');
$pdf->SetFont('helvetica','',12);
$pdf->SetTextColor(100,116,139);
$pdf->Cell(0, 6, 'Distribution of '.number_format($d['verified']).' active, verified panel members across India.', 0, 1, 'L');
$pdf->Ln(4);
if (!empty($d['gender'])) {
$pdf->SetFont('helvetica','B',15);
$pdf->SetTextColor(6,78,59);
$pdf->Cell(0, 8, 'Gender Distribution', 0, 1, 'L');
$pdf->Ln(2);
$lb=array_column($d['gender'],'gender'); $vl=array_map('intval',array_column($d['gender'],'c'));
insertChart($pdf, gd_pie($lb,$vl,$CLRS,560), 30, $pdf->GetY(), 220);
$pdf->Ln(72);
}
if (!empty($d['age'])) {
$pdf->AddPage();
$pdf->SetFont('helvetica','B',24);
$pdf->SetTextColor(15,23,42);
$pdf->Cell(0, 11, 'Age Distribution', 0, 1, 'L');
$pdf->SetFont('helvetica','',12);
$pdf->SetTextColor(100,116,139);
$pdf->Cell(0, 6, 'Age brackets of active verified panel members.', 0, 1, 'L');
$pdf->Ln(3);
$lb=array_column($d['age'],'ag'); $vl=array_map('intval',array_column($d['age'],'c'));
insertChart($pdf, gd_vbar($lb,$vl,$CLRS,1800,560), $LM, $pdf->GetY(), $PW);
$pdf->Ln(70);
}
// ══════════════════════════════════════════
// GEOGRAPHY
// ══════════════════════════════════════════
if (!empty($d['geo'])) {
$pdf->AddPage();
$pdf->SetFont('helvetica','B',24);
$pdf->SetTextColor(15,23,42);
$pdf->Cell(0, 11, 'Geographic Distribution', 0, 1, 'L');
$pdf->SetFont('helvetica','',12);
$pdf->SetTextColor(100,116,139);
$pdf->Cell(0, 6, 'Panel member distribution across '.count($d['geo']).' Indian states & territories.', 0, 1, 'L');
$pdf->Ln(3);
$allLabels = array_column($d['geo'],'r');
$allValues = array_map('intval', array_column($d['geo'],'c'));
$total = count($allLabels);
if ($total <= 15) {
// Fits on one page
$ch = gd_hbar($allLabels, $allValues, $CLRS, [13,148,136], 2000);
$imgH = min($total*9.5+8, $PH-22);
insertChart($pdf, $ch, $LM, $pdf->GetY(), $PW, $imgH);
} else {
// Split across two pages
$half = (int)ceil($total/2);
$lb1 = array_slice($allLabels, 0, $half);
$vl1 = array_slice($allValues, 0, $half);
$ch1 = gd_hbar($lb1, $vl1, $CLRS, [13,148,136], 2000);
$imgH1 = min(count($lb1)*9.5+8, $PH-22);
insertChart($pdf, $ch1, $LM, $pdf->GetY(), $PW, $imgH1);
$lb2 = array_slice($allLabels, $half);
$vl2 = array_slice($allValues, $half);
if (!empty($lb2)) {
$pdf->AddPage();
$pdf->SetFont('helvetica','B',18);
$pdf->SetTextColor(15,23,42);
$pdf->Cell(0, 9, 'Geographic Distribution (continued)', 0, 1, 'L');
$pdf->Ln(3);
$ch2 = gd_hbar($lb2, $vl2, $CLRS, [13,148,136], 2000);
$imgH2 = min(count($lb2)*9.5+8, $PH-22);
insertChart($pdf, $ch2, $LM, $pdf->GetY(), $PW, $imgH2);
}
}
}
// SEC Class (ISEC) Distribution
if (!empty($d['sec'])) {
$pdf->AddPage();
$pdf->SetFont('helvetica','B',24);
$pdf->SetTextColor(15,23,42);
$pdf->Cell(0, 11, 'SEC Classification (ISEC)', 0, 1, 'L');
$pdf->SetFont('helvetica','',12);
$pdf->SetTextColor(100,116,139);
$pdf->Cell(0, 6, 'Socio-Economic Classification based on MRSI ISEC standard (2024).', 0, 1, 'L');
$pdf->Ln(5);
$secLabels = ['A'=>'SEC A — High','B'=>'SEC B — Upper Middle','C'=>'SEC C — Middle','D'=>'SEC D — Lower Middle','E'=>'SEC E — Low','Unknown'=>'Not Classified'];
$secColorsMap = ['A'=>[5,150,105],'B'=>[13,148,136],'C'=>[37,99,235],'D'=>[217,119,6],'E'=>[220,38,38],'Unknown'=>[148,163,184]];
// Table
$pdf->SetFillColor(6,78,59);
$pdf->SetTextColor(255,255,255);
$pdf->SetFont('helvetica','B',12);
$pdf->Cell($PW*0.40, 10, ' SEC Class', 1, 0, 'L', true);
$pdf->Cell($PW*0.20, 10, 'Members', 1, 0, 'C', true);
$pdf->Cell($PW*0.20, 10, '% of Panel', 1, 0, 'C', true);
$pdf->Cell($PW*0.20, 10, 'ISEC Tiers', 1, 1, 'C', true);
$secTierMap = ['A'=>'G1–G6','B'=>'G7–G8','C'=>'G9–G10','D'=>'G11','E'=>'G12','Unknown'=>'—'];
$secTotal = array_sum(array_column($d['sec'], 'c'));
$ev = false;
$pdf->SetFont('helvetica','',11);
foreach ($d['sec'] as $row) {
$cls = $row['sec_class'];
$cnt = (int)$row['c'];
$pct = $secTotal > 0 ? round($cnt / $secTotal * 100, 1) : 0;
$label = $secLabels[$cls] ?? 'SEC '.$cls;
$tiers = $secTierMap[$cls] ?? '—';
if($ev) $pdf->SetFillColor(248,250,252); else $pdf->SetFillColor(255,255,255);
$pdf->SetTextColor(51,65,85);
$pdf->Cell($PW*0.40, 8, ' '.$label, 'LR', 0, 'L', true);
$pdf->Cell($PW*0.20, 8, number_format($cnt), 'LR', 0, 'C', true);
$pdf->SetFont('helvetica','B',11);
$c = $secColorsMap[$cls] ?? [100,116,139];
$pdf->SetTextColor($c[0],$c[1],$c[2]);
$pdf->Cell($PW*0.20, 8, $pct.'%', 'LR', 0, 'C', true);
$pdf->SetFont('helvetica','',11);
$pdf->SetTextColor(100,116,139);
$pdf->Cell($PW*0.20, 8, $tiers, 'LR', 1, 'C', true);
$ev=!$ev;
}
$pdf->SetDrawColor(203,213,225);
$pdf->Line($LM, $pdf->GetY(), $LM+$PW, $pdf->GetY());
// Bar chart
$pdf->Ln(10);
$secLb = []; $secVl = [];
foreach ($d['sec'] as $row) {
$secLb[] = ($secLabels[$row['sec_class']] ?? $row['sec_class']);
$secVl[] = (int)$row['c'];
}
if (function_exists('gd_hbar')) {
$ch = gd_hbar($secLb, $secVl, $CLRS, [5,150,105], 2000);
$imgH = min(count($secLb)*9.5+8, $PH-22);
insertChart($pdf, $ch, $LM, $pdf->GetY(), $PW, $imgH);
}
}
// ══════════════════════════════════════════
// PROFILER SECTIONS
// ══════════════════════════════════════════
$sci=0;
foreach ($SECTIONS as $sk=>$sl) {
if (!isset($pdata[$sk]) || empty($pdata[$sk])) continue;
$qd=$pdata[$sk]; $scl=$CLRS[$sci%count($CLRS)]; $sci++;
$pc=$pcomp[$sk]??['done'=>0,'start'=>0];
$pdf->AddPage();
$pdf->SetFont('helvetica','B',24);
$pdf->SetTextColor(15,23,42);
$pdf->Cell(0, 11, $sl, 0, 1, 'L');
$pdf->SetFont('helvetica','',12);
$pdf->SetTextColor(100,116,139);
$pdf->Cell(0, 6, count($qd).' question'.(count($qd)!=1?'s':'').' — '.number_format($pc['done']).' members completed this section.', 0, 1, 'L');
$pdf->Ln(3);
foreach ($qd as $qid=>$qi) {
$dist=$qi['dist']; $rn=$qi['n'];
if(empty($dist)) continue;
$lb=array_column($dist,'l'); $vl=array_map('intval',array_column($dist,'c'));
$nc=count($dist);
$need = ($nc<=5) ? 78 : ($nc*8.5+24);
if ($pdf->GetY()+$need > (SLIDE_H - 22)) $pdf->AddPage();
$pdf->SetFont('helvetica','B',15);
$pdf->SetTextColor(15,23,42);
$pdf->Cell(0, 8, fmtQ($qid), 0, 1, 'L');
$pdf->SetFont('helvetica','',11);
$pdf->SetTextColor(100,116,139);
$pdf->Cell(0, 5, number_format($rn).' respondents', 0, 1, 'L');
$pdf->Ln(1);
if ($nc<=5 && $nc>=2) {
insertChart($pdf, gd_pie($lb,$vl,$CLRS,520), 35, $pdf->GetY(), 210);
$pdf->Ln(68);
} else {
$ch=gd_hbar($lb,$vl,$CLRS,$scl,2000);
$imgH=min($nc*8.5+5, 120);
insertChart($pdf, $ch, $LM, $pdf->GetY(), $PW, $imgH);
$pdf->Ln($imgH+3);
}
$pdf->Ln(3);
}
}
// ══════════════════════════════════════════
// CONTACT PAGE
// ══════════════════════════════════════════
$pdf->AddPage();
$pdf->Ln(14);
$pdf->SetFillColor(5,150,105);
$pdf->RoundedRect($CX-20, $pdf->GetY(), 40, 26, 5, '1111', 'F');
$pdf->SetFont('helvetica','B',22);
$pdf->SetTextColor(255,255,255);
$pdf->SetXY($CX-20, $pdf->GetY()+3);
$pdf->Cell(40, 20, 'RR', 0, 0, 'C');
$pdf->Ln(34);
$pdf->SetFont('helvetica','B',28);
$pdf->SetTextColor(6,78,59);
$pdf->Cell(0, 11, 'Relevant Reflex Consulting', 0, 1, 'C');
$pdf->Ln(1);
$pdf->SetFont('helvetica','I',14);
$pdf->SetTextColor(100,116,139);
$pdf->Cell(0, 7, "India's Transparent Consumer Access Panel for Market Research", 0, 1, 'C');
$pdf->Ln(14);
$ctc = [
['Email','sridhar.mani@relevantreflex.com'],
['Phone','+91 80565 26579'],
['Web','www.relevantreflex.com'],
];
foreach ($ctc as $c) {
$half = (SLIDE_W)/2;
$pdf->SetFont('helvetica','',12);
$pdf->SetTextColor(100,116,139);
$pdf->Cell($half, 9, $c[0], 0, 0, 'R');
$pdf->SetFont('helvetica','B',13);
$pdf->SetTextColor(15,23,42);
$pdf->Cell($half, 9, ' '.$c[1], 0, 1, 'L');
}
$pdf->Ln(14);
$pdf->SetDrawColor(203,213,225);
$pdf->Line($CX-40, $pdf->GetY(), $CX+40, $pdf->GetY());
$pdf->Ln(6);
$pdf->SetFont('helvetica','',10);
$pdf->SetTextColor(100,116,139);
$pdf->MultiCell(0, 5, 'For panel inquiries, project feasibility, or partnership opportunities, please reach out to our team.', 0, 'C');
$pdf->Ln(4);
$pdf->SetFont('helvetica','',9);
$pdf->Cell(0, 4, 'This document was generated on '.$d['ts'].'.', 0, 1, 'C');
// ═══════════════════════════════════════════════
// 4. SERVE PDF
// ═══════════════════════════════════════════════
logActivity($_SESSION['admin_id'], 'panelbook_download', 'Panel Book PDF generated');
$pdf->Output('RRC_India_Panel_Book_'.date('Y-m-d_His').'.pdf', 'D');
exit;
-------------------- END OF FILE --------------------
### FILE 28: RR SHOP/projects.php
- Type: PHP
- Size: 29.16 KB
- Path: RR SHOP
- Name: projects.php
------------------------------------------------------------
exec("SET time_zone = '+05:30'");
} catch (Exception $e) { error_log("Projects page DB error: ".$e->getMessage()); }
// ===== Snapshot KPIs =====
$kpi = [
'total' => 0, 'live' => 0, 'closed' => 0, 'on_hold' => 0,
'created' => 0, 'targeted' => 0,
'sample_target' => 0, 'completes_total' => 0, 'completes_valid' => 0,
'avg_eloi' => 0, 'avg_actual_loi_min' => 0,
'revenue_inr' => 0, 'active_clients' => 0,
'urls_sent_total' => 0, 'urls_screenout_total' => 0,
];
$forexRates = ['INR' => 1.0, 'USD' => 86.50, 'EUR' => 93.00];
if ($pdo) {
// Live forex (best-effort)
try {
$ctx = stream_context_create(['http' => ['timeout' => 3]]);
$rateJson = @file_get_contents('https://api.exchangerate-api.com/v4/latest/USD', false, $ctx);
if ($rateJson) {
$rateData = json_decode($rateJson, true);
if (!empty($rateData['rates']['INR'])) {
$usdToInr = (float)$rateData['rates']['INR'];
$forexRates['USD'] = $usdToInr;
if (!empty($rateData['rates']['EUR'])) {
$forexRates['EUR'] = $usdToInr / (float)$rateData['rates']['EUR'];
}
}
}
} catch (Exception $e) { /* fallback rates */ }
try {
$stmt = $pdo->query("SELECT
COUNT(*) as total,
SUM(CASE WHEN status='Live' THEN 1 ELSE 0 END) as live_cnt,
SUM(CASE WHEN status='Closed' THEN 1 ELSE 0 END) as closed_cnt,
SUM(CASE WHEN status='On hold' THEN 1 ELSE 0 END) as hold_cnt,
SUM(CASE WHEN status='Created' THEN 1 ELSE 0 END) as created_cnt,
SUM(CASE WHEN status='Targeted' THEN 1 ELSE 0 END) as targeted_cnt,
COALESCE(SUM(sample_size),0) as sample_target,
AVG(eloi) as avg_eloi,
COUNT(DISTINCT client_id) as active_clients
FROM projects");
$row = $stmt->fetch();
if ($row) {
$kpi['total'] = (int)$row['total'];
$kpi['live'] = (int)$row['live_cnt'];
$kpi['closed'] = (int)$row['closed_cnt'];
$kpi['on_hold'] = (int)$row['hold_cnt'];
$kpi['created'] = (int)$row['created_cnt'];
$kpi['targeted'] = (int)$row['targeted_cnt'];
$kpi['sample_target'] = (int)$row['sample_target'];
$kpi['avg_eloi'] = round((float)($row['avg_eloi'] ?? 0), 1);
$kpi['active_clients'] = (int)$row['active_clients'];
}
} catch (Exception $e) { error_log("Projects KPI agg error: ".$e->getMessage()); }
try {
$stmt = $pdo->query("SELECT
SUM(CASE WHEN status='complete' THEN 1 ELSE 0 END) as completes_total,
SUM(CASE WHEN status='complete' AND quality_flag='valid' THEN 1 ELSE 0 END) as completes_valid,
SUM(CASE WHEN status='sent' THEN 1 ELSE 0 END) as sent_cnt,
SUM(CASE WHEN status IN ('earlyscreenout','latescreenout') THEN 1 ELSE 0 END) as so_cnt,
AVG(CASE WHEN status='complete' AND quality_flag='valid' AND actual_loi_seconds IS NOT NULL THEN actual_loi_seconds END) as avg_loi_sec
FROM survey_urls");
$row = $stmt->fetch();
if ($row) {
$kpi['completes_total'] = (int)$row['completes_total'];
$kpi['completes_valid'] = (int)$row['completes_valid'];
$kpi['urls_sent_total'] = (int)$row['sent_cnt'];
$kpi['urls_screenout_total'] = (int)$row['so_cnt'];
$kpi['avg_actual_loi_min'] = round((float)($row['avg_loi_sec'] ?? 0) / 60, 1);
}
} catch (Exception $e) { /* */ }
try {
$stmt = $pdo->query("SELECT currency, SUM(total_amount) as amt FROM invoices GROUP BY currency");
foreach ($stmt->fetchAll() as $r) {
$rate = $forexRates[$r['currency'] ?? 'INR'] ?? 1.0;
$kpi['revenue_inr'] += (float)$r['amt'] * $rate;
}
} catch (Exception $e) { /* */ }
}
// ===== Build WHERE for filtered list =====
$where = ['1=1'];
$params = [];
if ($f_status !== '') {
$where[] = "p.status = ?";
$params[] = $f_status;
}
if ($f_client > 0) {
$where[] = "p.client_id = ?";
$params[] = $f_client;
}
if ($f_search !== '') {
$where[] = "(p.project_id LIKE ? OR p.project_name LIKE ? OR p.client_reference LIKE ?)";
$params[] = '%'.$f_search.'%';
$params[] = '%'.$f_search.'%';
$params[] = '%'.$f_search.'%';
}
switch ($f_sort) {
case 'newest': $orderBy = 'p.created_at DESC'; break;
case 'oldest': $orderBy = 'p.created_at ASC'; break;
case 'sample': $orderBy = 'p.sample_size DESC'; break;
case 'completes': $orderBy = 'urls_valid DESC'; break;
case 'name': $orderBy = 'p.project_name ASC'; break;
case 'client': $orderBy = 'c.company_name ASC, p.created_at DESC'; break;
default: $orderBy = 'p.updated_at DESC'; // 'recent'
}
// ===== Fetch project list =====
$projects = [];
if ($pdo) {
try {
$sql = "
SELECT
p.*,
c.company_name as client_name,
c.country as client_country,
c.currency as client_currency,
(SELECT COUNT(*) FROM project_selections WHERE project_id = p.id) as selections_count,
(SELECT COUNT(*) FROM survey_urls WHERE project_id = p.project_id) as urls_total,
(SELECT COUNT(*) FROM survey_urls WHERE project_id = p.project_id AND status='available') as urls_available,
(SELECT COUNT(*) FROM survey_urls WHERE project_id = p.project_id AND status='sent') as urls_sent,
(SELECT COUNT(*) FROM survey_urls WHERE project_id = p.project_id AND status='complete') as urls_complete,
(SELECT COUNT(*) FROM survey_urls WHERE project_id = p.project_id AND status='complete' AND quality_flag='valid') as urls_valid,
(SELECT COUNT(*) FROM survey_urls WHERE project_id = p.project_id AND status IN ('earlyscreenout','latescreenout')) as urls_screenout,
(SELECT COUNT(*) FROM survey_urls WHERE project_id = p.project_id AND status='quotafull') as urls_quotafull,
(SELECT AVG(actual_loi_seconds) FROM survey_urls WHERE project_id = p.project_id AND status='complete' AND quality_flag='valid' AND actual_loi_seconds IS NOT NULL) as avg_loi_seconds,
(SELECT SUM(total_amount) FROM invoices WHERE project_id = p.id) as invoice_total,
(SELECT currency FROM invoices WHERE project_id = p.id LIMIT 1) as invoice_currency,
(SELECT status FROM invoices WHERE project_id = p.id LIMIT 1) as invoice_status
FROM projects p
LEFT JOIN clients c ON p.client_id = c.id
WHERE ".implode(' AND ', $where)."
ORDER BY $orderBy
";
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
$projects = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (Exception $e) { error_log("Projects fetch error: ".$e->getMessage()); }
}
// Client dropdown
$clients_dd = [];
if ($pdo) {
try {
$stmt = $pdo->query("SELECT id, company_name FROM clients ORDER BY company_name ASC");
$clients_dd = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (Exception $e) { /* */ }
}
// Helpers
if (!function_exists('fmtIST')) {
function fmtIST($dt, $fmt = 'd M Y, h:i A') {
if (!$dt || $dt === '0000-00-00 00:00:00') return '—';
try {
$date = new DateTime($dt, new DateTimeZone('Asia/Kolkata'));
return $date->format($fmt);
} catch (Exception $e) { return date($fmt, strtotime($dt)); }
}
}
if (!function_exists('fmtINR')) { function fmtINR($v) { return '₹' . number_format((float)$v, 2); } }
if (!function_exists('fmtNum')) { function fmtNum($v) { return number_format((int)$v); } }
include 'includes/header.php';
?>
Projects
All projects across all clients · live + closed + draft
-------------------- END OF FILE --------------------
### FILE 30: RR SHOP/README.md
- Type: MD
- Size: 8.86 KB
- Path: RR SHOP
- Name: README.md
------------------------------------------------------------
# Relevant Reflex Panel Management System
A comprehensive, mobile-responsive panel management system built with PHP, MySQL, and modern web technologies. Designed for fast loading on shared hosting environments like Hostinger.
## 🚀 Features
- **Complete Panel Management**: Create, manage, and analyze survey panels
- **User Management**: Role-based access control with admin, manager, and user roles
- **Supply Chain Management**: Track inventory, suppliers, and procurement
- **Demand Analytics**: Market analysis and forecasting tools
- **Financial Management**: Revenue tracking and expense management
- **Support System**: Built-in ticketing and help center
- **Real-time Dashboard**: Live statistics and performance metrics
- **Mobile Responsive**: 100% mobile-friendly design
- **SEO Optimized**: Search engine friendly structure
- **Fast Loading**: Optimized for shared hosting environments
## 📁 File Structure
```
relevant-reflex/
├── index.php # Dashboard home page
├── users.php # User management
├── panel.php # Panel management
├── supply.php # Supply management
├── demand.php # Demand analysis
├── finance.php # Financial management
├── support.php # Support center
├── settings.php # System settings
├── config.php # Database configuration
├── .htaccess # Apache configuration
├── database_schema.sql # Database structure
├── robots.txt # Search engine directives
├── sitemap.xml # Site structure for SEO
├── includes/
│ ├── header.php # Site header
│ ├── footer.php # SEO footer
│ └── navigation.php # Navigation menu
├── assets/
│ ├── css/
│ │ ├── main.css # Main stylesheet
│ │ ├── responsive.css # Mobile responsiveness
│ │ └── dashboard.css # Dashboard styles
│ ├── js/
│ │ ├── main.js # Core JavaScript
│ │ └── dashboard.js # Dashboard functionality
│ └── images/
│ └── (your images here)
└── error-pages/
├── 404.html # Page not found
├── 500.html # Server error
└── maintenance.html # Maintenance mode
```
## 🛠️ Installation Instructions
### Prerequisites
- **Web Hosting**: Shared hosting account (Hostinger, cPanel, etc.)
- **PHP**: Version 7.4 or higher
- **MySQL**: Version 5.7 or higher
- **Apache**: With mod_rewrite enabled
### Step 1: Download and Extract
1. Download all the files provided in the artifacts
2. Create a new folder named `relevant-reflex` on your computer
3. Copy all files into this folder maintaining the directory structure
### Step 2: Database Setup
1. **Login to phpMyAdmin** via your hosting control panel
2. **Create a new database**:
- Database name: `relevant_reflex_db` (or your preferred name)
- Collation: `utf8mb4_unicode_ci`
3. **Import the schema**:
- Click on your database
- Go to "Import" tab
- Choose the `database_schema.sql` file
- Click "Go" to execute
### Step 3: Configuration
1. **Edit config.php**:
```php
define('DB_HOST', 'localhost');
define('DB_USER', 'your_db_username'); // From your hosting panel
define('DB_PASS', 'your_db_password'); // From your hosting panel
define('DB_NAME', 'relevant_reflex_db'); // Your database name
define('SITE_URL', 'https://yourdomain.com'); // Your actual domain
```
2. **Update site settings** in other files if needed
### Step 4: File Upload
1. **Connect via FTP/File Manager**:
- Use your hosting panel's file manager or FTP client
- Navigate to `public_html` directory (or your domain's root)
2. **Upload files**:
- Upload all files maintaining the folder structure
- Ensure permissions are set correctly:
- Files: 644
- Directories: 755
- config.php: 600 (more secure)
### Step 5: Testing
1. **Visit your website**: `https://yourdomain.com`
2. **Default admin login**:
- Username: `admin`
- Email: `admin@relevantreflex.com`
- Password: `admin123`
- **⚠️ Change this immediately after first login!**
3. **Test all features**:
- Dashboard loading
- User management
- Panel creation
- Mobile responsiveness
### Step 6: Security Hardening
1. **Change default admin password**
2. **Update config.php** with strong database credentials
3. **Enable SSL certificate** (usually free with hosting)
4. **Uncomment HTTPS redirect** in .htaccess:
```apache
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
```
## 🎨 Customization
### Changing Colors and Theme
All colors are centralized in `assets/css/main.css` at the top:
```css
:root {
--primary-color: #0066cc; /* Change main brand color */
--primary-hover: #0052a3; /* Hover state */
--success-color: #28a745; /* Success messages */
--warning-color: #ffc107; /* Warnings */
--danger-color: #dc3545; /* Errors */
/* ... more color variables */
}
```
Simply update these values to match your brand colors.
### Adding Your Logo
1. **Replace the RR logo**:
- Update the `.logo` content in `includes/navigation.php`
- Or replace with an image: ``
2. **Update favicon**:
- Add your `favicon.ico` to the root directory
- Update the reference in `includes/header.php`
### Custom Styling
- **Main styles**: `assets/css/main.css`
- **Mobile styles**: `assets/css/responsive.css`
- **Dashboard styles**: `assets/css/dashboard.css`
## 📱 Mobile Optimization
The system is built mobile-first with:
- **Responsive Grid System**: Adapts to all screen sizes
- **Touch-Friendly Interface**: 44px minimum touch targets
- **Optimized Navigation**: Hamburger menu on mobile
- **Fast Loading**: Optimized assets and caching
- **Progressive Enhancement**: Works without JavaScript
## 🔧 Maintenance
### Regular Tasks
1. **Database Backups**: Weekly automated backups recommended
2. **Update Dependencies**: Keep PHP and MySQL updated
3. **Monitor Performance**: Check load times and optimize
4. **Security Updates**: Regular security audits
5. **Content Updates**: Keep information current
### Performance Optimization
1. **Enable Gzip Compression** (included in .htaccess)
2. **Optimize Images**: Use WebP format when possible
3. **Monitor Database**: Run `OPTIMIZE TABLE` monthly
4. **Cache Headers**: Properly configured in .htaccess
5. **CDN Integration**: Consider using a CDN for static assets
## 🔐 Security Features
- **SQL Injection Protection**: PDO prepared statements
- **XSS Prevention**: Input sanitization and CSP headers
- **CSRF Protection**: Session-based token validation
- **Secure Headers**: Comprehensive security headers
- **File Upload Security**: Restricted file types and locations
- **Access Control**: Role-based permissions
## 🆘 Troubleshooting
### Common Issues
**1. Database Connection Error**
```
Solution: Check config.php credentials and database server status
```
**2. Page Not Found (404)**
```
Solution: Verify .htaccess file is uploaded and mod_rewrite is enabled
```
**3. Slow Loading**
```
Solution: Enable compression, check hosting performance, optimize images
```
**4. Mobile Display Issues**
```
Solution: Clear browser cache, check responsive.css is loaded
```
**5. JavaScript Not Working**
```
Solution: Check browser console for errors, verify JS files are accessible
```
### Getting Help
1. **Check Error Logs**: In your hosting control panel
2. **Browser Console**: F12 to check for JavaScript errors
3. **PHP Error Display**: Temporarily enable in config.php for debugging
4. **Hosting Support**: Contact your hosting provider for server issues
## 📞 Support
For technical support and customization services:
- **Email**: support@relevantreflex.com
- **Documentation**: Check inline comments in code files
- **Updates**: Monitor for system updates and security patches
## 📝 License
This system is proprietary software developed for Relevant Reflex. All rights reserved.
## 🚀 Quick Start Checklist
- [ ] Create database in phpMyAdmin
- [ ] Import database_schema.sql
- [ ] Update config.php with database credentials
- [ ] Upload all files to web server
- [ ] Set correct file permissions
- [ ] Test login with admin/admin123
- [ ] Change default admin password
- [ ] Customize colors and branding
- [ ] Enable SSL and HTTPS redirect
- [ ] Test all functionality
- [ ] Setup regular backups
## Version Information
- **Version**: 1.0.0
- **Release Date**: September 2025
- **PHP Compatibility**: 7.4+
- **MySQL Compatibility**: 5.7+
- **Browser Support**: All modern browsers, IE11+
---
**Important**: Always backup your database and files before making changes or updates.
-------------------- END OF FILE --------------------
### FILE 31: RR SHOP/referral-audit.php
- Type: PHP
- Size: 44.13 KB
- Path: RR SHOP
- Name: referral-audit.php
------------------------------------------------------------
prepare("
SELECT id, email, first_name, last_name, gender, date_of_birth, postcode,
status, created_at, email_verified, onboarding_completed
FROM users WHERE email = ? LIMIT 1
");
$stmt->execute([$searchEmail]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$user) $searchError = 'No member found with that email.';
} catch (Exception $e) { $searchError = 'Database error: ' . $e->getMessage(); }
}
// ─── If member found, run the full audit ───
if ($user) {
$userId = (int)$user['id'];
$report = [];
try {
$panelPdo = getPanelDBConnection();
// Tier configuration
$report['tier_cfg'] = null;
try {
$st = $panelPdo->prepare("SELECT * FROM member_referral_tiers WHERE user_id = ?");
$st->execute([$userId]);
$report['tier_cfg'] = $st->fetch(PDO::FETCH_ASSOC) ?: null;
} catch (Exception $e) { /* table may not exist */ }
// Aggregate referral stats
$st = $panelPdo->prepare("
SELECT
COUNT(*) AS total_clicks,
COALESCE(SUM(signup_completed), 0) AS signups,
COALESCE(SUM(email_verified), 0) AS verified,
COUNT(DISTINCT ip_address) AS unique_ips,
COUNT(DISTINCT referee_user_id) AS unique_referees,
COUNT(DISTINCT user_agent) AS unique_uas,
MIN(clicked_at) AS first_click,
MAX(clicked_at) AS last_click
FROM member_referral_clicks WHERE referrer_user_id = ?
");
$st->execute([$userId]);
$report['aggregate'] = $st->fetch(PDO::FETCH_ASSOC) ?: [];
// Engagement (mobile + profilers)
$st = $panelPdo->prepare("
SELECT
COUNT(*) AS verified_referees,
SUM(CASE WHEN EXISTS(SELECT 1 FROM mobile_verifications mv
WHERE mv.user_id = u.id AND mv.is_verified = 1)
THEN 1 ELSE 0 END) AS mobile_verified,
SUM(CASE WHEN (SELECT COUNT(*) FROM profiler_completion
WHERE user_id = u.id AND is_completed = 1) > 0
THEN 1 ELSE 0 END) AS profilers_started
FROM users u
JOIN member_referral_clicks mrc ON mrc.referee_user_id = u.id
WHERE mrc.referrer_user_id = ? AND mrc.email_verified = 1
");
$st->execute([$userId]);
$report['engagement'] = $st->fetch(PDO::FETCH_ASSOC) ?: [];
// Timing analysis
$st = $panelPdo->prepare("
SELECT
COUNT(*) AS verified_count,
MIN(TIMESTAMPDIFF(SECOND, clicked_at, signed_up_at)) AS click_to_signup_min_s,
ROUND(AVG(TIMESTAMPDIFF(SECOND, clicked_at, signed_up_at))) AS click_to_signup_avg_s,
MAX(TIMESTAMPDIFF(SECOND, clicked_at, signed_up_at)) AS click_to_signup_max_s,
MIN(TIMESTAMPDIFF(SECOND, signed_up_at, verified_at)) AS signup_to_verify_min_s,
ROUND(AVG(TIMESTAMPDIFF(SECOND, signed_up_at, verified_at))) AS signup_to_verify_avg_s,
MAX(TIMESTAMPDIFF(SECOND, signed_up_at, verified_at)) AS signup_to_verify_max_s,
SUM(CASE WHEN TIMESTAMPDIFF(SECOND, clicked_at, signed_up_at) < 15 THEN 1 ELSE 0 END) AS fast_signups_under_15s,
SUM(CASE WHEN TIMESTAMPDIFF(SECOND, signed_up_at, verified_at) < 30 THEN 1 ELSE 0 END) AS fast_verifies_under_30s
FROM member_referral_clicks
WHERE referrer_user_id = ? AND email_verified = 1
AND clicked_at IS NOT NULL AND signed_up_at IS NOT NULL AND verified_at IS NOT NULL
");
$st->execute([$userId]);
$report['timing'] = $st->fetch(PDO::FETCH_ASSOC) ?: [];
// Hour-of-day distribution (IST)
$st = $panelPdo->prepare("
SELECT
HOUR(CONVERT_TZ(signed_up_at, '+00:00', '+05:30')) AS ist_hour,
COUNT(*) AS signups
FROM member_referral_clicks
WHERE referrer_user_id = ? AND email_verified = 1
GROUP BY ist_hour ORDER BY ist_hour
");
$st->execute([$userId]);
$report['hours'] = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
// Burst detection (with IP diversity)
$st = $panelPdo->prepare("
SELECT
DATE_FORMAT(CONVERT_TZ(signed_up_at, '+00:00', '+05:30'), '%Y-%m-%d %H:00') AS hour_window_ist,
COUNT(*) AS signups,
COUNT(DISTINCT ip_address) AS unique_ips,
ROUND(COUNT(DISTINCT ip_address) / COUNT(*), 2) AS ip_diversity
FROM member_referral_clicks
WHERE referrer_user_id = ? AND email_verified = 1 AND signed_up_at IS NOT NULL
GROUP BY hour_window_ist HAVING signups >= 3
ORDER BY signups DESC LIMIT 20
");
$st->execute([$userId]);
$report['bursts'] = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
// Top IPs (any IP feeding multiple signups)
$st = $panelPdo->prepare("
SELECT
ip_address,
COUNT(*) AS hits,
SUM(signup_completed) AS signups,
SUM(email_verified) AS verified
FROM member_referral_clicks
WHERE referrer_user_id = ?
GROUP BY ip_address
HAVING hits > 1
ORDER BY hits DESC LIMIT 15
");
$st->execute([$userId]);
$report['top_ips'] = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
// Email pattern duplicates (Gmail dot/plus tricks)
$st = $panelPdo->prepare("
SELECT
REPLACE(SUBSTRING_INDEX(SUBSTRING_INDEX(u.email, '@', 1), '+', 1), '.', '') AS norm_local,
SUBSTRING_INDEX(u.email, '@', -1) AS domain,
COUNT(*) AS dupes,
GROUP_CONCAT(u.email ORDER BY u.email SEPARATOR ' | ') AS emails
FROM member_referral_clicks mrc
JOIN users u ON u.id = mrc.referee_user_id
WHERE mrc.referrer_user_id = ?
GROUP BY norm_local, domain HAVING dupes > 1
ORDER BY dupes DESC LIMIT 10
");
$st->execute([$userId]);
$report['email_dupes'] = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
// Geographic — region (first digit)
$st = $panelPdo->prepare("
SELECT
LEFT(u.postcode, 1) AS region_code,
CASE LEFT(u.postcode, 1)
WHEN '1' THEN 'Delhi / Haryana / Punjab / HP / J&K'
WHEN '2' THEN 'UP / Uttarakhand'
WHEN '3' THEN 'Rajasthan / Gujarat'
WHEN '4' THEN 'Maharashtra / MP / Goa / Chhattisgarh'
WHEN '5' THEN 'Telangana / AP / Karnataka'
WHEN '6' THEN 'Tamil Nadu / Kerala / Puducherry'
WHEN '7' THEN 'West Bengal / Odisha / NE States'
WHEN '8' THEN 'Bihar / Jharkhand'
ELSE 'Other / Unknown'
END AS region,
COUNT(*) AS members
FROM users u
JOIN member_referral_clicks mrc ON mrc.referee_user_id = u.id
WHERE mrc.referrer_user_id = ? AND mrc.email_verified = 1
AND u.postcode IS NOT NULL AND u.postcode != ''
GROUP BY region_code, region ORDER BY members DESC
");
$st->execute([$userId]);
$report['regions'] = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
// Geographic — district (first 3 digits)
$st = $panelPdo->prepare("
SELECT
LEFT(u.postcode, 3) AS prefix,
COUNT(*) AS members,
GROUP_CONCAT(DISTINCT LEFT(u.postcode, 6) ORDER BY u.postcode SEPARATOR ', ') AS sample_pins
FROM users u
JOIN member_referral_clicks mrc ON mrc.referee_user_id = u.id
WHERE mrc.referrer_user_id = ? AND mrc.email_verified = 1
AND u.postcode IS NOT NULL AND u.postcode != ''
GROUP BY prefix ORDER BY members DESC LIMIT 25
");
$st->execute([$userId]);
$report['districts'] = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
// Get referee user IDs for cross-DB survey lookup
$st = $panelPdo->prepare("
SELECT referee_user_id FROM member_referral_clicks
WHERE referrer_user_id = ? AND referee_user_id IS NOT NULL AND email_verified = 1
");
$st->execute([$userId]);
$refIds = array_map('intval', array_column($st->fetchAll(PDO::FETCH_ASSOC), 'referee_user_id'));
// Survey activity (cross-DB → rrshop)
$report['surveys'] = ['referees_with_invites' => 0, 'total_invites' => 0,
'total_completes' => 0, 'unique_with_completes' => 0,
'bad_quality' => 0];
if (!empty($refIds)) {
try {
$shopPdo = getDBConnection();
$placeholders = implode(',', array_fill(0, count($refIds), '?'));
$st = $shopPdo->prepare("
SELECT
COUNT(DISTINCT sent_to_user_id) AS referees_with_invites,
COUNT(*) AS total_invites,
SUM(CASE WHEN status = 'complete' THEN 1 ELSE 0 END) AS total_completes,
COUNT(DISTINCT CASE WHEN status = 'complete' THEN sent_to_user_id END) AS unique_with_completes,
SUM(CASE WHEN quality_flag IN ('speedster','ip_duplicate','client_flagged') THEN 1 ELSE 0 END) AS bad_quality
FROM survey_urls
WHERE sent_to_user_id IN ($placeholders)
");
$st->execute($refIds);
$report['surveys'] = $st->fetch(PDO::FETCH_ASSOC) ?: $report['surveys'];
} catch (Exception $e) { error_log("Audit survey lookup error: " . $e->getMessage()); }
}
// Recent referees detail (top 30)
$st = $panelPdo->prepare("
SELECT
mrc.id, mrc.ip_address, mrc.email AS masked_email, mrc.signup_completed, mrc.email_verified,
mrc.signup_reward_paid, mrc.clicked_at, mrc.signed_up_at, mrc.verified_at,
u.email AS full_email, u.gender, u.postcode, u.status,
EXISTS(SELECT 1 FROM mobile_verifications mv WHERE mv.user_id = u.id AND mv.is_verified = 1) AS mob_v,
(SELECT COUNT(*) FROM profiler_completion WHERE user_id = u.id AND is_completed = 1) AS prof_c
FROM member_referral_clicks mrc
LEFT JOIN users u ON u.id = mrc.referee_user_id
WHERE mrc.referrer_user_id = ?
ORDER BY mrc.clicked_at DESC LIMIT 30
");
$st->execute([$userId]);
$report['recent'] = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
} catch (Exception $e) { error_log("Referral audit error: " . $e->getMessage()); }
// ─── Compute fraud verdict score (0=clean, higher=worse) ───
$score = 0;
$signals = [];
$agg = $report['aggregate'] ?? [];
$eng = $report['engagement'] ?? [];
$tim = $report['timing'] ?? [];
$bursts = $report['bursts'] ?? [];
$verifiedN = max((int)($agg['verified'] ?? 0), 1);
// 1) Aggregate IP diversity
$aggIpRatio = (int)($agg['total_clicks'] ?? 0) > 0
? (int)($agg['unique_ips'] ?? 0) / (int)$agg['total_clicks']
: 0;
if ($aggIpRatio >= 0.5) { $signals[] = ['ok', 'IP diversity (overall): ' . round($aggIpRatio*100) . '% — healthy']; }
elseif ($aggIpRatio >= 0.3) { $score += 1; $signals[] = ['warn', 'IP diversity (overall): ' . round($aggIpRatio*100) . '% — borderline']; }
else { $score += 2; $signals[] = ['bad', 'IP diversity (overall): ' . round($aggIpRatio*100) . '% — too low']; }
// 2) Worst-burst IP diversity
$worstBurst = 1.0;
foreach ($bursts as $b) { if ((float)$b['ip_diversity'] < $worstBurst) $worstBurst = (float)$b['ip_diversity']; }
if (!empty($bursts)) {
if ($worstBurst >= 0.7) { $signals[] = ['ok', 'Worst-burst IP diversity: ' . round($worstBurst*100) . '% — healthy']; }
elseif ($worstBurst >= 0.3){ $score += 1; $signals[] = ['warn', 'Worst-burst IP diversity: ' . round($worstBurst*100) . '%']; }
else { $score += 3; $signals[] = ['bad', 'Worst-burst IP diversity: ' . round($worstBurst*100) . '% — concentrated']; }
}
// 3) Mobile verified rate
$mobRate = $verifiedN > 0 ? (int)($eng['mobile_verified'] ?? 0) / $verifiedN : 0;
if ($mobRate >= 0.30) { $signals[] = ['ok', 'Mobile-verified rate: ' . round($mobRate*100) . '%']; }
elseif ($mobRate >= 0.10) { $score += 1; $signals[] = ['warn', 'Mobile-verified rate: ' . round($mobRate*100) . '% — low']; }
else { $score += 2; $signals[] = ['bad', 'Mobile-verified rate: ' . round($mobRate*100) . '% — almost no engagement']; }
// 4) Profilers started rate
$profRate = $verifiedN > 0 ? (int)($eng['profilers_started'] ?? 0) / $verifiedN : 0;
if ($profRate >= 0.30) { $signals[] = ['ok', 'Profiler-started rate: ' . round($profRate*100) . '%']; }
elseif ($profRate >= 0.10) { $score += 1; $signals[] = ['warn', 'Profiler-started rate: ' . round($profRate*100) . '% — low']; }
else { $score += 2; $signals[] = ['bad', 'Profiler-started rate: ' . round($profRate*100) . '% — empty profiles']; }
// 5) Fast verifies (<30s) rate
$vCount = (int)($tim['verified_count'] ?? 0);
$fast30 = (int)($tim['fast_verifies_under_30s'] ?? 0);
$fastRate = $vCount > 0 ? $fast30 / $vCount : 0;
if ($fastRate < 0.20) { $signals[] = ['ok', 'Fast-verify rate (<30s): ' . round($fastRate*100) . '%']; }
elseif ($fastRate < 0.50) { $signals[] = ['info', 'Fast-verify rate (<30s): ' . round($fastRate*100) . '% — note']; }
else { $score += 1; $signals[] = ['warn', 'Fast-verify rate (<30s): ' . round($fastRate*100) . '% — high (event style or automation)']; }
// 6) Scripted-fast signups (<15s)
$fast15 = (int)($tim['fast_signups_under_15s'] ?? 0);
if ($fast15 == 0) { $signals[] = ['ok', 'Scripted-fast signups (<15s): 0']; }
elseif ($fast15 < 5) { $score += 1; $signals[] = ['warn', "Scripted-fast signups (<15s): $fast15"]; }
else { $score += 3; $signals[] = ['bad', "Scripted-fast signups (<15s): $fast15 — likely automation"]; }
// 7) Email pattern duplicates
$emailDupCount = count($report['email_dupes'] ?? []);
if ($emailDupCount == 0) { $signals[] = ['ok', 'Email pattern duplicates: none']; }
elseif ($emailDupCount <= 3) { $score += 1; $signals[] = ['warn', "Email pattern duplicates: $emailDupCount (possible Gmail tricks)"]; }
else { $score += 3; $signals[] = ['bad', "Email pattern duplicates: $emailDupCount — same person, multiple aliases"]; }
// 8) Surveys completed by referees
$sv = $report['surveys'] ?? [];
$totalRefs = max(count($refIds), 1);
$surveyEngagement = (int)($sv['unique_with_completes'] ?? 0) / $totalRefs;
if ($verifiedN >= 20) { // only meaningful at scale
if ($surveyEngagement >= 0.10) { $signals[] = ['ok', 'Survey engagement: ' . round($surveyEngagement*100) . '% of referees took surveys']; }
elseif ($surveyEngagement >= 0.03) { $score += 1; $signals[] = ['warn', 'Survey engagement: ' . round($surveyEngagement*100) . '% — low']; }
else { $score += 2; $signals[] = ['bad', 'Survey engagement: ' . round($surveyEngagement*100) . '% — referees never take surveys']; }
}
// Verdict from total score
if ($score <= 3) { $verdictLevel = 'clean'; $verdictText = 'CLEAN'; $verdictColor = '#059669'; $verdictBg = '#d1fae5'; $verdictIcon = '✓'; }
elseif ($score <= 7) { $verdictLevel = 'review'; $verdictText = 'REQUIRES REVIEW'; $verdictColor = '#d97706'; $verdictBg = '#fef3c7'; $verdictIcon = '⚠'; }
else { $verdictLevel = 'risky'; $verdictText = 'HIGH FRAUD RISK'; $verdictColor = '#dc2626'; $verdictBg = '#fee2e2'; $verdictIcon = '✗'; }
$report['verdict'] = [
'score' => $score, 'level' => $verdictLevel, 'text' => $verdictText,
'color' => $verdictColor, 'bg' => $verdictBg, 'icon' => $verdictIcon, 'signals' => $signals
];
}
// Helpers
if (!function_exists('fmtNum')) { function fmtNum($v) { return number_format((int)$v); } }
if (!function_exists('fmtIST')) {
function fmtIST($dt, $fmt = 'd M Y h:i A') {
if (!$dt || $dt === '0000-00-00 00:00:00') return '—';
try { $d = new DateTime($dt, new DateTimeZone('Asia/Kolkata')); return $d->format($fmt); }
catch (Exception $e) { return date($fmt, strtotime($dt)); }
}
}
function fmtDuration($s) {
if ($s === null || $s === '') return '—';
$s = (int)$s;
if ($s < 60) return $s . 's';
if ($s < 3600) return floor($s/60) . 'm ' . ($s%60) . 's';
if ($s < 86400) return floor($s/3600) . 'h ' . floor(($s%3600)/60) . 'm';
return floor($s/86400) . 'd ' . floor(($s%86400)/3600) . 'h';
}
include 'includes/header.php';
?>
Referral Audit Report
Generated IST · Relevant Reflex Admin · By
Referral Audit
Comprehensive fraud/legitimacy assessment for any referring panel member. Enter their email to run a full multi-signal check across IPs, timing, engagement, geographic spread, and survey activity.
IP Repeats — addresses appearing in multiple referrals
IP Address
Total Hits
Signups
Verified
A small number of repeats with 2-3 hits each is normal (people use multiple devices). Many IPs each producing 5+ verified signups is a fraud signal.
IP Repeats
No IP appears in multiple referrals — every signup came from a distinct IP. ✓
Email Pattern Duplicates — Gmail dot/plus aliases pointing to same inbox
Normalized Local
Domain
Variants
Emails
Geographic Spread — by pincode region
xxxxx —
· %
Top Districts
Pincode Prefix
Members
Sample Pincodes
Recent Referees — most recent 30 click events
Email
IP
C→S
S→V
Verified
Mob
Prof
Postcode
Clicked (IST)
✓' : '—'; ?>
✓' : '—'; ?>
0 ? '' . (int)$r['prof_c'] . '' : '—'; ?>
Report generated IST · Relevant Reflex Admin Audit · For internal review only
How this audit works: Enter the email of any panel member who has been referring others. The tool runs 8 independent checks across IP diversity, signup/verify timing patterns, post-signup engagement, geographic concentration, email-pattern tricks, burst-window analysis, and survey activity in real projects — then scores each signal and gives a verdict (CLEAN / REQUIRES REVIEW / HIGH FRAUD RISK). Click "Save as PDF" once results are shown to keep a record for fraud-related disputes or member suspensions.