[ 'print_page' => '/dashboard/crop-analysis/soil-test-data/soil-analysis.php?rid={rid}&rand={rand}&cid={cid}&stid={stid}&print=1', 'verify_table' => 'soil_records', 'filename' => 'soil-analysis', 'label' => 'Soil Analysis', ], 'soil-report' => [ 'print_page' => '/dashboard/crop-analysis/soil-test-data/soil-report-pdf.php?rid={rid}&rand={rand}', 'verify_table' => 'soil_records', 'filename' => 'soil-report', 'label' => 'Soil Report', ], // Combined: analysis + AI report in one PDF 'soil' => [ 'print_page' => '/dashboard/crop-analysis/soil-test-data/soil-print-combined.php?rid={rid}&rand={rand}&cid={cid}&stid={stid}', 'verify_table' => 'soil_records', 'filename' => 'soil-analysis-report', 'label' => 'Soil Analysis & Report', ], // Plant 'plant-analysis' => [ 'print_page' => '/dashboard/crop-analysis/plant-test-data/plant-analysis.php?rid={rid}&rand={rand}&cid={cid}', 'verify_table' => 'plant_records', 'filename' => 'plant-analysis', 'label' => 'Plant Analysis', ], 'plant-report' => [ 'print_page' => '/dashboard/crop-analysis/plant-test-data/plant-report-pdf.php?rid={rid}&rand={rand}', 'verify_table' => 'plant_records', 'filename' => 'plant-report', 'label' => 'Plant Report', ], 'plant' => [ 'print_page' => '/dashboard/crop-analysis/plant-test-data/plant-print-combined.php?rid={rid}&rand={rand}&cid={cid}', 'verify_table' => 'plant_records', 'filename' => 'plant-analysis-report', 'label' => 'Plant Analysis & Report', ], // Water 'water-analysis' => [ 'print_page' => '/dashboard/crop-analysis/water-test-data/water-analysis-pdf.php?rid={rid}&rand={rand}&cid={cid}', 'verify_table' => 'water_records', 'filename' => 'water-analysis', 'label' => 'Water Analysis', ], 'water-report' => [ 'print_page' => '/dashboard/crop-analysis/water-test-data/water-report-pdf.php?rid={rid}&rand={rand}', 'verify_table' => 'water_records', 'filename' => 'water-report', 'label' => 'Water Report', ], 'water' => [ 'print_page' => '/dashboard/crop-analysis/water-test-data/water-print-combined.php?rid={rid}&rand={rand}&cid={cid}', 'verify_table' => 'water_records', 'filename' => 'water-analysis-report', 'label' => 'Water Analysis & Report', ], // Animal 'animal-analysis' => [ 'print_page' => '/dashboard/crop-analysis/animal-dietary-balance/animal-dietary-balance.php?rid={rid}&rand={rand}&cid={cid}', 'verify_table' => 'animal_records', 'filename' => 'animal-analysis', 'label' => 'Animal Dietary Analysis', ], 'animal-report' => [ 'print_page' => '/dashboard/crop-analysis/animal-dietary-balance/animal-report-pdf.php?rid={rid}&rand={rand}', 'verify_table' => 'animal_records', 'filename' => 'animal-report', 'label' => 'Animal Dietary Report', ], 'animal' => [ 'print_page' => '/dashboard/crop-analysis/animal-dietary-balance/animal-print-combined.php?rid={rid}&rand={rand}&cid={cid}', 'verify_table' => 'animal_records', 'filename' => 'animal-dietary-report', 'label' => 'Animal Dietary Analysis & Report', ], ]; if (!isset($reportTypes[$type])) { http_response_code(400); $valid = implode(', ', array_keys($reportTypes)); die("Unknown report type \"" . htmlspecialchars($type, ENT_QUOTES, 'UTF-8') . "\". Valid types: $valid"); } $config = $reportTypes[$type]; if ($recordId <= 0 || $randId === '') { http_response_code(400); die('Missing required parameters: rid, rand'); } // ── Verify record exists via its table ─────────────────────────────────────── $pdo = getDBConnection(); $table = $config['verify_table']; // Allowlist the table name against known tables to prevent SQL injection $allowedTables = ['soil_records', 'plant_records', 'water_records', 'animal_records']; if (!in_array($table, $allowedTables, true)) { http_response_code(500); die('Invalid verify_table in report type config.'); } $stmt = $pdo->prepare("SELECT lab_no FROM `{$table}` WHERE id = ? AND rand = ? LIMIT 1"); $stmt->execute([$recordId, $randId]); $record = $stmt->fetch(); if (!$record) { http_response_code(404); die('Record not found or access denied.'); } // ── Write one-time print token ──────────────────────────────────────────────── $token = bin2hex(random_bytes(16)); $expires = time() + 120; $tokenDir = __DIR__ . '/tokens'; if (!is_dir($tokenDir)) { mkdir($tokenDir, 0750, true); } file_put_contents($tokenDir . '/' . $token . '.tmp', json_encode([ 'rid' => $recordId, 'rand' => $randId, 'expires' => $expires, 'type' => $type, ])); // ── Build print page URL ────────────────────────────────────────────────────── $scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http'; $host = $_SERVER['HTTP_HOST'] ?? 'localhost'; $baseUrl = $scheme . '://' . $host; $printPath = str_replace( ['{rid}', '{rand}', '{cid}', '{stid}'], [$recordId, urlencode($randId), $clientId, urlencode($stid)], $config['print_page'] ); $printUrl = $baseUrl . $printPath . '&ptoken=' . urlencode($token); // ── Output directory + filename ─────────────────────────────────────────────── $outputDir = __DIR__; $today = date('Y-m-d'); $labNo = preg_replace('/[^A-Za-z0-9\-_]/', '_', $record['lab_no'] ?? $recordId); $filename = $config['filename'] . '-' . $labNo . '-' . $today; // ── Headless Chrome ─────────────────────────────────────────────────────────── $chromeBinary = '/usr/bin/google-chrome'; foreach (['/usr/bin/google-chrome', '/usr/bin/chromium-browser', '/usr/bin/chromium'] as $bin) { if (file_exists($bin)) { $chromeBinary = $bin; break; } } $arguments = [ '--headless' => '', '--disable-gpu' => '', '--hide-scrollbars' => '', '--enable-viewport' => '', '--timeout=' => '6000', '--disable-web-security' => '', '--run-all-compositor-stages-before-draw' => '', '--virtual-time-budget' => '40000', ]; try { $chrome = new HeadlessChrome(); $chrome->disablePDFHeader(); $chrome->setUrl($printUrl); $chrome->setBinaryPath($chromeBinary); $chrome->setOutputDirectory($outputDir); $chrome->toPDF($filename . '.pdf'); $pdfPath = $chrome->getFilePath(); } catch (Exception $e) { error_log('HeadlessChrome PDF error [' . $type . ']: ' . $e->getMessage()); @unlink($tokenDir . '/' . $token . '.tmp'); http_response_code(500); die('PDF generation failed: ' . htmlspecialchars($e->getMessage(), ENT_QUOTES, 'UTF-8')); } @unlink($tokenDir . '/' . $token . '.tmp'); // ── Stream PDF ──────────────────────────────────────────────────────────────── if (!file_exists($pdfPath)) { http_response_code(500); die('PDF file was not created. Ensure Chrome is installed at: ' . $chromeBinary); } chmod($pdfPath, 0644); header('Content-Type: application/pdf'); header('Content-Disposition: attachment; filename="' . $filename . '.pdf"'); header('Content-Length: ' . filesize($pdfPath)); header('Expires: 0'); header('Cache-Control: must-revalidate'); header('Pragma: public'); readfile($pdfPath); @unlink($pdfPath); exit;