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(); // 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' ]; $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, $sa, $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); } } } // ══════════════════════════════════════════ // 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;