&rand= * * Headless Chrome access (no session, called from headlessChrome_pdf.php): * ?rid=&rand=&ptoken= * The ptoken is a short-lived file written by the generator. This lets Chrome * render the page without a PHP session cookie. */ require_once __DIR__ . '/../../../config/database.php'; require_once __DIR__ . '/../../../lib/auth.php'; require_once __DIR__ . '/../../../lib/print_auth.php'; require_once __DIR__ . '/../../../lib/soil_calculations.php'; require_once __DIR__ . '/../../../vendor/autoload.php'; if (session_status() === PHP_SESSION_NONE) { session_start(); } $recordId = (int) ($_GET['rid'] ?? 0); $randId = trim( $_GET['rand'] ?? ''); $chromeAccess = authenticatePrintPage($recordId, $randId); $pdo = getDBConnection(); $userId = $chromeAccess ? null : getCurrentUserId(); $row = null; $spec = []; $savedComments = []; $today = date('jS F Y'); if ($recordId > 0 && $randId !== '') { $stmt = $pdo->prepare('SELECT * FROM soil_records WHERE id = ? AND rand = ? LIMIT 1'); $stmt->execute([$recordId, $randId]); $row = $stmt->fetch(PDO::FETCH_ASSOC); if ($row) { // Load spec ranges for this soil type if (!empty($row['soil_type'])) { $stmtSpec = $pdo->prepare('SELECT * FROM soil_specifications WHERE soil_type = ? LIMIT 1'); $stmtSpec->execute([$row['soil_type']]); $spec = $stmtSpec->fetch(PDO::FETCH_ASSOC) ?: []; } // Load saved report comments — use modx_user_id when available, else any comment for this record if ($userId !== null) { $stmtRpt = $pdo->prepare( 'SELECT comment FROM reports WHERE record_id = ? AND modx_user_id = ? ORDER BY id DESC LIMIT 1' ); $stmtRpt->execute([$recordId, $userId]); } else { $stmtRpt = $pdo->prepare( 'SELECT comment FROM reports WHERE record_id = ? ORDER BY id DESC LIMIT 1' ); $stmtRpt->execute([$recordId]); } $savedRow = $stmtRpt->fetchColumn(); if ($savedRow) { $decoded = json_decode($savedRow, true); if (is_array($decoded)) { $savedComments = $decoded; } } } } // ── Five-year plan ──────────────────────────────────────────────────────────── $planElements = [ ['Calcium', 'BS_ca_ppm', 'ca_ppm_min', 'ca_ppm_max', 'kg/ha'], ['Magnesium', 'BS_mg_ppm', 'mg_ppm_min', 'mg_ppm_max', 'kg/ha'], ['Potassium', 'BS_k_ppm', 'k_ppm_min', 'k_ppm_max', 'kg/ha'], ['Sodium', 'BS_na_ppm', 'na_ppm_min', 'na_ppm_max', 'kg/ha'], ['Phosphate', 'p_colwell', '', '', 'kg/ha'], ['Sulfur', 's_morgan', '', '', 'kg/ha'], ['Boron', 'b_cacl2', '', '', 'kg/ha'], ['Manganese', 'mn_dtpa', '', '', 'kg/ha'], ['Zinc', 'zn_dtpa', '', '', 'kg/ha'], ['Copper', 'cu_dtpa', '', '', 'kg/ha'], ]; $acHa = 2.4710559990832394739; function calcDeficitPdf(array $row, array $spec, string $col, string $minCol, string $maxCol): float { global $acHa; $value = (float)($row[$col] ?? 0); $maxVal = $maxCol !== '' ? (float)($row[$maxCol] ?? 0) : (float)($spec[$col] ?? 0); $deficit = ($maxVal - $value) * $acHa; return $deficit > 0 ? round($deficit, 2) : 0.0; } $h = fn($v) => htmlspecialchars((string)($v ?? ''), ENT_QUOTES, 'UTF-8'); // Parse and render markdown text from AI-generated report sections function formatReportText(string $text): string { if (trim($text) === '') { return '

No content saved.

'; } $parsedown = new Parsedown(); $parsedown->setSafeMode(true); // strip any raw HTML in the text return $parsedown->text($text); } ?> Soil Analysis Report | Crop Monitor
Record not found or access denied.
Crop Monitor
Soil Analysis Report
CLIENT: SAMPLE ID:
ADDRESS: DATE SAMPLED:
LAB NUMBER:
CROP:
SOIL TYPE:

Total kilograms per hectare of each element needed to balance soil
Ideal Soil Balancing Program — 5-Year Plan (kg/ha per year)
0 ? round($total / 5, 2) : 0; ?>
Element Total Deficit Year
0 ? $total . ' ' . $unit : '✓ Adequate' ?> 0 ? $perYear . ' ' . $unit : '—' ?>
Overview
AI Soil Interpretation
Microbial Program

Any recommendations provided by Crop Monitor are advice only. We are not paid consultants and accept no responsibility for any of our suggestions. No control can be exercised over storage, handling, mixing, application, or use, or over weather, plant or soil conditions before, during or after application — all of which may affect the performance of our program. No responsibility for, or liability for, any failure in performance, losses, damage or injuries consequential or otherwise arising from such storage, mixing, application or use will be accepted under any circumstances whatsoever. The buyer assumes all responsibility for the use of any of our products.