animal-report-pdf.php 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. <?php
  2. /**
  3. * animal-report-pdf.php
  4. *
  5. * Print / PDF-export version of a completed animal dietary balance report.
  6. * Normal access: ?rid=<id>&rand=<token>
  7. * Headless Chrome: ?rid=<id>&rand=<token>&ptoken=<one-time-token>
  8. */
  9. require_once __DIR__ . '/../../../config/database.php';
  10. require_once __DIR__ . '/../../../lib/auth.php';
  11. require_once __DIR__ . '/../../../lib/print_auth.php';
  12. require_once __DIR__ . '/../../../vendor/autoload.php';
  13. if (session_status() === PHP_SESSION_NONE) {
  14. session_start();
  15. }
  16. $recordId = (int) ($_GET['rid'] ?? 0);
  17. $randId = trim( $_GET['rand'] ?? '');
  18. $clientId = (int) ($_GET['cid'] ?? 0);
  19. $printMode = isset($_GET['ptoken']) || isset($_GET['print']);
  20. if (!$recordId || $randId === '') {
  21. http_response_code(400);
  22. die('Invalid request parameters');
  23. }
  24. $chromeAccess = authenticatePrintPage($recordId, $randId);
  25. try {
  26. $pdo = getDBConnection();
  27. $userId = $chromeAccess ? null : getCurrentUserId();
  28. $stmt = $pdo->prepare('SELECT * FROM animal_records WHERE id = ? AND rand = ?');
  29. $stmt->execute([$recordId, $randId]);
  30. $row = $stmt->fetch(PDO::FETCH_ASSOC);
  31. if (!$row) {
  32. http_response_code(404);
  33. die('Animal record not found');
  34. }
  35. // Load saved report comments
  36. $savedComments = [
  37. 'general_details' => '',
  38. 'ai_interpretation' => '',
  39. 'recommended_details' => '',
  40. 'foliar_details' => '',
  41. ];
  42. $reportUserId = $userId ?? (int)($row['modx_user_id'] ?? 0);
  43. $stmtRpt = $pdo->prepare(
  44. 'SELECT comment FROM reports WHERE record_id = ? AND modx_user_id = ? ORDER BY id DESC LIMIT 1'
  45. );
  46. $stmtRpt->execute([$recordId, $reportUserId]);
  47. $savedRow = $stmtRpt->fetchColumn();
  48. if ($savedRow) {
  49. $decoded = json_decode($savedRow, true);
  50. if (is_array($decoded)) {
  51. $savedComments = array_merge($savedComments, $decoded);
  52. }
  53. }
  54. } catch (PDOException $e) {
  55. error_log('DB error in animal-report-pdf.php: ' . $e->getMessage());
  56. die('Database error occurred');
  57. }
  58. $h = fn($v) => htmlspecialchars((string)($v ?? ''), ENT_QUOTES, 'UTF-8');
  59. $today = date('jS F Y');
  60. function formatReportText(string $text): string
  61. {
  62. if (trim($text) === '') {
  63. return '<p class="text-muted fst-italic">No content saved.</p>';
  64. }
  65. $parsedown = new Parsedown();
  66. $parsedown->setSafeMode(true);
  67. return $parsedown->text($text);
  68. }
  69. ?>
  70. <!doctype html>
  71. <html lang="en">
  72. <head>
  73. <meta charset="UTF-8">
  74. <meta name="viewport" content="width=device-width, initial-scale=1">
  75. <title>Animal Dietary Balance Report | Crop Monitor</title>
  76. <link rel="icon" href="/favicon.ico?v=2" type="image/x-icon">
  77. <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
  78. <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.9.0/css/all.css" rel="stylesheet" crossorigin="anonymous">
  79. <link href="/client-assets/css/dashboard.css" rel="stylesheet">
  80. <style>
  81. @media print {
  82. .d-print-none { display: none !important; }
  83. body { font-size: 11px; }
  84. }
  85. .report-section p { margin-bottom: 0.6rem; line-height: 1.6; }
  86. .report-section h1, .report-section h2,
  87. .report-section h3, .report-section h4 { font-size: 1rem; font-weight: 600; margin: 1rem 0 0.4rem; }
  88. .report-section ul, .report-section ol { padding-left: 1.4rem; margin-bottom: 0.6rem; }
  89. .report-section li { margin-bottom: 0.25rem; line-height: 1.5; }
  90. .section-header {
  91. background: #212529; color: #fff;
  92. padding: 6px 12px; font-weight: 600; margin-bottom: 0;
  93. }
  94. .section-body {
  95. border: 1px solid #dee2e6; border-top: 0;
  96. padding: 14px 16px; margin-bottom: 1.2rem;
  97. }
  98. .title-table td, .title-table th { padding: 2px 8px; }
  99. </style>
  100. </head>
  101. <body>
  102. <div class="container page" id="pdf-content">
  103. <?php if (!$row): ?>
  104. <div class="alert alert-danger mt-4">Record not found or access denied.</div>
  105. <?php else: ?>
  106. <!-- ── Header ──────────────────────────────────────────────────────────── -->
  107. <div class="row align-items-center mb-3 mt-3">
  108. <div class="col-3">
  109. <img class="img-fluid" src="/client-assets/images/crop-monitor.png"
  110. alt="Crop Monitor" style="max-height:55px;">
  111. </div>
  112. <div class="col-9 text-end">
  113. <div class="fw-bold h5 mb-0">Animal Dietary Balance Report</div>
  114. <div class="text-muted small"><?= $h($today) ?></div>
  115. </div>
  116. </div>
  117. <table class="title-table w-100 mb-3 small">
  118. <tbody>
  119. <tr>
  120. <td class="text-end fw-bold text-nowrap">CLIENT:</td>
  121. <td><?= $h($row['client_name']) ?></td>
  122. <td></td>
  123. <td class="text-end fw-bold text-nowrap">SAMPLE ID:</td>
  124. <td><?= $h($row['sample_id']) ?></td>
  125. </tr>
  126. <tr>
  127. <td class="text-end fw-bold text-nowrap">SITE ID:</td>
  128. <td><?= $h($row['site_id']) ?></td>
  129. <td></td>
  130. <td class="text-end fw-bold text-nowrap">DATE SAMPLED:</td>
  131. <td><?= $h($row['date_sampled']) ?></td>
  132. </tr>
  133. <tr>
  134. <td class="text-end fw-bold text-nowrap">ANALYSIS TYPE:</td>
  135. <td><?= $h($row['analysis_type'] ?? '') ?></td>
  136. <td></td>
  137. <td class="text-end fw-bold text-nowrap">LAB NUMBER:</td>
  138. <td><?= $h($row['lab_no']) ?></td>
  139. </tr>
  140. </tbody>
  141. </table>
  142. <!-- ── Back / Download buttons ─────────────────────────────────────────── -->
  143. <div class="d-print-none mb-3 d-flex gap-2">
  144. <a href="/dashboard/crop-analysis/animal-dietary-balance/animal-report.php?rid=<?= $recordId ?>&rand=<?= urlencode($randId) ?>"
  145. class="btn btn-outline-secondary btn-sm">
  146. &larr; Back to Report
  147. </a>
  148. <a href="/pdf-files/headlessChrome_pdf.php?type=animal-report&rid=<?= $recordId ?>&rand=<?= urlencode($randId) ?>"
  149. class="btn btn-success btn-sm">
  150. <i class="fas fa-download me-1"></i>Download PDF
  151. </a>
  152. <button class="btn btn-outline-dark btn-sm" onclick="window.print()">
  153. <i class="fas fa-print me-1"></i>Print
  154. </button>
  155. </div>
  156. <hr>
  157. <div class="section-header">General Comment</div>
  158. <div class="section-body report-section">
  159. <?= formatReportText($savedComments['general_details'] ?? '') ?>
  160. </div>
  161. <div class="section-header">AI Dietary Interpretation</div>
  162. <div class="section-body report-section">
  163. <?= formatReportText($savedComments['ai_interpretation'] ?? '') ?>
  164. </div>
  165. <div class="section-header">Recommended Supplementation Program</div>
  166. <div class="section-body report-section">
  167. <?= formatReportText($savedComments['recommended_details'] ?? '') ?>
  168. </div>
  169. <div class="section-header">Ongoing Management Program</div>
  170. <div class="section-body report-section">
  171. <?= formatReportText($savedComments['foliar_details'] ?? '') ?>
  172. </div>
  173. <div class="mt-3 pt-3 border-top">
  174. <p class="text-muted fst-italic" style="font-size:0.7rem;">
  175. Any recommendations provided by Crop Monitor are advice only. We are not paid consultants
  176. and accept no responsibility for any of our suggestions.
  177. </p>
  178. </div>
  179. <?php endif; ?>
  180. </div><!-- /container -->
  181. <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
  182. </body>
  183. </html>