|
|
@@ -0,0 +1,280 @@
|
|
|
+<?php
|
|
|
+/**
|
|
|
+ * plant-report-pdf.php
|
|
|
+ *
|
|
|
+ * Print / PDF-export version of a completed plant analysis report.
|
|
|
+ * Displays the saved AI-generated sections from the reports table alongside
|
|
|
+ * the element requirement cards and five-year balancing plan.
|
|
|
+ *
|
|
|
+ * Normal access (logged-in user):
|
|
|
+ * ?rid=<id>&rand=<token>
|
|
|
+ *
|
|
|
+ * Headless Chrome access (no session, called from headlessChrome_pdf.php):
|
|
|
+ * ?rid=<id>&rand=<token>&ptoken=<one-time-file-token>
|
|
|
+ * 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__ . '/../../../vendor/autoload.php';
|
|
|
+
|
|
|
+if (session_status() === PHP_SESSION_NONE) {
|
|
|
+ session_start();
|
|
|
+}
|
|
|
+
|
|
|
+$recordId = (int) ($_GET['rid'] ?? 0);
|
|
|
+$randId = trim( $_GET['rand'] ?? '');
|
|
|
+$clientId = (int) ($_GET['cid'] ?? 0);
|
|
|
+
|
|
|
+if (!$recordId || $randId === '') {
|
|
|
+ http_response_code(400);
|
|
|
+ die('Invalid request parameters');
|
|
|
+}
|
|
|
+
|
|
|
+try {
|
|
|
+ $pdo = getDBConnection();
|
|
|
+ $userId = getCurrentUserId();
|
|
|
+
|
|
|
+ $stmt = $pdo->prepare('SELECT * FROM plant_records WHERE id = ? AND rand = ?');
|
|
|
+ $stmt->execute([$recordId, $randId]);
|
|
|
+ $row = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
+
|
|
|
+ if (!$row) {
|
|
|
+ http_response_code(404);
|
|
|
+ die('Plant record not found');
|
|
|
+ }
|
|
|
+
|
|
|
+ // Load spec ranges
|
|
|
+ $specs = [];
|
|
|
+ if (!empty($row['crop_type'])) {
|
|
|
+ $stmtSpec = $pdo->prepare('SELECT * FROM plant_specifications WHERE plant_type = ? LIMIT 1');
|
|
|
+ $stmtSpec->execute([$row['crop_type']]);
|
|
|
+ $specs = $stmtSpec->fetch(PDO::FETCH_ASSOC) ?: [];
|
|
|
+ }
|
|
|
+
|
|
|
+ // Load saved report comments
|
|
|
+ $savedComments = [
|
|
|
+ 'general_details' => '',
|
|
|
+ 'ai_interpretation' => '',
|
|
|
+ 'recommended_details' => '',
|
|
|
+ 'foliar_details' => '',
|
|
|
+ ];
|
|
|
+ $stmtRpt = $pdo->prepare(
|
|
|
+ 'SELECT comment FROM reports WHERE record_id = ? AND modx_user_id = ? ORDER BY id DESC LIMIT 1'
|
|
|
+ );
|
|
|
+ $stmtRpt->execute([$recordId, $userId]);
|
|
|
+ $savedRow = $stmtRpt->fetchColumn();
|
|
|
+ if ($savedRow) {
|
|
|
+ $decoded = json_decode($savedRow, true);
|
|
|
+ if (is_array($decoded)) {
|
|
|
+ $savedComments = array_merge($savedComments, $decoded);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+} catch (PDOException $e) {
|
|
|
+ error_log('DB error in plant-report.php: ' . $e->getMessage());
|
|
|
+ die('Database error occurred');
|
|
|
+}
|
|
|
+
|
|
|
+$h = fn($v) => htmlspecialchars((string)($v ?? ''), ENT_QUOTES, 'UTF-8');
|
|
|
+$today = date('jS F Y');
|
|
|
+$pageTitle = 'Plant Report' . (!empty($row['client_name']) ? ' — ' . $row['client_name'] : '');
|
|
|
+$siteName = 'Crop Monitor';
|
|
|
+
|
|
|
+// Parse and render markdown text from AI-generated report sections
|
|
|
+function formatReportText(string $text): string
|
|
|
+{
|
|
|
+ if (trim($text) === '') {
|
|
|
+ return '<p class="text-muted fst-italic">No content saved.</p>';
|
|
|
+ }
|
|
|
+ $parsedown = new Parsedown();
|
|
|
+ $parsedown->setSafeMode(true);
|
|
|
+ return $parsedown->text($text);
|
|
|
+}
|
|
|
+
|
|
|
+?>
|
|
|
+<!doctype html>
|
|
|
+<html lang="en">
|
|
|
+<head>
|
|
|
+ <meta charset="UTF-8">
|
|
|
+ <meta name="viewport" content="width=device-width, initial-scale=1">
|
|
|
+ <title>Soil Analysis Report | Crop Monitor</title>
|
|
|
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
|
|
|
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.9.0/css/all.css" rel="stylesheet" crossorigin="anonymous">
|
|
|
+ <link href="/client-assets/css/dashboard.css" rel="stylesheet">
|
|
|
+ <link rel="stylesheet" href="/client-assets/css/graphPrint.css" media="print">
|
|
|
+ <style>
|
|
|
+ @media print {
|
|
|
+ .d-print-none { display: none !important; }
|
|
|
+ .page-break { page-break-before: always; }
|
|
|
+ body { font-size: 11px; }
|
|
|
+ .report-section p { font-size: 11px; }
|
|
|
+ }
|
|
|
+ .report-section p { margin-bottom: 0.6rem; line-height: 1.6; }
|
|
|
+ .report-section h1,
|
|
|
+ .report-section h2,
|
|
|
+ .report-section h3,
|
|
|
+ .report-section h4 { font-size: 1rem; font-weight: 600; margin: 1rem 0 0.4rem; }
|
|
|
+ .report-section ul,
|
|
|
+ .report-section ol { padding-left: 1.4rem; margin-bottom: 0.6rem; }
|
|
|
+ .report-section li { margin-bottom: 0.25rem; line-height: 1.5; }
|
|
|
+ .report-section table {
|
|
|
+ width: 100%;
|
|
|
+ border-collapse: collapse;
|
|
|
+ margin-bottom: 0.8rem;
|
|
|
+ font-size: 0.85rem;
|
|
|
+ }
|
|
|
+ .report-section table th,
|
|
|
+ .report-section table td {
|
|
|
+ border: 1px solid #dee2e6;
|
|
|
+ padding: 4px 8px;
|
|
|
+ text-align: left;
|
|
|
+ }
|
|
|
+ .report-section table thead th {
|
|
|
+ background: #f8f9fa;
|
|
|
+ font-weight: 600;
|
|
|
+ }
|
|
|
+ .section-header {
|
|
|
+ background: #212529;
|
|
|
+ color: #fff;
|
|
|
+ padding: 6px 12px;
|
|
|
+ font-weight: 600;
|
|
|
+ margin-bottom: 0;
|
|
|
+ }
|
|
|
+ .section-body {
|
|
|
+ border: 1px solid #dee2e6;
|
|
|
+ border-top: 0;
|
|
|
+ padding: 14px 16px;
|
|
|
+ margin-bottom: 1.2rem;
|
|
|
+ }
|
|
|
+ .title-table td, .title-table th { padding: 2px 8px; }
|
|
|
+ .element-required-module .col { padding: 0 6px; }
|
|
|
+ </style>
|
|
|
+</head>
|
|
|
+<body>
|
|
|
+
|
|
|
+<div class="container" id="pdf-content">
|
|
|
+
|
|
|
+ <?php if (!$row): ?>
|
|
|
+ <div class="alert alert-danger mt-4">Record not found or access denied.</div>
|
|
|
+ <?php else: ?>
|
|
|
+
|
|
|
+ <!-- ── Header ──────────────────────────────────────────────────────────── -->
|
|
|
+ <div class="row align-items-center mb-3 mt-3">
|
|
|
+ <div class="col-3">
|
|
|
+ <img class="img-fluid" src="/client-assets/images/crop-monitor.png"
|
|
|
+ alt="Crop Monitor" style="max-height:55px;">
|
|
|
+ </div>
|
|
|
+ <div class="col-9 text-end">
|
|
|
+ <div class="fw-bold h5 mb-0">Plant Analysis Report</div>
|
|
|
+ <div class="text-muted small"><?= $h($today) ?></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <table class="title-table w-100 mb-3 small">
|
|
|
+ <tbody>
|
|
|
+ <tr>
|
|
|
+ <td class="text-end fw-bold text-nowrap">CLIENT:</td>
|
|
|
+ <td><?= $h($row['client_name']) ?></td>
|
|
|
+ <td></td>
|
|
|
+ <td class="text-end fw-bold text-nowrap">SAMPLE ID:</td>
|
|
|
+ <td><?= $h($row['site_id']) ?></td>
|
|
|
+ </tr>
|
|
|
+ <tr>
|
|
|
+ <td class="text-end fw-bold text-nowrap">ADDRESS:</td>
|
|
|
+ <td><?= $h($row['site_address']) ?></td>
|
|
|
+ <td></td>
|
|
|
+ <td class="text-end fw-bold text-nowrap">DATE SAMPLED:</td>
|
|
|
+ <td><?= $h($row['date_sampled']) ?></td>
|
|
|
+ </tr>
|
|
|
+ <tr>
|
|
|
+ <td></td>
|
|
|
+ <td><?= $h($row['state_postcode']) ?></td>
|
|
|
+ <td></td>
|
|
|
+ <td class="text-end fw-bold text-nowrap">LAB NUMBER:</td>
|
|
|
+ <td><?= $h($row['lab_no']) ?></td>
|
|
|
+ </tr>
|
|
|
+ <tr>
|
|
|
+ <td></td>
|
|
|
+ <td><?= $h($row['email']) ?></td>
|
|
|
+ <td></td>
|
|
|
+ <td class="text-end fw-bold text-nowrap">CROP:</td>
|
|
|
+ <td><?= $h($row['sample_id']) ?></td>
|
|
|
+ </tr>
|
|
|
+ <tr>
|
|
|
+ <td></td>
|
|
|
+ <td></td>
|
|
|
+ <td></td>
|
|
|
+ <td class="text-end fw-bold text-nowrap">PLANT TYPE:</td>
|
|
|
+ <td><?= $h($row['crop_type']) ?></td>
|
|
|
+ </tr>
|
|
|
+ </tbody>
|
|
|
+ </table>
|
|
|
+
|
|
|
+ <!-- ── Download / Back buttons ─────────────────────────────────────────── -->
|
|
|
+ <div class="d-print-none mb-3 d-flex gap-2">
|
|
|
+ <a href="/dashboard/crop-analysis/plant-test-data/plant-report.php?rid=<?= $recordId ?>&rand=<?= urlencode($randId) ?>"
|
|
|
+ class="btn btn-outline-secondary btn-sm">
|
|
|
+ ← Back to Report
|
|
|
+ </a>
|
|
|
+ <a href="/pdf-files/headlessChrome_pdf.php?type=plant-report&rid=<?= $recordId ?>&rand=<?= urlencode($randId) ?>"
|
|
|
+ class="btn btn-success btn-sm">
|
|
|
+ <i class="fas fa-download me-1"></i>Download PDF
|
|
|
+ </a>
|
|
|
+ <button class="btn btn-outline-dark btn-sm" onclick="window.print()">
|
|
|
+ <i class="fas fa-print me-1"></i>Print
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <hr>
|
|
|
+
|
|
|
+ <!-- ── 1. General Comment ─────────────────────────────────────────────────────── -->
|
|
|
+ <div class="section-header">General Comment</div>
|
|
|
+ <div class="section-body report-section">
|
|
|
+ <?= formatReportText($savedComments['general_details'] ?? '') ?>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- ── 2. AI Interpretation ───────────────────────────────────────── -->
|
|
|
+ <div class="section-header">AI Soil Interpretation</div>
|
|
|
+ <div class="section-body report-section">
|
|
|
+ <?= formatReportText($savedComments['ai_interpretation'] ?? '') ?>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- ── 3. Recommended Remedial Program ────────────────────────────────────────────────── -->
|
|
|
+ <div class="section-header">
|
|
|
+ <?= $h($savedComments['header1'] ?? 'Recommended Remedial Program') ?>
|
|
|
+ </div>
|
|
|
+ <div class="section-body report-section">
|
|
|
+ <?= formatReportText($savedComments['recommended_details'] ?? '') ?>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- ── 4. Foliar Program ─────────────────────────────────────────────── -->
|
|
|
+ <div class="section-header">Foliar Program</div>
|
|
|
+ <div class="section-body report-section">
|
|
|
+ <?= formatReportText($savedComments['foliar_details'] ?? '') ?>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- ── Disclaimer ──────────────────────────────────────────────────────── -->
|
|
|
+ <div class="mt-3 pt-3 border-top">
|
|
|
+ <p class="text-muted" style="font-size:0.7rem;">
|
|
|
+ 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.
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <?php endif; ?>
|
|
|
+
|
|
|
+</div><!-- /container -->
|
|
|
+
|
|
|
+<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
|
|
|
+
|
|
|
+</body>
|
|
|
+</html>
|