feat(diagnosis): add print templates with image upload and optimize PDF rendering

This commit is contained in:
DevSoftTech Server 2026-03-15 06:50:44 +05:30
parent 68dfbaf006
commit 1dee940ac0
10 changed files with 2143 additions and 29 deletions

View File

@ -5,6 +5,8 @@ namespace App\Controllers;
use App\Controllers\BaseController;
use CodeIgniter\I18n\Time;
use Exception;
use Mpdf\HTMLParserMode;
use Mpdf\Mpdf;
class Diagnosis extends BaseController
{
@ -341,6 +343,7 @@ class Diagnosis extends BaseController
'lab_type' => $labType,
'lab_type_name' => $this->getLabTypeName($labType),
'lab_type_route' => $this->getLabTypeRoute($labType),
'print_templates' => $this->getDiagnosisPrintTemplates($labType),
];
return view('diagnosis/pathology_detail', $data);
@ -421,6 +424,7 @@ class Diagnosis extends BaseController
'invoiceId' => $invoiceId,
'labType' => $labType,
'lab_type' => $labType,
'print_templates' => $this->getDiagnosisPrintTemplates($labType),
];
$view = view('diagnosis/lab_invoice_main_test_list', $data);
@ -1046,6 +1050,7 @@ class Diagnosis extends BaseController
public function openReportEditorPage($labReqId)
{
$labReqId = (int) $labReqId;
$editReason = trim((string) ($this->request->getGet('edit_reason') ?? ''));
$reportRow = $this->db->table('lab_request')->where('id', $labReqId)->get()->getRow();
if (! $reportRow) {
@ -1111,6 +1116,7 @@ class Diagnosis extends BaseController
return view('diagnosis/lab_final_report_editor', [
'report_format' => [$reportRow],
'radiology_ultrasound_template' => $templates,
'edit_reason' => $editReason,
]);
}
@ -1139,14 +1145,28 @@ class Diagnosis extends BaseController
public function finalUpdateXray($labReqId)
{
$labReqId = (int) $labReqId;
$htmlData = (string) $this->request->getPost('HTMLData');
$impression = (string) $this->request->getPost('report_data_Impression');
$htmlData = (string) ($this->request->getPost('HTMLData') ?? $this->request->getPost('report_data') ?? '');
$impression = (string) ($this->request->getPost('report_data_Impression') ?? $this->request->getPost('report_data_impression') ?? '');
$editReason = trim((string) ($this->request->getPost('edit_reason') ?? ''));
if ($labReqId <= 0) {
return $this->response->setJSON(['status' => 'error', 'message' => 'Invalid request id']);
}
try {
$before = $this->db->table('lab_request')
->select('id, status, report_edit_req_no, Report_Data, report_data_Impression')
->where('id', $labReqId)
->get(1)
->getRowArray() ?? [];
if ((int) ($before['status'] ?? 0) === 2 && $editReason === '') {
return $this->response->setJSON([
'status' => 'error',
'message' => 'Edit reason is required for verified report changes (NABH audit).',
]);
}
$this->db->table('lab_request')
->where('id', $labReqId)
->update([
@ -1154,6 +1174,35 @@ class Diagnosis extends BaseController
'report_data_Impression' => $impression,
]);
$fields = $this->db->getFieldNames('lab_request');
if (in_array('report_edit_req_no', $fields, true) && (int) ($before['status'] ?? 0) === 2) {
$nextEditNo = (int) ($before['report_edit_req_no'] ?? 0) + 1;
$this->db->table('lab_request')
->where('id', $labReqId)
->update(['report_edit_req_no' => $nextEditNo]);
}
if ($this->db->tableExists('lab_log')) {
$user = service('auth')->user();
$userId = (int) ($user->id ?? 0);
$userName = $user ? trim(($user->first_name ?? '') . ' ' . ($user->last_name ?? '')) : 'System';
$beforeHash = md5((string) ($before['Report_Data'] ?? '') . '|' . (string) ($before['report_data_Impression'] ?? ''));
$afterHash = md5($htmlData . '|' . $impression);
$reasonLog = $editReason !== '' ? $editReason : 'NA';
$this->db->table('lab_log')->insert([
'lab_repo_id' => $labReqId,
'log_by_id' => $userId,
'log_by' => $userName,
'log_type_id' => 0,
'log_type' => 'Report Edit',
'log_Faults_id' => 0,
'log_Faults' => 'Radiology',
'comments' => 'Editor save [reason:' . substr($reasonLog, 0, 350) . '] [before:' . substr($beforeHash, 0, 8) . ' after:' . substr($afterHash, 0, 8) . ']',
]);
}
return $this->response->setJSON(['status' => 'success', 'message' => 'Saved']);
} catch (\Throwable $e) {
return $this->response->setJSON(['status' => 'error', 'message' => $e->getMessage()]);
@ -1212,23 +1261,624 @@ class Diagnosis extends BaseController
public function printSingleReport($labReqId)
{
$labReqId = (int) $labReqId;
$templateId = (int) ($this->request->getGet('template_id') ?? 0);
set_time_limit(300);
if ($labReqId <= 0) {
return '<h3>Invalid request id</h3>';
}
$row = $this->db->table('lab_request')
->where('id', $labReqId)
->get()
->getRow();
$sql = "SELECT l.*, m.invoice_code, m.attach_id,
p.p_fname, p.p_rname, p.p_relative, p.p_code, p.gender, p.dob, p.age, p.age_in, p.age_in_month, p.estimate_dob,
r.Title as repo_title, g.RepoGrp
FROM lab_request l
LEFT JOIN invoice_master m ON m.id = l.charge_id
LEFT JOIN patient_master p ON p.id = m.attach_id
LEFT JOIN lab_repo r ON r.mstRepoKey = l.lab_repo_id
LEFT JOIN lab_rgroups g ON g.mstRGrpKey = r.GrpKey
WHERE l.id = ?";
$row = $this->db->query($sql, [$labReqId])->getRow();
if (! $row) {
return '<h3>Report not found</h3>';
}
return view('diagnosis/print_single_report', [
'report' => $row,
$reportData = trim((string) ($row->Report_Data ?? ''));
$impression = trim((string) ($row->report_data_Impression ?? ''));
$reportHtml = '';
if ($reportData !== '') {
$reportHtml .= $reportData;
}
if ($impression !== '') {
$reportHtml .= '<div style="margin-top:10px;"><b>Impression :</b><br/>' . nl2br($impression) . '</div>';
}
if ($reportHtml === '') {
$reportHtml = $this->resolveFallbackReportHtml((int) ($row->lab_repo_id ?? 0), (int) ($row->charge_item_id ?? 0), (int) ($row->lab_type ?? 0));
}
if ($reportHtml === '') {
$reportHtml = '<p>No report data available for this request. Please use Edit and save report content.</p>';
}
$genderValue = (int) ($row->gender ?? 0);
$genderText = $genderValue === 1 ? 'Male' : ($genderValue === 2 ? 'Female' : '-');
$ageText = '-';
if (function_exists('get_age_1')) {
$ageText = (string) get_age_1(
$row->dob ?? null,
$row->age ?? '',
$row->age_in_month ?? '',
$row->estimate_dob ?? ''
);
} elseif (! empty($row->age)) {
$ageText = (string) $row->age;
}
$printSetting = $this->getDiagnosisTemplateSetting((int) ($row->lab_type ?? 0), $templateId);
$isPlainPaper = false;
// Compute display values here so they are available for token substitution.
$patientName = trim((string) ($row->p_fname ?? ''));
$relativeName = trim((string) ($row->p_relative ?? ''));
$relativeText = trim((string) ($row->p_rname ?? ''));
$repoTitle = trim((string) ($row->report_name ?? $row->repo_title ?? $row->RepoGrp ?? 'Report'));
$headRow = $this->db->table('diagnosis_head_name')
->where('d_type', (int) ($row->lab_type ?? 0))
->get(1)
->getRow();
$tokens = $this->buildPdfTokens([
'invoice_code' => $row->invoice_code ?? '',
'patient_name' => $patientName,
'relative_type' => $relativeName,
'relative_name' => $relativeText,
'age' => $ageText,
'gender' => $genderText,
'uhid' => $row->p_code ?? '',
'collected_time' => $row->collected_time ?? '',
'reported_time' => $row->reported_time ?? '',
'report_title' => $repoTitle,
'doctor_name' => $headRow->doc_name ?? '',
'doctor_education' => $headRow->doc_edu ?? '',
'technician_name' => $headRow->tech_name ?? '',
'signature_image_url' => (string) ($printSetting['signature_image'] ?? ''),
]);
// Render the optional patient info template (tokens resolved).
$rawPatientInfoHtml = trim((string) ($printSetting['patient_info_html'] ?? ''));
if ($rawPatientInfoHtml !== '') {
$patientInfoHtml = $this->applyPdfTokens($rawPatientInfoHtml, $tokens);
} elseif (($printSetting['id'] ?? 0) <= 0) {
$patientInfoHtml = $this->buildDefaultPatientInfoHtml([
'invoice_code' => $row->invoice_code ?? '',
'patient_name' => $patientName,
'relative_name' => $relativeName,
'relative_text' => $relativeText,
'age_text' => $ageText,
'gender_text' => $genderText,
'uhid' => $row->p_code ?? '',
'collected_time' => $row->collected_time ?? '',
'reported_time' => $row->reported_time ?? '',
]);
} else {
$patientInfoHtml = '';
}
$reportHtml = trim((string) ($printSetting['content_prefix_html'] ?? '')) . $reportHtml . trim((string) ($printSetting['content_suffix_html'] ?? ''));
$pdfHtml = view('diagnosis/pdf_single_report', [
'report' => $row,
'reportHtml' => $reportHtml,
'isPlainPaper' => $isPlainPaper,
'ageText' => $ageText,
'genderText' => $genderText,
'printSetting' => $printSetting,
'patientName' => $patientName,
'relativeName' => $relativeName,
'relativeText' => $relativeText,
'repoTitle' => $repoTitle,
'patientInfoHtml' => $patientInfoHtml,
]);
$rawHeaderHtml = trim((string) ($printSetting['header_html'] ?? ''));
$rawFirstPageHeaderHtml = trim((string) ($printSetting['first_page_header_html'] ?? ''));
$rawFooterHtml = trim((string) ($printSetting['footer_html'] ?? ''));
$rawLastPageFooterHtml = trim((string) ($printSetting['last_page_footer_html'] ?? ''));
$autoHeaderHtml = $this->buildAutoMpdfHeaderBlock(
$rawHeaderHtml,
$rawFirstPageHeaderHtml,
'diag_auto_header_single'
);
$autoFooterHtml = $this->buildAutoMpdfFooterBlock(
$rawFooterHtml,
$rawLastPageFooterHtml,
'diag_auto_footer_single'
);
$prefixHtml = $this->applyPdfTokens(trim((string) ($printSetting['mpdf_prefix_html'] ?? ''))
. $autoHeaderHtml
. $autoFooterHtml, $tokens);
$suffixHtml = $this->applyPdfTokens(trim((string) ($printSetting['mpdf_suffix_html'] ?? '')), $tokens);
$marginTop = $this->cmToMm($printSetting['page_margin_top_cm'] ?? 1.2, 1.2);
$marginBottom = $this->cmToMm($printSetting['page_margin_bottom_cm'] ?? 1.2, 1.2);
$marginLeft = $this->cmToMm($printSetting['page_margin_left_cm'] ?? 1.0, 1.0);
$marginRight = $this->cmToMm($printSetting['page_margin_right_cm'] ?? 1.0, 1.0);
$marginHeader = $this->cmToMm($printSetting['margin_header_cm'] ?? 0.5, 0.5);
$marginFooter = $this->cmToMm($printSetting['margin_footer_cm'] ?? 1.5, 1.5);
$mpdfTempDir = WRITEPATH . 'cache' . DIRECTORY_SEPARATOR . 'mpdf';
if (! is_dir($mpdfTempDir)) {
mkdir($mpdfTempDir, 0755, true);
}
$mpdf = new Mpdf([
'format' => (string) ($printSetting['page_size'] ?? 'A4'),
'margin_top' => $marginTop,
'margin_bottom' => $marginBottom,
'margin_left' => $marginLeft,
'margin_right' => $marginRight,
'margin_header' => $marginHeader,
'margin_footer' => $marginFooter,
'tempDir' => $mpdfTempDir,
'autoScriptToLang' => false,
'autoLanguageDetection' => false,
'autoArabic' => false,
'autoVietnamese' => false,
]);
// For plain HTML (without explicit mPDF tags), set header/footer via API.
$headerHasTags = preg_match('/<\s*(htmlpageheader|sethtmlpageheader)\b/i', $rawHeaderHtml . $rawFirstPageHeaderHtml) === 1;
$footerHasTags = preg_match('/<\s*(htmlpagefooter|sethtmlpagefooter)\b/i', $rawFooterHtml . $rawLastPageFooterHtml) === 1;
if (! $headerHasTags) {
$plainHeader = $this->applyPdfTokens($rawHeaderHtml . $rawFirstPageHeaderHtml, $tokens);
if ($plainHeader !== '') {
$mpdf->SetHTMLHeader($plainHeader, 'O');
$mpdf->SetHTMLHeader($plainHeader, 'E');
}
}
if (! $footerHasTags) {
$plainFooter = $this->applyPdfTokens($rawFooterHtml, $tokens);
if ($plainFooter === '') {
$plainFooter = $this->applyPdfTokens($rawLastPageFooterHtml, $tokens);
}
if ($plainFooter !== '') {
$mpdf->SetHTMLFooter($plainFooter, 'O');
$mpdf->SetHTMLFooter($plainFooter, 'E');
}
}
$backgroundImage = trim((string) ($printSetting['page_background_image'] ?? ''));
if ($backgroundImage !== '') {
$absolutePath = FCPATH . ltrim(str_replace('\\', '/', $backgroundImage), '/');
if (is_file($absolutePath)) {
$mpdf->SetWatermarkImage($absolutePath, 1, [210, 297], 'F');
$mpdf->showWatermarkImage = true;
}
}
$watermarkType = (string) ($printSetting['watermark_type'] ?? 'none');
$watermarkAlpha = (float) ($printSetting['watermark_alpha'] ?? 0.12);
if ($watermarkType === 'text') {
$text = trim((string) ($printSetting['watermark_text'] ?? ''));
if ($text !== '') {
$mpdf->SetWatermarkText($text, $watermarkAlpha);
$mpdf->showWatermarkText = true;
}
} elseif ($watermarkType === 'image') {
$wmImage = trim((string) ($printSetting['watermark_image'] ?? ''));
if ($wmImage !== '') {
$absolutePath = FCPATH . ltrim(str_replace('\\', '/', $wmImage), '/');
if (is_file($absolutePath)) {
$mpdf->SetWatermarkImage($absolutePath, $watermarkAlpha);
$mpdf->showWatermarkImage = true;
}
}
}
if ($prefixHtml !== '') {
$mpdf->WriteHTML($prefixHtml, HTMLParserMode::HTML_HEADER_BUFFER);
}
$mpdf->WriteHTML($pdfHtml);
if ($suffixHtml !== '') {
$mpdf->WriteHTML($suffixHtml, HTMLParserMode::HTML_HEADER_BUFFER, false, false);
}
$invoiceCode = preg_replace('/[^A-Za-z0-9\-_]/', '_', (string) ($row->invoice_code ?? 'invoice'));
$fileName = 'Report_' . $invoiceCode . '_' . $labReqId . '.pdf';
return $this->response
->setHeader('Content-Type', 'application/pdf')
->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
->setHeader('Pragma', 'no-cache')
->setHeader('Expires', '0')
->setHeader('Content-Disposition', 'inline; filename="' . $fileName . '"')
->setBody($mpdf->Output($fileName, 'S'));
}
private function getDiagnosisPrintTemplates(int $labType): array
{
if ($labType <= 0 || ! $this->db->tableExists('diagnosis_print_templates')) {
return [];
}
return $this->db->table('diagnosis_print_templates')
->select('id, template_name, is_default')
->where('modality', $labType)
->where('status', 1)
->orderBy('is_default', 'DESC')
->orderBy('template_name', 'ASC')
->get()
->getResultArray();
}
private function getDiagnosisTemplateSetting(int $labType, int $templateId = 0): array
{
$legacy = $this->getDiagnosisPrintSetting($labType);
$defaults = array_merge($legacy, [
'page_size' => 'A4',
'page_margin_top_cm' => 1.2,
'id' => 0,
'page_margin_bottom_cm' => 1.2,
'page_margin_left_cm' => 1.0,
'page_margin_right_cm' => 1.0,
'margin_header_cm' => 0.5,
'margin_footer_cm' => 1.5,
'page_background_image' => '',
'watermark_type' => 'none',
'watermark_text' => '',
'watermark_image' => '',
'signature_image' => '',
'watermark_alpha' => 0.12,
'header_html' => '',
'first_page_header_html' => '',
'content_prefix_html' => '',
'content_suffix_html' => '',
'footer_html' => '',
'last_page_footer_html' => '',
]);
if ($labType <= 0 || ! $this->db->tableExists('diagnosis_print_templates')) {
return $defaults;
}
$builder = $this->db->table('diagnosis_print_templates')
->where('modality', $labType)
->where('status', 1);
if ($templateId > 0) {
$builder->where('id', $templateId);
} else {
$builder->orderBy('is_default', 'DESC')->orderBy('id', 'ASC');
}
$row = $builder->get(1)->getRowArray();
if (! is_array($row)) {
return $defaults;
}
foreach ($defaults as $key => $value) {
if (array_key_exists($key, $row) && $row[$key] !== null && $row[$key] !== '') {
if (is_float($value)) {
$defaults[$key] = (float) $row[$key];
} else {
$defaults[$key] = (string) $row[$key];
}
}
}
$pageSize = strtoupper(trim((string) ($defaults['page_size'] ?? 'A4')));
$defaults['id'] = (int) ($row['id'] ?? 0);
if (! in_array($pageSize, ['A4', 'A4-L', 'LETTER', 'LEGAL'], true)) {
$pageSize = 'A4';
}
$defaults['page_size'] = $pageSize;
return $defaults;
}
private function getDiagnosisPrintSetting(int $labType): array
{
$defaults = [
'letter_margin_top' => 12.0,
'letter_margin_left' => 10.0,
'letter_margin_right' => 10.0,
'letter_margin_bottom' => 12.0,
'plain_header_html' => '',
'plain_background_image' => '',
'mpdf_prefix_html' => '',
'mpdf_suffix_html' => '',
'patient_info_html' => '',
];
if ($labType <= 0 || ! $this->db->tableExists('diagnosis_head_name')) {
return $defaults;
}
$fields = $this->db->getFieldNames('diagnosis_head_name');
$select = ['d_type'];
foreach (array_keys($defaults) as $field) {
if (in_array($field, $fields, true)) {
$select[] = $field;
}
}
$row = $this->db->table('diagnosis_head_name')
->select(implode(',', $select))
->where('d_type', $labType)
->get(1)
->getRowArray();
if (! is_array($row)) {
return $defaults;
}
foreach ($defaults as $key => $defaultValue) {
if (array_key_exists($key, $row) && $row[$key] !== null && $row[$key] !== '') {
$defaults[$key] = is_float($defaultValue) ? (float) $row[$key] : (string) $row[$key];
}
}
return $defaults;
}
/**
* Build an associative array of {{token}} => value pairs for PDF template substitution.
* All string values are HTML-escaped so they are safe to embed inside HTML attributes and text.
*/
private function buildPdfTokens(array $data): array
{
$e = static function (string $v): string {
return htmlspecialchars($v, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
};
$age = (string) ($data['age'] ?? '-');
$gender = (string) ($data['gender'] ?? '-');
$hospitalName = defined('H_Name') ? (string) constant('H_Name') : 'Hospital';
$hospitalAddress1 = defined('H_address_1') ? (string) constant('H_address_1') : '';
$hospitalPhone = defined('H_phone_No') ? (string) constant('H_phone_No') : '';
$hospitalLogoName = defined('H_logo') ? trim((string) constant('H_logo')) : '';
$hospitalLogoUrl = $this->resolvePdfAssetPath([
$hospitalLogoName !== '' ? 'assets/images/' . ltrim($hospitalLogoName, '/\\') : '',
'assets/img/logo.png',
'assets/images/no_image.svg',
]);
$signatureImageUrl = $this->resolvePdfAssetPath([
(string) ($data['signature_image_url'] ?? ''),
'assets/images/drPreetiSingh.jpg',
]);
return [
'{{invoice_code}}' => $e((string) ($data['invoice_code'] ?? '')),
'{{patient_name}}' => $e((string) ($data['patient_name'] ?? '')),
'{{relative_type}}' => $e((string) ($data['relative_type'] ?? '')),
'{{relative_name}}' => $e((string) ($data['relative_name'] ?? '')),
'{{relative}}' => $e(trim((string) ($data['relative_type'] ?? '') . ' ' . (string) ($data['relative_name'] ?? ''))),
'{{age}}' => $e($age),
'{{gender}}' => $e($gender),
'{{age_sex}}' => $e($age . ' / ' . $gender),
'{{uhid}}' => $e((string) ($data['uhid'] ?? '')),
'{{collected_time}}' => $e((string) ($data['collected_time'] ?? '')),
'{{reported_time}}' => $e((string) ($data['reported_time'] ?? '')),
'{{printed_time}}' => date('d-m-Y h:i A'),
'{{report_title}}' => $e((string) ($data['report_title'] ?? '')),
'{{doctor_name}}' => $e((string) ($data['doctor_name'] ?? '')),
'{{doctor_education}}' => $e((string) ($data['doctor_education'] ?? '')),
'{{technician_name}}' => $e((string) ($data['technician_name'] ?? '')),
'{{hospital_name}}' => $e($hospitalName),
'{{hospital_address_1}}' => $e($hospitalAddress1),
'{{hospital_phone}}' => $e($hospitalPhone),
'{{hospital_logo_name}}' => $e($hospitalLogoName),
'{{hospital_logo_url}}' => $e($hospitalLogoUrl),
'{{hospital_logo_path}}' => $e($hospitalLogoUrl),
'{{signature_image_url}}' => $e($signatureImageUrl),
'{{signature_image_path}}' => $e($signatureImageUrl),
];
}
private function resolvePdfAssetPath(array $candidates): string
{
foreach ($candidates as $candidate) {
$candidate = trim((string) $candidate);
if ($candidate === '') {
continue;
}
if (preg_match('/^https?:\/\//i', $candidate) === 1 || str_starts_with($candidate, 'data:')) {
continue;
}
if (preg_match('/^[A-Za-z]:[\\\\\/]/', $candidate) === 1) {
$absolutePath = $candidate;
} else {
$relativePath = ltrim(str_replace('\\', '/', $candidate), '/');
$absolutePath = str_starts_with($relativePath, 'public/')
? ROOTPATH . $relativePath
: FCPATH . $relativePath;
}
$absolutePath = str_replace('\\', '/', $absolutePath);
if (is_file($absolutePath)) {
return $absolutePath;
}
}
return '';
}
/**
* Replace all {{token}} placeholders in $html using the token map returned by buildPdfTokens().
*/
private function applyPdfTokens(string $html, array $tokens): string
{
return str_replace(array_keys($tokens), array_values($tokens), $html);
}
private function buildDefaultPatientInfoHtml(array $data): string
{
$e = static function (string $v): string {
return htmlspecialchars($v, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
};
$ageSex = $e((string) ($data['age_text'] ?? '-')) . ' / ' . $e((string) ($data['gender_text'] ?? '-'));
$relative = $e(trim((string) ($data['relative_name'] ?? '') . ' ' . (string) ($data['relative_text'] ?? '')));
return '<table width="100%" style="border-collapse:collapse;margin-bottom:8px;font-size:11px;">'
. '<tr>'
. '<td width="50%" style="vertical-align:top;">'
. '<b>Invoice ID:</b> ' . $e((string) ($data['invoice_code'] ?? '')) . '<br>'
. '<b>Patient:</b> ' . $e((string) ($data['patient_name'] ?? '')) . '<br>'
. '<b>Relative:</b> ' . $relative . '<br>'
. '<b>Age/Sex:</b> ' . $ageSex
. '</td>'
. '<td width="50%" style="vertical-align:top;text-align:right;">'
. '<b>UHID:</b> ' . $e((string) ($data['uhid'] ?? '')) . '<br>'
. '<b>Collected:</b> ' . $e((string) ($data['collected_time'] ?? '')) . '<br>'
. '<b>Reported:</b> ' . $e((string) ($data['reported_time'] ?? '')) . '<br>'
. '<b>Printed:</b> ' . $e(date('d-m-Y h:i A'))
. '</td>'
. '</tr></table>';
}
private function buildAutoMpdfHeaderBlock(string $headerHtml, string $firstPageHeaderHtml, string $name): string
{
$headerHtml = trim($headerHtml);
$firstPageHeaderHtml = trim($firstPageHeaderHtml);
$combined = $headerHtml . $firstPageHeaderHtml;
if ($combined === '') {
return '';
}
if (preg_match('/<\s*(htmlpageheader|sethtmlpageheader)\b/i', $combined) === 1) {
return $combined;
}
return '';
}
private function buildAutoMpdfFooterBlock(string $footerHtml, string $lastPageFooterHtml, string $name): string
{
$footerHtml = trim($footerHtml);
$lastPageFooterHtml = trim($lastPageFooterHtml);
$combined = $footerHtml . $lastPageFooterHtml;
if ($combined === '') {
return '';
}
if (preg_match('/<\s*(htmlpagefooter|sethtmlpagefooter)\b/i', $combined) === 1) {
return $combined;
}
return '';
}
private function normalizeMarginMm($rawValue, float $default): float
{
if ($rawValue === null || $rawValue === '') {
return $default;
}
$value = (float) $rawValue;
if (! is_finite($value)) {
$value = $default;
}
$value = max(0.0, min(60.0, $value));
return round($value, 2);
}
private function cmToMm($rawCmValue, float $defaultCm): float
{
if ($rawCmValue === null || $rawCmValue === '') {
return round($defaultCm * 10.0, 2);
}
$cm = (float) $rawCmValue;
if (! is_finite($cm)) {
$cm = $defaultCm;
}
$cm = max(0.0, min(25.0, $cm));
return round($cm * 10.0, 2);
}
private function resolveFallbackReportHtml(int $labRepoId, int $chargeItemId, int $labType): string
{
$html = '';
if ($labRepoId > 0) {
$repoRow = $this->db->table('lab_repo')
->select('HTMLData')
->where('mstRepoKey', $labRepoId)
->get(1)
->getRowArray();
$html = trim((string) ($repoRow['HTMLData'] ?? ''));
}
if ($html !== '') {
return $html;
}
if (! $this->db->tableExists('radiology_ultrasound_template') || $chargeItemId <= 0) {
return '';
}
$invoiceItem = $this->db->table('invoice_item')
->select('item_id')
->where('id', $chargeItemId)
->get(1)
->getRowArray();
$chargeId = (int) ($invoiceItem['item_id'] ?? 0);
$builder = $this->db->table('radiology_ultrasound_template')
->select('Findings, Impression')
->where('Modality', $labType);
if ($chargeId > 0) {
$builder->groupStart()
->where('charge_id', $chargeId)
->orWhere('charge_id', 0)
->groupEnd();
}
$template = $builder->orderBy('id', 'ASC')->get(1)->getRowArray();
if (! is_array($template)) {
return '';
}
$findings = trim((string) ($template['Findings'] ?? ''));
$impression = trim((string) ($template['Impression'] ?? ''));
if ($findings === '' && $impression === '') {
return '';
}
$html = $findings;
if ($impression !== '') {
$html .= '<div style="margin-top:10px;"><b>Impression :</b><br/>' . nl2br($impression) . '</div>';
}
return $html;
}
public function removeTest($labReqId)
@ -1419,6 +2069,9 @@ class Diagnosis extends BaseController
$labType = (int) $labType;
$print = (int) $print;
$printOnType = (int) $printOnType;
$templateId = (int) ($this->request->getGet('template_id') ?? 0);
set_time_limit(300);
if ($invoiceId <= 0 || $labType <= 0) {
return '<h3>Invalid parameters</h3>';
@ -1450,31 +2103,233 @@ class Diagnosis extends BaseController
. ' / Invoice ID :' . ($invoice->invoice_code ?? '')
. ' / Person Name :' . ($patient->p_fname ?? '');
$isPlainPaper = ($printOnType === 1);
$isPlainPaper = false;
if ($print > 0) {
return view('diagnosis/print_compiled_report', [
'invoiceRequest' => $invoiceRequest,
'invoice' => $invoice,
'patient' => $patient,
'head' => $head,
'reportHead' => $reportHead,
'isPlainPaper' => $isPlainPaper,
'labType' => $labType,
'invoiceId' => $invoiceId,
]);
$genderValue = (int) ($patient->gender ?? 0);
$genderText = $genderValue === 1 ? 'Male' : ($genderValue === 2 ? 'Female' : '-');
$ageText = '-';
if (function_exists('get_age_1')) {
$ageText = (string) get_age_1(
$patient->dob ?? null,
$patient->age ?? '',
$patient->age_in_month ?? '',
$patient->estimate_dob ?? ''
);
} elseif (! empty($patient->age)) {
$ageText = (string) $patient->age;
}
return view('diagnosis/print_compiled_report', [
'invoiceRequest' => $invoiceRequest,
'invoice' => $invoice,
'patient' => $patient,
'head' => $head,
'reportHead' => $reportHead,
'isPlainPaper' => $isPlainPaper,
'labType' => $labType,
'invoiceId' => $invoiceId,
$reportHtml = trim((string) ($invoiceRequest->report_data ?? ''));
if ($reportHtml === '') {
$reportHtml = '<p>No compiled report data available for this request.</p>';
}
$printSetting = $this->getDiagnosisTemplateSetting($labType, $templateId);
$patientName = trim((string) ($patient->p_fname ?? ''));
$relativeName = trim((string) ($patient->p_relative ?? ''));
$relativeText = trim((string) ($patient->p_rname ?? ''));
$repoTitle = trim((string) ($itemType->group_desc ?? 'Lab Report'));
$tokens = $this->buildPdfTokens([
'invoice_code' => $invoice->invoice_code ?? '',
'patient_name' => $patientName,
'relative_type' => $relativeName,
'relative_name' => $relativeText,
'age' => $ageText,
'gender' => $genderText,
'uhid' => $patient->p_code ?? '',
'collected_time' => $invoiceRequest->collected_time ?? '',
'reported_time' => $invoiceRequest->reported_time ?? '',
'report_title' => $repoTitle,
'doctor_name' => $head->doc_name ?? '',
'doctor_education' => $head->doc_edu ?? '',
'technician_name' => $head->tech_name ?? '',
'signature_image_url' => (string) ($printSetting['signature_image'] ?? ''),
]);
$rawPatientInfoHtml = trim((string) ($printSetting['patient_info_html'] ?? ''));
if ($rawPatientInfoHtml !== '') {
$patientInfoHtml = $this->applyPdfTokens($rawPatientInfoHtml, $tokens);
} elseif (($printSetting['id'] ?? 0) <= 0) {
$patientInfoHtml = $this->buildDefaultPatientInfoHtml([
'invoice_code' => $invoice->invoice_code ?? '',
'patient_name' => $patientName,
'relative_name' => $relativeName,
'relative_text' => $relativeText,
'age_text' => $ageText,
'gender_text' => $genderText,
'uhid' => $patient->p_code ?? '',
'collected_time' => $invoiceRequest->collected_time ?? '',
'reported_time' => $invoiceRequest->reported_time ?? '',
]);
} else {
$patientInfoHtml = '';
}
$reportHtml = trim((string) ($printSetting['content_prefix_html'] ?? '')) . $reportHtml . trim((string) ($printSetting['content_suffix_html'] ?? ''));
$marginTop = $this->cmToMm($printSetting['page_margin_top_cm'] ?? 1.2, 1.2);
$marginBottom = $this->cmToMm($printSetting['page_margin_bottom_cm'] ?? 1.2, 1.2);
$marginLeft = $this->cmToMm($printSetting['page_margin_left_cm'] ?? 1.0, 1.0);
$marginRight = $this->cmToMm($printSetting['page_margin_right_cm'] ?? 1.0, 1.0);
$marginHeader = $this->cmToMm($printSetting['margin_header_cm'] ?? 0.5, 0.5);
$marginFooter = $this->cmToMm($printSetting['margin_footer_cm'] ?? 1.5, 1.5);
$pdfHtml = view('diagnosis/pdf_compiled_report', [
'invoiceRequest' => $invoiceRequest,
'invoice' => $invoice,
'patient' => $patient,
'reportHead' => $reportHead,
'reportHtml' => $reportHtml,
'isPlainPaper' => $isPlainPaper,
'ageText' => $ageText,
'genderText' => $genderText,
'printSetting' => $printSetting,
'labType' => $labType,
'invoiceId' => $invoiceId,
'itemType' => $itemType,
'patientName' => $patientName,
'relativeName' => $relativeName,
'relativeText' => $relativeText,
'repoTitle' => $repoTitle,
'patientInfoHtml' => $patientInfoHtml,
]);
$rawHeaderHtml = trim((string) ($printSetting['header_html'] ?? ''));
$rawFirstPageHeaderHtml = trim((string) ($printSetting['first_page_header_html'] ?? ''));
$rawFooterHtml = trim((string) ($printSetting['footer_html'] ?? ''));
$rawLastPageFooterHtml = trim((string) ($printSetting['last_page_footer_html'] ?? ''));
$autoHeaderHtml = $this->buildAutoMpdfHeaderBlock(
$rawHeaderHtml,
$rawFirstPageHeaderHtml,
'diag_auto_header_compiled'
);
$autoFooterHtml = $this->buildAutoMpdfFooterBlock(
$rawFooterHtml,
$rawLastPageFooterHtml,
'diag_auto_footer_compiled'
);
$prefixHtml = $this->applyPdfTokens(trim((string) ($printSetting['mpdf_prefix_html'] ?? ''))
. $autoHeaderHtml
. $autoFooterHtml, $tokens);
$suffixHtml = $this->applyPdfTokens(trim((string) ($printSetting['mpdf_suffix_html'] ?? '')), $tokens);
$mpdfTempDir = WRITEPATH . 'cache' . DIRECTORY_SEPARATOR . 'mpdf';
if (! is_dir($mpdfTempDir)) {
mkdir($mpdfTempDir, 0755, true);
}
$t_start = microtime(true);
$mpdf = new Mpdf([
'format' => (string) ($printSetting['page_size'] ?? 'A4'),
'margin_top' => $marginTop,
'margin_bottom' => $marginBottom,
'margin_left' => $marginLeft,
'margin_right' => $marginRight,
'margin_header' => $marginHeader,
'margin_footer' => $marginFooter,
'tempDir' => $mpdfTempDir,
'autoScriptToLang' => false,
'autoLanguageDetection' => false,
'autoArabic' => false,
'autoVietnamese' => false,
]);
$t_mpdf_init = microtime(true);
// For plain HTML (without explicit mPDF tags), set header/footer via API.
$headerHasTags = preg_match('/<\s*(htmlpageheader|sethtmlpageheader)\b/i', $rawHeaderHtml . $rawFirstPageHeaderHtml) === 1;
$footerHasTags = preg_match('/<\s*(htmlpagefooter|sethtmlpagefooter)\b/i', $rawFooterHtml . $rawLastPageFooterHtml) === 1;
if (! $headerHasTags) {
$plainHeader = $this->applyPdfTokens($rawHeaderHtml . $rawFirstPageHeaderHtml, $tokens);
if ($plainHeader !== '') {
$mpdf->SetHTMLHeader($plainHeader, 'O');
$mpdf->SetHTMLHeader($plainHeader, 'E');
}
}
if (! $footerHasTags) {
$plainFooter = $this->applyPdfTokens($rawFooterHtml, $tokens);
if ($plainFooter === '') {
$plainFooter = $this->applyPdfTokens($rawLastPageFooterHtml, $tokens);
}
if ($plainFooter !== '') {
$mpdf->SetHTMLFooter($plainFooter, 'O');
$mpdf->SetHTMLFooter($plainFooter, 'E');
}
}
$backgroundImage = trim((string) ($printSetting['page_background_image'] ?? ''));
if ($backgroundImage !== '') {
$absolutePath = FCPATH . ltrim(str_replace('\\', '/', $backgroundImage), '/');
if (is_file($absolutePath)) {
$mpdf->SetWatermarkImage($absolutePath, 1, [210, 297], 'F');
$mpdf->showWatermarkImage = true;
}
}
$watermarkType = (string) ($printSetting['watermark_type'] ?? 'none');
$watermarkAlpha = (float) ($printSetting['watermark_alpha'] ?? 0.12);
if ($watermarkType === 'text') {
$text = trim((string) ($printSetting['watermark_text'] ?? ''));
if ($text !== '') {
$mpdf->SetWatermarkText($text, $watermarkAlpha);
$mpdf->showWatermarkText = true;
}
} elseif ($watermarkType === 'image') {
$wmImage = trim((string) ($printSetting['watermark_image'] ?? ''));
if ($wmImage !== '') {
$absolutePath = FCPATH . ltrim(str_replace('\\', '/', $wmImage), '/');
if (is_file($absolutePath)) {
$mpdf->SetWatermarkImage($absolutePath, $watermarkAlpha);
$mpdf->showWatermarkImage = true;
}
}
}
if ($prefixHtml !== '') {
$mpdf->WriteHTML($prefixHtml, HTMLParserMode::HTML_HEADER_BUFFER);
}
$t_prefix = microtime(true);
$mpdf->WriteHTML($pdfHtml);
$t_body = microtime(true);
if ($suffixHtml !== '') {
$mpdf->WriteHTML($suffixHtml, HTMLParserMode::HTML_HEADER_BUFFER, false, false);
}
$t_suffix = microtime(true);
$invoiceCode = preg_replace('/[^A-Za-z0-9\-_]/', '_', (string) ($invoice->invoice_code ?? 'invoice'));
$fileName = 'Compiled_Report_' . $invoiceCode . '_' . $labType . '.pdf';
$pdfOutput = $mpdf->Output($fileName, 'S');
$t_output = microtime(true);
log_message('debug', sprintf(
'[PDF_TIMING printPdfCreate] invoice=%s lab=%s | new_Mpdf()=%.2fs | WriteHTML(prefix)=%.2fs | WriteHTML(body)=%.2fs | WriteHTML(suffix)=%.2fs | Output()=%.2fs | TOTAL=%.2fs | body_bytes=%d',
$invoiceId, $labType,
$t_mpdf_init - $t_start,
$t_prefix - $t_mpdf_init,
$t_body - $t_prefix,
$t_suffix - $t_body,
$t_output - $t_suffix,
$t_output - $t_start,
strlen($pdfHtml)
));
return $this->response
->setHeader('Content-Type', 'application/pdf')
->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
->setHeader('Pragma', 'no-cache')
->setHeader('Expires', '0')
->setHeader('Content-Disposition', 'inline; filename="' . $fileName . '"')
->setBody($pdfOutput);
}
public function reportFileList($invoiceId, $labType, $delete = 0)

View File

@ -638,6 +638,318 @@ class Template extends BaseController
return $this->response->setBody($html);
}
public function diagnosis_print_settings(int $modality = 3)
{
if ($resp = $this->requireAnyPermission(['template.pathology', 'template.ultrasound', 'template.xray', 'template.ct', 'template.mri', 'template.echo'])) {
return $resp;
}
$modalityList = [
5 => 'Pathology',
1 => 'Ultrasound',
2 => 'MRI',
3 => 'X-Ray',
4 => 'CT-Scan',
6 => 'Echo',
];
if (! in_array($modality, [1, 2, 3, 4, 5, 6], true)) {
$modality = 3;
}
$notice = '';
$noticeType = 'success';
if (! $this->db->tableExists('diagnosis_print_templates')) {
return view('Setting/Template/diagnosis_print_settings', [
'modality' => $modality,
'row' => [],
'templates' => [],
'selected_template_id' => 0,
'notice' => 'diagnosis_print_templates table not found. Please run migration.',
'notice_type' => 'danger',
'columns_ready' => false,
'modality_list' => $modalityList,
]);
}
$columnsReady = true;
$templateTable = $this->db->table('diagnosis_print_templates');
$templateFields = $this->db->getFieldNames('diagnosis_print_templates');
$hasSignatureImageColumn = in_array('signature_image', $templateFields, true);
// Backward-compatible: if this DB has not yet received the signature column,
// try to add it automatically so template-level signature upload can work.
if (! $hasSignatureImageColumn) {
try {
$this->db->query("ALTER TABLE diagnosis_print_templates ADD COLUMN signature_image VARCHAR(255) NULL AFTER watermark_image");
$templateFields = $this->db->getFieldNames('diagnosis_print_templates');
$hasSignatureImageColumn = in_array('signature_image', $templateFields, true);
} catch (\Throwable $e) {
// Keep page usable even when ALTER permission is not available.
$hasSignatureImageColumn = false;
}
}
$selectedTemplateId = (int) ($this->request->getGet('template_id') ?? 0);
$isNewTemplate = (int) ($this->request->getGet('new') ?? 0) === 1;
if (strtolower($this->request->getMethod()) === 'post') {
$modality = (int) ($this->request->getPost('modality') ?? $modality);
if (! in_array($modality, [1, 2, 3, 4, 5, 6], true)) {
$modality = 3;
}
$selectedTemplateId = (int) ($this->request->getPost('template_id') ?? 0);
if (! $columnsReady) {
$notice = 'Print settings columns are missing. Please run migration first.';
$noticeType = 'danger';
} else {
$existing = [];
if ($selectedTemplateId > 0) {
$existing = $templateTable
->where('id', $selectedTemplateId)
->where('modality', $modality)
->get(1)
->getRowArray() ?? [];
}
$templateName = trim((string) ($this->request->getPost('template_name') ?? ''));
if ($templateName === '') {
$templateName = 'Template ' . date('d-m-Y H:i');
}
$removeBackground = (int) ($this->request->getPost('remove_background') ?? 0) === 1;
$removeWatermarkImage = (int) ($this->request->getPost('remove_watermark_image') ?? 0) === 1;
$removeSignatureImage = (int) ($this->request->getPost('remove_signature_image') ?? 0) === 1;
$backgroundPath = (string) ($existing['page_background_image'] ?? '');
$watermarkImagePath = (string) ($existing['watermark_image'] ?? '');
$signatureImagePath = (string) ($existing['signature_image'] ?? '');
if ($removeBackground) {
$backgroundPath = '';
}
if ($removeWatermarkImage) {
$watermarkImagePath = '';
}
if ($removeSignatureImage) {
$signatureImagePath = '';
}
$bgUpload = $this->request->getFile('page_background_image');
if ($bgUpload && $bgUpload->isValid() && ! $bgUpload->hasMoved()) {
$ext = strtolower((string) $bgUpload->getExtension());
if (in_array($ext, ['png', 'jpg', 'jpeg', 'webp'], true)) {
$folder = rtrim(FCPATH, '/\\') . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'diagnosis_print_bg';
if (! is_dir($folder)) {
@mkdir($folder, 0777, true);
}
$newName = 'diag_tpl_bg_' . $modality . '_' . date('Ymd_His') . '.' . $ext;
$bgUpload->move($folder, $newName, true);
$backgroundPath = 'uploads/diagnosis_print_bg/' . $newName;
} else {
$notice = 'Background image must be PNG/JPG/JPEG/WEBP.';
$noticeType = 'danger';
}
}
$wmUpload = $this->request->getFile('watermark_image');
if ($wmUpload && $wmUpload->isValid() && ! $wmUpload->hasMoved()) {
$ext = strtolower((string) $wmUpload->getExtension());
if (in_array($ext, ['png', 'jpg', 'jpeg', 'webp'], true)) {
$folder = rtrim(FCPATH, '/\\') . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'diagnosis_print_wm';
if (! is_dir($folder)) {
@mkdir($folder, 0777, true);
}
$newName = 'diag_tpl_wm_' . $modality . '_' . date('Ymd_His') . '.' . $ext;
$wmUpload->move($folder, $newName, true);
$watermarkImagePath = 'uploads/diagnosis_print_wm/' . $newName;
} else {
$notice = 'Watermark image must be PNG/JPG/JPEG/WEBP.';
$noticeType = 'danger';
}
}
if ($hasSignatureImageColumn) {
$sigUpload = $this->request->getFile('signature_image');
if ($sigUpload && $sigUpload->isValid() && ! $sigUpload->hasMoved()) {
$ext = strtolower((string) $sigUpload->getExtension());
if (in_array($ext, ['png', 'jpg', 'jpeg', 'webp'], true)) {
$folder = rtrim(FCPATH, '/\\') . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'diagnosis_print_sign';
if (! is_dir($folder)) {
@mkdir($folder, 0777, true);
}
$newName = 'diag_tpl_sign_' . $modality . '_' . date('Ymd_His') . '.' . $ext;
$sigUpload->move($folder, $newName, true);
$signatureImagePath = 'uploads/diagnosis_print_sign/' . $newName;
} else {
$notice = 'Signature image must be PNG/JPG/JPEG/WEBP.';
$noticeType = 'danger';
}
}
}
$data = [
'modality' => $modality,
'template_name' => $templateName,
'page_size' => trim((string) ($this->request->getPost('page_size') ?? 'A4')) ?: 'A4',
'page_margin_top_cm' => $this->normalizeMarginCm($this->request->getPost('page_margin_top_cm'), 1.2),
'page_margin_bottom_cm' => $this->normalizeMarginCm($this->request->getPost('page_margin_bottom_cm'), 1.2),
'page_margin_left_cm' => $this->normalizeMarginCm($this->request->getPost('page_margin_left_cm'), 1.0),
'page_margin_right_cm' => $this->normalizeMarginCm($this->request->getPost('page_margin_right_cm'), 1.0),
'margin_header_cm' => $this->normalizeMarginCm($this->request->getPost('margin_header_cm'), 0.5),
'margin_footer_cm' => $this->normalizeMarginCm($this->request->getPost('margin_footer_cm'), 1.5),
'page_background_image' => $backgroundPath,
'watermark_type' => in_array((string) $this->request->getPost('watermark_type'), ['none', 'text', 'image'], true)
? (string) $this->request->getPost('watermark_type')
: 'none',
'watermark_text' => (string) ($this->request->getPost('watermark_text') ?? ''),
'watermark_image' => $watermarkImagePath,
'signature_image' => $signatureImagePath,
'watermark_alpha' => $this->normalizeWatermarkAlpha($this->request->getPost('watermark_alpha')),
'header_html' => (string) ($this->request->getPost('header_html') ?? ''),
'first_page_header_html' => (string) ($this->request->getPost('first_page_header_html') ?? ''),
'content_prefix_html' => (string) ($this->request->getPost('content_prefix_html') ?? ''),
'content_suffix_html' => (string) ($this->request->getPost('content_suffix_html') ?? ''),
'footer_html' => (string) ($this->request->getPost('footer_html') ?? ''),
'last_page_footer_html' => (string) ($this->request->getPost('last_page_footer_html') ?? ''),
'patient_info_html' => (string) ($this->request->getPost('patient_info_html') ?? ''),
'mpdf_prefix_html' => (string) ($this->request->getPost('mpdf_prefix_html') ?? ''),
'mpdf_suffix_html' => (string) ($this->request->getPost('mpdf_suffix_html') ?? ''),
'is_default' => (int) ($this->request->getPost('is_default') ?? 0) === 1 ? 1 : 0,
'status' => 1,
];
if (! $hasSignatureImageColumn) {
unset($data['signature_image']);
}
if ($noticeType !== 'danger') {
if ($data['is_default'] === 1) {
$templateTable
->where('modality', $modality)
->set('is_default', 0)
->update();
}
if (! empty($existing) && $selectedTemplateId > 0) {
$templateTable
->where('id', $selectedTemplateId)
->where('modality', $modality)
->update($data);
} else {
$templateTable->insert($data);
$selectedTemplateId = (int) $this->db->insertID();
}
$notice = (($modalityList[$modality] ?? 'Diagnosis') . ' print template saved.');
$noticeType = 'success';
}
}
if ($this->request->isAJAX()) {
return $this->response->setJSON([
'status' => $noticeType === 'success' ? 'success' : 'error',
'notice' => $notice,
'notice_type' => $noticeType,
'modality' => $modality,
'selected_template_id' => $selectedTemplateId,
'csrfName' => csrf_token(),
'csrfHash' => csrf_hash(),
]);
}
}
$templates = $templateTable
->select('id, template_name, is_default')
->where('modality', $modality)
->where('status', 1)
->orderBy('is_default', 'DESC')
->orderBy('template_name', 'ASC')
->get()
->getResultArray();
if (! $isNewTemplate && $selectedTemplateId <= 0 && ! empty($templates)) {
$selectedTemplateId = (int) ($templates[0]['id'] ?? 0);
}
$row = [];
if ($selectedTemplateId > 0) {
$row = $templateTable
->where('id', $selectedTemplateId)
->where('modality', $modality)
->get(1)
->getRowArray() ?? [];
}
return view('Setting/Template/diagnosis_print_settings', [
'modality' => $modality,
'row' => $row,
'templates' => $templates,
'selected_template_id' => $selectedTemplateId,
'notice' => $notice,
'notice_type' => $noticeType,
'columns_ready' => $columnsReady,
'modality_list' => $modalityList,
'has_signature_image_column' => $hasSignatureImageColumn,
'has_signature_image_column' => $hasSignatureImageColumn,
]);
}
private function normalizeWatermarkAlpha($rawValue): float
{
if ($rawValue === null || $rawValue === '') {
return 0.12;
}
$value = (float) $rawValue;
if (! is_finite($value)) {
$value = 0.12;
}
$value = max(0.01, min(1.0, $value));
return round($value, 2);
}
private function normalizeMarginCm($rawValue, float $default): float
{
if ($rawValue === null || $rawValue === '') {
return $default;
}
$value = (float) $rawValue;
if (! is_finite($value)) {
$value = $default;
}
// Allow large top margins like 6.1cm for heavy report headers.
$value = max(0.0, min(25.0, $value));
return round($value, 2);
}
private function normalizeMarginMm($rawValue, float $default): float
{
if ($rawValue === null || $rawValue === '') {
return $default;
}
$value = (float) $rawValue;
if (! is_finite($value)) {
$value = $default;
}
$value = max(0.0, min(60.0, $value));
return round($value, 2);
}
private function renderOptionTable(int $mstTestKey): string
{
$sql = "select *, if(option_bold=1,'Bold','') as option_bold_str

View File

@ -0,0 +1,131 @@
<?php
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class AddDiagnosisHeadPrintSettings extends Migration
{
private function hasDiagnosisHeadTable(): bool
{
$query = $this->db->query("SHOW TABLES LIKE 'diagnosis_head_name'");
return $query->getNumRows() > 0;
}
private function diagnosisHeadFields(): array
{
$fields = [];
$query = $this->db->query("SHOW COLUMNS FROM diagnosis_head_name");
foreach ($query->getResultArray() as $row) {
$fields[] = (string) ($row['Field'] ?? '');
}
return $fields;
}
public function up()
{
if (! $this->hasDiagnosisHeadTable()) {
return;
}
$fields = $this->diagnosisHeadFields();
if (! in_array('letter_margin_top', $fields, true)) {
$this->forge->addColumn('diagnosis_head_name', [
'letter_margin_top' => [
'type' => 'decimal',
'constraint' => '6,2',
'default' => '12.00',
'null' => true,
'after' => 'print_page_direct',
],
]);
}
if (! in_array('letter_margin_left', $fields, true)) {
$this->forge->addColumn('diagnosis_head_name', [
'letter_margin_left' => [
'type' => 'decimal',
'constraint' => '6,2',
'default' => '10.00',
'null' => true,
'after' => 'letter_margin_top',
],
]);
}
if (! in_array('letter_margin_right', $fields, true)) {
$this->forge->addColumn('diagnosis_head_name', [
'letter_margin_right' => [
'type' => 'decimal',
'constraint' => '6,2',
'default' => '10.00',
'null' => true,
'after' => 'letter_margin_left',
],
]);
}
if (! in_array('letter_margin_bottom', $fields, true)) {
$this->forge->addColumn('diagnosis_head_name', [
'letter_margin_bottom' => [
'type' => 'decimal',
'constraint' => '6,2',
'default' => '12.00',
'null' => true,
'after' => 'letter_margin_right',
],
]);
}
if (! in_array('plain_header_html', $fields, true)) {
$this->forge->addColumn('diagnosis_head_name', [
'plain_header_html' => [
'type' => 'longtext',
'null' => true,
'after' => 'letter_margin_bottom',
],
]);
}
if (! in_array('plain_background_image', $fields, true)) {
$this->forge->addColumn('diagnosis_head_name', [
'plain_background_image' => [
'type' => 'varchar',
'constraint' => 255,
'null' => true,
'after' => 'plain_header_html',
],
]);
}
}
public function down()
{
if (! $this->hasDiagnosisHeadTable()) {
return;
}
$fields = $this->diagnosisHeadFields();
if (in_array('plain_background_image', $fields, true)) {
$this->forge->dropColumn('diagnosis_head_name', 'plain_background_image');
}
if (in_array('plain_header_html', $fields, true)) {
$this->forge->dropColumn('diagnosis_head_name', 'plain_header_html');
}
if (in_array('letter_margin_bottom', $fields, true)) {
$this->forge->dropColumn('diagnosis_head_name', 'letter_margin_bottom');
}
if (in_array('letter_margin_right', $fields, true)) {
$this->forge->dropColumn('diagnosis_head_name', 'letter_margin_right');
}
if (in_array('letter_margin_left', $fields, true)) {
$this->forge->dropColumn('diagnosis_head_name', 'letter_margin_left');
}
if (in_array('letter_margin_top', $fields, true)) {
$this->forge->dropColumn('diagnosis_head_name', 'letter_margin_top');
}
}
}

View File

@ -0,0 +1,71 @@
<?php
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class AddDiagnosisHeadMpdfBlocks extends Migration
{
private function hasDiagnosisHeadTable(): bool
{
$query = $this->db->query("SHOW TABLES LIKE 'diagnosis_head_name'");
return $query->getNumRows() > 0;
}
private function diagnosisHeadFields(): array
{
$fields = [];
$query = $this->db->query("SHOW COLUMNS FROM diagnosis_head_name");
foreach ($query->getResultArray() as $row) {
$fields[] = (string) ($row['Field'] ?? '');
}
return $fields;
}
public function up()
{
if (! $this->hasDiagnosisHeadTable()) {
return;
}
$fields = $this->diagnosisHeadFields();
if (! in_array('mpdf_prefix_html', $fields, true)) {
$this->forge->addColumn('diagnosis_head_name', [
'mpdf_prefix_html' => [
'type' => 'longtext',
'null' => true,
'after' => 'plain_background_image',
],
]);
}
if (! in_array('mpdf_suffix_html', $fields, true)) {
$this->forge->addColumn('diagnosis_head_name', [
'mpdf_suffix_html' => [
'type' => 'longtext',
'null' => true,
'after' => 'mpdf_prefix_html',
],
]);
}
}
public function down()
{
if (! $this->hasDiagnosisHeadTable()) {
return;
}
$fields = $this->diagnosisHeadFields();
if (in_array('mpdf_suffix_html', $fields, true)) {
$this->forge->dropColumn('diagnosis_head_name', 'mpdf_suffix_html');
}
if (in_array('mpdf_prefix_html', $fields, true)) {
$this->forge->dropColumn('diagnosis_head_name', 'mpdf_prefix_html');
}
}
}

View File

@ -0,0 +1,57 @@
<?php
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class AddDiagnosisPatientInfoHtml extends Migration
{
private function hasDiagnosisHeadTable(): bool
{
$query = $this->db->query("SHOW TABLES LIKE 'diagnosis_head_name'");
return $query->getNumRows() > 0;
}
private function diagnosisHeadFields(): array
{
$fields = [];
$query = $this->db->query("SHOW COLUMNS FROM diagnosis_head_name");
foreach ($query->getResultArray() as $row) {
$fields[] = (string) ($row['Field'] ?? '');
}
return $fields;
}
public function up()
{
if (! $this->hasDiagnosisHeadTable()) {
return;
}
$fields = $this->diagnosisHeadFields();
if (! in_array('patient_info_html', $fields, true)) {
$this->forge->addColumn('diagnosis_head_name', [
'patient_info_html' => [
'type' => 'longtext',
'null' => true,
'after' => 'mpdf_suffix_html',
],
]);
}
}
public function down()
{
if (! $this->hasDiagnosisHeadTable()) {
return;
}
$fields = $this->diagnosisHeadFields();
if (in_array('patient_info_html', $fields, true)) {
$this->forge->dropColumn('diagnosis_head_name', 'patient_info_html');
}
}
}

View File

@ -0,0 +1,128 @@
<?php
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class CreateDiagnosisPrintTemplates extends Migration
{
private function hasTemplateTable(): bool
{
$query = $this->db->query("SHOW TABLES LIKE 'diagnosis_print_templates'");
return $query->getNumRows() > 0;
}
public function up()
{
if ($this->hasTemplateTable()) {
return;
}
$this->forge->addField([
'id' => [
'type' => 'INT',
'constraint' => 11,
'unsigned' => true,
'auto_increment' => true,
],
'modality' => [
'type' => 'INT',
'constraint' => 11,
'default' => 3,
],
'template_name' => [
'type' => 'VARCHAR',
'constraint' => 120,
],
'is_default' => [
'type' => 'TINYINT',
'constraint' => 1,
'default' => 0,
],
'status' => [
'type' => 'TINYINT',
'constraint' => 1,
'default' => 1,
],
'page_size' => [
'type' => 'VARCHAR',
'constraint' => 20,
'default' => 'A4',
],
'page_background_image' => [
'type' => 'VARCHAR',
'constraint' => 255,
'null' => true,
],
'watermark_type' => [
'type' => 'VARCHAR',
'constraint' => 10,
'default' => 'none',
],
'watermark_text' => [
'type' => 'VARCHAR',
'constraint' => 255,
'null' => true,
],
'watermark_image' => [
'type' => 'VARCHAR',
'constraint' => 255,
'null' => true,
],
'watermark_alpha' => [
'type' => 'DECIMAL',
'constraint' => '4,2',
'default' => '0.12',
],
'header_html' => [
'type' => 'LONGTEXT',
'null' => true,
],
'first_page_header_html' => [
'type' => 'LONGTEXT',
'null' => true,
],
'content_prefix_html' => [
'type' => 'LONGTEXT',
'null' => true,
],
'content_suffix_html' => [
'type' => 'LONGTEXT',
'null' => true,
],
'footer_html' => [
'type' => 'LONGTEXT',
'null' => true,
],
'last_page_footer_html' => [
'type' => 'LONGTEXT',
'null' => true,
],
'patient_info_html' => [
'type' => 'LONGTEXT',
'null' => true,
],
'mpdf_prefix_html' => [
'type' => 'LONGTEXT',
'null' => true,
],
'mpdf_suffix_html' => [
'type' => 'LONGTEXT',
'null' => true,
],
'created_at DATETIME DEFAULT CURRENT_TIMESTAMP',
'updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP',
]);
$this->forge->addKey('id', true);
$this->forge->addKey(['modality', 'status']);
$this->forge->createTable('diagnosis_print_templates', true, ['ENGINE' => 'InnoDB', 'DEFAULT CHARSET' => 'utf8mb4']);
}
public function down()
{
if ($this->hasTemplateTable()) {
$this->forge->dropTable('diagnosis_print_templates', true);
}
}
}

View File

@ -0,0 +1,117 @@
<?php
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class AddDiagnosisTemplateMarginsCm extends Migration
{
private function hasTemplateTable(): bool
{
$query = $this->db->query("SHOW TABLES LIKE 'diagnosis_print_templates'");
return $query->getNumRows() > 0;
}
private function templateFields(): array
{
$fields = [];
$query = $this->db->query("SHOW COLUMNS FROM diagnosis_print_templates");
foreach ($query->getResultArray() as $row) {
$fields[] = (string) ($row['Field'] ?? '');
}
return $fields;
}
public function up()
{
if (! $this->hasTemplateTable()) {
return;
}
$fields = $this->templateFields();
$add = [];
if (! in_array('page_margin_top_cm', $fields, true)) {
$add['page_margin_top_cm'] = [
'type' => 'decimal',
'constraint' => '6,2',
'null' => true,
'default' => '1.20',
'after' => 'page_size',
];
}
if (! in_array('page_margin_bottom_cm', $fields, true)) {
$add['page_margin_bottom_cm'] = [
'type' => 'decimal',
'constraint' => '6,2',
'null' => true,
'default' => '1.20',
'after' => 'page_margin_top_cm',
];
}
if (! in_array('page_margin_left_cm', $fields, true)) {
$add['page_margin_left_cm'] = [
'type' => 'decimal',
'constraint' => '6,2',
'null' => true,
'default' => '1.00',
'after' => 'page_margin_bottom_cm',
];
}
if (! in_array('page_margin_right_cm', $fields, true)) {
$add['page_margin_right_cm'] = [
'type' => 'decimal',
'constraint' => '6,2',
'null' => true,
'default' => '1.00',
'after' => 'page_margin_left_cm',
];
}
if (! in_array('margin_header_cm', $fields, true)) {
$add['margin_header_cm'] = [
'type' => 'decimal',
'constraint' => '6,2',
'null' => true,
'default' => '0.50',
'after' => 'page_margin_right_cm',
];
}
if (! in_array('margin_footer_cm', $fields, true)) {
$add['margin_footer_cm'] = [
'type' => 'decimal',
'constraint' => '6,2',
'null' => true,
'default' => '1.50',
'after' => 'margin_header_cm',
];
}
if (! empty($add)) {
$this->forge->addColumn('diagnosis_print_templates', $add);
}
}
public function down()
{
if (! $this->hasTemplateTable()) {
return;
}
$fields = $this->templateFields();
$dropList = [
'page_margin_top_cm',
'page_margin_bottom_cm',
'page_margin_left_cm',
'page_margin_right_cm',
'margin_header_cm',
'margin_footer_cm',
];
foreach ($dropList as $field) {
if (in_array($field, $fields, true)) {
$this->forge->dropColumn('diagnosis_print_templates', $field);
}
}
}
}

View File

@ -0,0 +1,362 @@
<?php
$row = $row ?? [];
$notice = (string) ($notice ?? '');
$noticeType = (string) ($notice_type ?? 'success');
$modality = (int) ($modality ?? 3);
$columnsReady = (bool) ($columns_ready ?? false);
$modalityList = $modality_list ?? [];
$templates = $templates ?? [];
$selectedTemplateId = (int) ($selected_template_id ?? 0);
$hasSignatureImageColumn = (bool) ($has_signature_image_column ?? false);
$modalityLabel = (string) ($modalityList[$modality] ?? 'Diagnosis');
$panelTitle = $modalityLabel . ' Print Template';
?>
<section class="content">
<div class="card border-0 shadow-sm">
<div class="card-header bg-light d-flex justify-content-between align-items-center">
<h6 class="mb-0"><?= esc($panelTitle) ?> Settings</h6>
<a class="btn btn-sm btn-outline-secondary" href="javascript:load_form_div('<?= base_url('setting/template/diagnosis_print_settings/' . $modality) ?>','maindiv','<?= esc($panelTitle) ?>');">Reset</a>
</div>
<div class="card-body">
<div id="diag_print_notice">
<?php if ($notice !== ''): ?>
<div class="alert alert-<?= esc($noticeType) ?> py-2 mb-3"><?= esc($notice) ?></div>
<?php endif; ?>
</div>
<?php if (! $columnsReady): ?>
<div class="alert alert-warning py-2 mb-3">
Required table/columns are missing.
Run migration: <b>php spark migrate</b>
</div>
<?php endif; ?>
<form method="post" action="<?= base_url('setting/template/diagnosis_print_settings/' . $modality) ?>" enctype="multipart/form-data" id="diag_print_setting_form">
<?= csrf_field() ?>
<input type="hidden" name="template_id" value="<?= (int) ($row['id'] ?? $selectedTemplateId) ?>">
<div class="row g-2">
<div class="col-md-3">
<label class="form-label small">Modality</label>
<select class="form-select form-select-sm" name="modality" onchange="changeDiagnosisModality(this.value)">
<?php foreach ($modalityList as $key => $label): ?>
<option value="<?= (int) $key ?>" <?= (int) $key === $modality ? 'selected' : '' ?>><?= esc($label) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-5">
<label class="form-label small">Template Name</label>
<input type="text" class="form-control form-control-sm" name="template_name" value="<?= esc((string) ($row['template_name'] ?? '')) ?>" placeholder="e.g. Standard <?= esc($modalityLabel) ?> Template" required>
</div>
<div class="col-md-4 d-flex align-items-end">
<div class="form-check mb-1">
<input class="form-check-input" type="checkbox" name="is_default" id="is_default" value="1" <?= ((int) ($row['is_default'] ?? 0) === 1) ? 'checked' : '' ?>>
<label class="form-check-label" for="is_default">Set as default template for this modality</label>
</div>
</div>
</div>
<div class="mt-2 mb-2">
<label class="form-label small mb-1">Template List</label>
<div class="d-flex flex-wrap gap-2">
<?php foreach ($templates as $tpl): ?>
<?php
$tplId = (int) ($tpl['id'] ?? 0);
$tplName = (string) ($tpl['template_name'] ?? ('Template ' . $tplId));
$activeClass = ($tplId === (int) ($row['id'] ?? $selectedTemplateId)) ? 'btn-primary' : 'btn-outline-primary';
?>
<a class="btn btn-sm <?= $activeClass ?>" href="javascript:load_form_div('<?= base_url('setting/template/diagnosis_print_settings/' . $modality . '?template_id=' . $tplId) ?>','maindiv','<?= esc($panelTitle) ?>');"><?= esc($tplName) ?></a>
<?php endforeach; ?>
<a class="btn btn-sm btn-outline-success" href="javascript:load_form_div('<?= base_url('setting/template/diagnosis_print_settings/' . $modality . '?new=1') ?>','maindiv','<?= esc($panelTitle) ?>');">+ New Template</a>
</div>
</div>
<hr>
<h6 class="mb-2">1. Page Size, Page Background Image, Page Watermark</h6>
<div class="alert alert-light border py-2 small mb-2">
Legacy mapping: margin-top/bottom/left/right and margin-header/footer from old @page CSS are now configurable here in cm.
Example old values: top 6.1, bottom 2.5, left 0.7, right 0.7, header 0.5, footer 1.5.
</div>
<div class="row g-2 mb-3">
<div class="col-md-2">
<label class="form-label small">Page Size</label>
<select class="form-select form-select-sm" name="page_size">
<?php $pageSize = strtoupper((string) ($row['page_size'] ?? 'A4')); ?>
<option value="A4" <?= $pageSize === 'A4' ? 'selected' : '' ?>>A4</option>
<option value="A4-L" <?= $pageSize === 'A4-L' ? 'selected' : '' ?>>A4 Landscape</option>
<option value="LETTER" <?= $pageSize === 'LETTER' ? 'selected' : '' ?>>Letter</option>
<option value="LEGAL" <?= $pageSize === 'LEGAL' ? 'selected' : '' ?>>Legal</option>
</select>
</div>
<div class="col-md-2">
<label class="form-label small">Top Margin (cm)</label>
<input type="number" step="0.1" min="0" max="25" class="form-control form-control-sm" name="page_margin_top_cm" value="<?= esc((string) ($row['page_margin_top_cm'] ?? '1.2')) ?>">
</div>
<div class="col-md-2">
<label class="form-label small">Bottom Margin (cm)</label>
<input type="number" step="0.1" min="0" max="25" class="form-control form-control-sm" name="page_margin_bottom_cm" value="<?= esc((string) ($row['page_margin_bottom_cm'] ?? '1.2')) ?>">
</div>
<div class="col-md-2">
<label class="form-label small">Left Margin (cm)</label>
<input type="number" step="0.1" min="0" max="25" class="form-control form-control-sm" name="page_margin_left_cm" value="<?= esc((string) ($row['page_margin_left_cm'] ?? '1.0')) ?>">
</div>
<div class="col-md-2">
<label class="form-label small">Right Margin (cm)</label>
<input type="number" step="0.1" min="0" max="25" class="form-control form-control-sm" name="page_margin_right_cm" value="<?= esc((string) ($row['page_margin_right_cm'] ?? '1.0')) ?>">
</div>
<div class="col-md-2">
<label class="form-label small">Header Margin (cm)</label>
<input type="number" step="0.1" min="0" max="25" class="form-control form-control-sm" name="margin_header_cm" value="<?= esc((string) ($row['margin_header_cm'] ?? '0.5')) ?>">
</div>
<div class="col-md-2">
<label class="form-label small">Footer Margin (cm)</label>
<input type="number" step="0.1" min="0" max="25" class="form-control form-control-sm" name="margin_footer_cm" value="<?= esc((string) ($row['margin_footer_cm'] ?? '1.5')) ?>">
</div>
<div class="col-md-4">
<label class="form-label small">Page Background Image</label>
<input type="file" class="form-control form-control-sm" name="page_background_image" accept=".png,.jpg,.jpeg,.webp,image/png,image/jpeg,image/webp">
<?php if (! empty($row['page_background_image'])): ?>
<small class="text-muted d-block mt-1">Current: <?= esc((string) $row['page_background_image']) ?></small>
<?php endif; ?>
</div>
<div class="col-md-2 d-flex align-items-end">
<div class="form-check mb-1">
<input class="form-check-input" type="checkbox" name="remove_background" id="remove_background" value="1">
<label class="form-check-label" for="remove_background">Remove BG</label>
</div>
</div>
<div class="col-md-2">
<label class="form-label small">Watermark Type</label>
<?php $watermarkType = (string) ($row['watermark_type'] ?? 'none'); ?>
<select class="form-select form-select-sm" name="watermark_type">
<option value="none" <?= $watermarkType === 'none' ? 'selected' : '' ?>>None</option>
<option value="text" <?= $watermarkType === 'text' ? 'selected' : '' ?>>Text</option>
<option value="image" <?= $watermarkType === 'image' ? 'selected' : '' ?>>Image</option>
</select>
</div>
<div class="col-md-2">
<label class="form-label small">Watermark Alpha</label>
<input type="number" step="0.01" min="0.01" max="1" class="form-control form-control-sm" name="watermark_alpha" value="<?= esc((string) ($row['watermark_alpha'] ?? '0.12')) ?>">
</div>
<div class="col-md-6">
<label class="form-label small">Watermark Text</label>
<input type="text" class="form-control form-control-sm" name="watermark_text" value="<?= esc((string) ($row['watermark_text'] ?? '')) ?>" placeholder="CONFIDENTIAL">
</div>
<div class="col-md-4">
<label class="form-label small">Watermark Image</label>
<input type="file" class="form-control form-control-sm" name="watermark_image" accept=".png,.jpg,.jpeg,.webp,image/png,image/jpeg,image/webp">
<?php if (! empty($row['watermark_image'])): ?>
<small class="text-muted d-block mt-1">Current: <?= esc((string) $row['watermark_image']) ?></small>
<?php endif; ?>
</div>
<div class="col-md-2 d-flex align-items-end">
<div class="form-check mb-1">
<input class="form-check-input" type="checkbox" name="remove_watermark_image" id="remove_watermark_image" value="1">
<label class="form-check-label" for="remove_watermark_image">Remove WM</label>
</div>
</div>
<div class="col-md-4">
<label class="form-label small">Signature Image (for {{signature_image_url}})</label>
<input type="file" class="form-control form-control-sm" name="signature_image" accept=".png,.jpg,.jpeg,.webp,image/png,image/jpeg,image/webp" <?= $hasSignatureImageColumn ? '' : 'disabled' ?>>
<?php if (! $hasSignatureImageColumn): ?>
<small class="text-danger d-block mt-1">Signature column missing in DB. Please refresh this page once.</small>
<?php elseif (! empty($row['signature_image'])): ?>
<small class="text-muted d-block mt-1">Current: <?= esc((string) $row['signature_image']) ?></small>
<?php endif; ?>
</div>
<div class="col-md-2 d-flex align-items-end">
<div class="form-check mb-1">
<input class="form-check-input" type="checkbox" name="remove_signature_image" id="remove_signature_image" value="1" <?= $hasSignatureImageColumn ? '' : 'disabled' ?>>
<label class="form-check-label" for="remove_signature_image">Remove Sign</label>
</div>
</div>
</div>
<h6 class="mb-2">2. Header</h6>
<div class="row g-2">
<div class="col-12">
<label class="form-label small">Header HTML / mPDF tags</label>
<textarea class="form-control" name="header_html" rows="6" style="font-family:Consolas,Monaco,monospace;"><?= esc((string) ($row['header_html'] ?? '')) ?></textarea>
</div>
</div>
<h6 class="mb-2 mt-3">3. Header for First Page</h6>
<div class="row g-2">
<div class="col-12">
<label class="form-label small">First Page Header HTML / mPDF tags</label>
<textarea class="form-control" name="first_page_header_html" rows="5" style="font-family:Consolas,Monaco,monospace;"><?= esc((string) ($row['first_page_header_html'] ?? '')) ?></textarea>
</div>
</div>
<hr>
<div class="d-flex align-items-center justify-content-between mb-2">
<h6 class="mb-0">4. Content Part</h6>
<button type="button" class="btn btn-sm btn-outline-secondary" data-bs-toggle="modal" data-bs-target="#pdfTemplateTipsModal" title="Template Help Tips">
? Help Tips
</button>
</div>
<div class="alert alert-secondary py-2 small mb-2">
Main content customization with token support. Click <b>Help Tips</b> for available tokens and mPDF constants/snippets.
</div>
<div class="row g-2">
<div class="col-12">
<label class="form-label small">Patient Info HTML Template (tokens supported)</label>
<textarea class="form-control" name="patient_info_html" rows="8" style="font-family:Consolas,Monaco,monospace;"><?= esc((string) ($row['patient_info_html'] ?? '')) ?></textarea>
</div>
<div class="col-12">
<label class="form-label small">Content Prefix HTML (before report body)</label>
<textarea class="form-control" name="content_prefix_html" rows="4" style="font-family:Consolas,Monaco,monospace;"><?= esc((string) ($row['content_prefix_html'] ?? '')) ?></textarea>
</div>
<div class="col-12">
<label class="form-label small">Content Suffix HTML (after report body)</label>
<textarea class="form-control" name="content_suffix_html" rows="4" style="font-family:Consolas,Monaco,monospace;"><?= esc((string) ($row['content_suffix_html'] ?? '')) ?></textarea>
</div>
</div>
<hr>
<h6 class="mb-2">5. Footer</h6>
<div class="row g-2">
<div class="col-12">
<label class="form-label small">Footer HTML / mPDF tags</label>
<textarea class="form-control" name="footer_html" rows="5" style="font-family:Consolas,Monaco,monospace;"><?= esc((string) ($row['footer_html'] ?? '')) ?></textarea>
</div>
</div>
<h6 class="mb-2 mt-3">6. Footer for Last Page</h6>
<div class="row g-2">
<div class="col-12">
<label class="form-label small">Last Page Footer HTML / mPDF tags</label>
<textarea class="form-control" name="last_page_footer_html" rows="5" style="font-family:Consolas,Monaco,monospace;"><?= esc((string) ($row['last_page_footer_html'] ?? '')) ?></textarea>
</div>
</div>
<h6 class="mb-2 mt-3">Advanced mPDF Prefix/Suffix (Optional)</h6>
<div class="alert alert-info py-2 small mb-2">
Optional raw blocks. Use if you need extra mPDF tags beyond the 6 template sections.
</div>
<div class="row g-2">
<div class="col-12">
<label class="form-label small">mPDF Prefix HTML (before content)</label>
<textarea class="form-control" name="mpdf_prefix_html" rows="8" style="font-family:Consolas,Monaco,monospace;"><?= esc((string) ($row['mpdf_prefix_html'] ?? '')) ?></textarea>
</div>
<div class="col-12">
<label class="form-label small">mPDF Suffix HTML (after content)</label>
<textarea class="form-control" name="mpdf_suffix_html" rows="5" style="font-family:Consolas,Monaco,monospace;"><?= esc((string) ($row['mpdf_suffix_html'] ?? '')) ?></textarea>
</div>
</div>
<div class="mt-3 d-flex gap-2">
<button type="submit" class="btn btn-primary btn-sm" <?= ! $columnsReady ? 'disabled' : '' ?>>Save Settings</button>
</div>
<div class="modal fade" id="pdfTemplateTipsModal" tabindex="-1" aria-labelledby="pdfTemplateTipsModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h6 class="modal-title" id="pdfTemplateTipsModalLabel">PDF Template Help Tips</h6>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body small">
<p class="mb-2"><b>Patient Tokens</b></p>
<p style="font-family:Consolas,Monaco,monospace; line-height:1.8;">
{{invoice_code}}, {{patient_name}}, {{relative_type}}, {{relative_name}}, {{relative}}, {{age}}, {{gender}}, {{age_sex}}, {{uhid}}, {{collected_time}}, {{reported_time}}, {{printed_time}}, {{report_title}}
</p>
<p style="font-family:Consolas,Monaco,monospace; line-height:1.8;">
{{hospital_logo_url}}, {{signature_image_url}}, {{doctor_name}}, {{doctor_education}}, {{technician_name}}
</p>
<p class="mb-1" style="font-family:Consolas,Monaco,monospace;">Signature image usage: &lt;img src="{{signature_image_url}}" width="100px" /&gt;</p>
<p class="mb-2 mt-3"><b>mPDF Constants / Tags (use in Prefix or Suffix)</b></p>
<p class="mb-1" style="font-family:Consolas,Monaco,monospace;">Page No: {PAGENO}</p>
<p class="mb-1" style="font-family:Consolas,Monaco,monospace;">Total Pages: {nbpg}</p>
<p class="mb-1" style="font-family:Consolas,Monaco,monospace;">Page Break: &lt;pagebreak /&gt;</p>
<p class="mb-1" style="font-family:Consolas,Monaco,monospace;">Page Size (A4): &lt;style&gt;@page { size: A4; }&lt;/style&gt;</p>
<p class="mb-1" style="font-family:Consolas,Monaco,monospace;">Page Size (Landscape): &lt;style&gt;@page { size: A4-L; }&lt;/style&gt;</p>
<p class="mb-1" style="font-family:Consolas,Monaco,monospace;">Header Tag: &lt;htmlpageheader name="myHeader"&gt;...&lt;/htmlpageheader&gt;</p>
<p class="mb-1" style="font-family:Consolas,Monaco,monospace;">Footer Tag: &lt;htmlpagefooter name="myFooter"&gt;...&lt;/htmlpagefooter&gt;</p>
<p class="mb-2 mt-3"><b>Quick Footer Example</b></p>
<pre class="bg-light border rounded p-2 mb-0" style="font-family:Consolas,Monaco,monospace; white-space:pre-wrap;">&lt;htmlpagefooter name="myFooter" style="display:none"&gt;
&lt;div style="text-align:right; font-size:10px;"&gt;Page {PAGENO} of {nbpg}&lt;/div&gt;
&lt;/htmlpagefooter&gt;
&lt;sethtmlpagefooter name="myFooter" value="on" /&gt;</pre>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
</section>
<script>
(function () {
var form = document.getElementById('diag_print_setting_form');
var noticeBox = document.getElementById('diag_print_notice');
function showNotice(type, message) {
if (!noticeBox) {
return;
}
var safeType = (type === 'success') ? 'success' : 'danger';
var safeText = String(message || '').replace(/</g, '&lt;').replace(/>/g, '&gt;');
noticeBox.innerHTML = '<div class="alert alert-' + safeType + ' py-2 mb-3">' + safeText + '</div>';
}
window.changeDiagnosisModality = function (value) {
var selected = parseInt(value || 0, 10);
if (!selected) {
return;
}
var url = '<?= base_url('setting/template/diagnosis_print_settings') ?>/' + selected;
if (typeof load_form_div === 'function') {
load_form_div(url, 'maindiv', '<?= esc($panelTitle) ?>');
} else {
window.location.href = url;
}
};
if (form) {
form.addEventListener('submit', function (event) {
event.preventDefault();
var submitBtn = form.querySelector('button[type="submit"]');
if (submitBtn) {
submitBtn.disabled = true;
}
fetch(form.action, {
method: 'POST',
body: new FormData(form),
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(function (response) {
return response.json();
})
.then(function (result) {
var status = (result && result.notice_type) ? result.notice_type : 'danger';
var message = (result && result.notice) ? result.notice : 'Unable to save settings.';
showNotice(status, message);
if (result && result.csrfName && result.csrfHash) {
var csrfInput = form.querySelector('input[name="' + result.csrfName + '"]');
if (csrfInput) {
csrfInput.value = result.csrfHash;
}
}
})
.catch(function (error) {
showNotice('danger', 'Save failed: ' + error.message);
})
.finally(function () {
if (submitBtn) {
submitBtn.disabled = false;
}
});
});
}
})();
</script>

View File

@ -0,0 +1,39 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<style>
body { font-family: Arial, sans-serif; color: #111; font-size: 12px; margin: 0; padding: 0; }
.section-title { font-size: 20px; font-weight: 700; text-align: center; margin: 4px 0 8px; }
table.meta-table { width: 100%; border-collapse: collapse; margin-bottom: 8px; }
table.meta-table td { vertical-align: top; padding: 0 4px; font-size: 11px; }
.label { font-weight: 700; display: inline-block; min-width: 72px; }
.divider { border-top: 1px solid #999; margin: 6px 0 8px; }
.report-content { font-size: 12px; line-height: 1.45; }
.plain-header { margin-bottom: 8px; }
</style>
</head>
<body>
<?php
$patientName = trim((string) ($patient->p_fname ?? ''));
$relativeName = trim((string) ($patient->p_relative ?? ''));
$relativeText = trim((string) ($patient->p_rname ?? ''));
$plainHeaderHtml = (string) ($printSetting['plain_header_html'] ?? '');
$title = trim((string) ($itemType->group_desc ?? 'Compiled Report'));
?>
<?php if (($isPlainPaper ?? false) && trim($plainHeaderHtml) !== ''): ?>
<div class="plain-header"><?= $plainHeaderHtml ?></div>
<?php endif; ?>
<div class="section-title"><?= esc($title) ?></div>
<?php $patientInfoHtml = (string) ($patientInfoHtml ?? ''); ?>
<?php if ($patientInfoHtml !== ''): ?>
<?= $patientInfoHtml ?>
<?php endif; ?>
<div class="divider"></div>
<div class="report-content"><?= $reportHtml ?? '' ?></div>
</body>
</html>

View File

@ -0,0 +1,42 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<style>
body { font-family: Arial, sans-serif; color: #111; font-size: 12px; margin: 0; padding: 0; }
.letterhead { margin-bottom: 8px; }
.letterhead-title { font-size: 24px; font-weight: 700; line-height: 1.1; }
.letterhead-sub { font-size: 11px; color: #222; line-height: 1.25; }
.section-title { font-size: 20px; font-weight: 700; text-align: center; margin: 4px 0 8px; }
table.meta-table { width: 100%; border-collapse: collapse; margin-bottom: 8px; }
table.meta-table td { vertical-align: top; padding: 0 4px; font-size: 11px; }
.label { font-weight: 700; display: inline-block; min-width: 72px; }
.divider { border-top: 1px solid #999; margin: 6px 0 8px; }
.report-content { font-size: 12px; line-height: 1.45; }
.plain-header { margin-bottom: 8px; }
</style>
</head>
<body>
<?php
$patientName = (string) ($patientName ?? trim((string) ($report->p_fname ?? '')));
$relativeName = (string) ($relativeName ?? trim((string) ($report->p_relative ?? '')));
$relativeText = (string) ($relativeText ?? trim((string) ($report->p_rname ?? '')));
$repoTitle = (string) ($repoTitle ?? trim((string) ($report->report_name ?? $report->repo_title ?? $report->RepoGrp ?? 'Report')));
$plainHeaderHtml = (string) ($printSetting['plain_header_html'] ?? '');
$patientInfoHtml = (string) ($patientInfoHtml ?? '');
?>
<?php if (($isPlainPaper ?? false) && trim($plainHeaderHtml) !== ''): ?>
<div class="plain-header"><?= $plainHeaderHtml ?></div>
<?php endif; ?>
<?php if ($patientInfoHtml !== ''): ?>
<?= $patientInfoHtml ?>
<?php endif; ?>
<div class="divider"></div>
<div class="section-title"><?= esc($repoTitle) ?></div>
<div class="report-content"><?= $reportHtml ?? '' ?></div>
</body>
</html>