<?php
namespace App\Service;
use Mpdf\Mpdf;
use Mpdf\Output\Destination;
use Symfony\Component\HttpFoundation\Response;
class MasterListPdf
{
// Column widths (mm) for the registrations table — sums to ~178mm inside
// the 180mm content area (matches the original layout's proportions).
private const COL_WIDTHS = [8, 40, 65, 23, 26, 8, 8];
private const COL_HEADERS = ['Seat', 'Student', 'Exam', 'JSE ID', 'Contact #', 'Dur', 'ID?'];
private const COL_MAXLEN = [4, 20, 33, 14, 14, 14, 4];
/**
* Generates the master list.
*
* - If $mpdf is supplied, a page is appended to that existing document
* (used by CoverSheetService when building one combined session PDF)
* and this method returns null.
* - If $mpdf is null, a standalone document is created and either its
* bytes are returned, or (when called directly from the browser) a
* Symfony Response with the PDF attached is returned.
*/
public static function generate($masterListData = null, $testSessionsService, $studentsRepository, $vendorsRepository, ?Mpdf $mpdf = null)
{
$toBrowser = false;
if ($masterListData === null) {
$toBrowser = true;
$testSessionId = $_REQUEST['sessionId'];
$masterListData = $testSessionsService->getMasterListData($testSessionId);
}
$rawName = $masterListData['testSessionName'];
// Convert date format: "Israel - 2026-05-03 - 4:00PM - Israel by Appointment"
// to: "Israel - Sun May 3, 2026 - 4:00PM - Israel by Appointment"
$testSessionDescriptor = preg_replace_callback(
'/(\d{4}-\d{2}-\d{2})/',
function ($matches) {
$date = new \DateTime($matches[1]);
return $date->format('D M j, Y');
},
$rawName
);
$ownDoc = ($mpdf === null);
if ($ownDoc) {
$mpdf = self::makeMpdf();
}
$mpdf->SetTitle('JSE Session Master List - ' . $testSessionDescriptor);
$mpdf->SetSubject('JSE Session Master List Subject - ' . $testSessionDescriptor);
$mpdf->SetKeywords('jsematerials, jse, jewish subject exams');
$mpdf->SetCreator('JSE-IL-version');
$mpdf->SetAuthor('Yaffie Goldberg');
$logoPath = realpath(__DIR__ . '/../../assets/images/jseLogo.png');
$mpdf->SetHTMLHeader(self::masterListHeaderHtml($logoPath, $testSessionDescriptor));
$mpdf->AddPageByArray([
'orientation' => 'P',
'margin-left' => 15,
'margin-right' => 15,
'margin-top' => 35,
'margin-bottom' => 25,
'margin-header' => 3,
'margin-footer' => 10,
'resetpagenum' => 1,
]);
$mpdf->SetHTMLFooter(
'<div style="text-align:center; font-family:Arial; font-size:8pt; border-top:0.2mm solid #999999; padding-top:1mm;">'
. 'Page {PAGENO} of {nbpg}'
. '</div>'
);
$totalStudents = $masterListData['totalStudents'];
$html = '<div style="font-family:Arial; font-size:10pt; margin-top:3mm; margin-bottom:3mm;">'
. 'Total Students registered for test session: <b>' . htmlspecialchars((string) $totalStudents) . '</b>'
. '</div>';
foreach ($masterListData['sections'] as $section) {
$sectionName = htmlspecialchars((string) $section['sectionName']);
$html .= '<div style="font-family:Arial; font-size:10pt; font-weight:bold; margin-top:2mm; margin-bottom:1mm;">Section ' . $sectionName . '</div>';
$rows = [];
foreach ($section['registrations'] as $registration) {
$seat = $registration['seat'] ?? 'X';
$student = $registration['student'];
$fullStudent = $studentsRepository->find($student['id']);
$studentFirstName = $fullStudent->getFirstName();
$studentLastName = $fullStudent->getLastName();
$studentName = "$studentLastName, $studentFirstName";
$cd_required = $registration['exam']['cd_required'];
$vendorName = $vendorsRepository->getVendorName($registration['exam']['vendor']);
$examSubject = $vendorName . " - " . $registration['exam']['name'];
$rows[] = [
(string) $seat,
(string) $studentName,
(string) $examSubject,
(string) ($fullStudent->getJseId() ?? ''),
(string) ($fullStudent->getPhone() ?? ''),
(string) ($registration['exam']['duration'] ?? ''),
];
if ($fullStudent->getAccommodations()) {
$rows[] = [
'', '', '',
(string) $fullStudent->getAccommodations(),
(string) ($fullStudent->getAccommodationsApproved() ? "Approved" : "(not approved)"),
'',
];
}
if ($cd_required == 1) {
$rows[] = ['', '', 'THIS EXAM NEEDS A CD','', '', ''];
}
}
$html .= self::sectionTableHtml($rows);
}
$mpdf->WriteHTML($html);
if (!$ownDoc) {
// Page(s) appended to the shared document; caller continues.
return null;
}
$ret = $mpdf->Output('', Destination::STRING_RETURN);
if ($toBrowser) {
$testSessionName = $testSessionsService->generateTestSessionName($testSessionId, true);
$response = new Response($ret);
$response->headers->set('Content-Type', 'application/pdf');
$response->headers->set('Content-Disposition', 'attachment; filename="' . $testSessionName . '.pdf"');
$response->headers->set('Content-Length', (string) strlen($ret));
return $response;
}
return $ret;
}
/**
* Shared mPDF configuration for a standalone master list document.
*/
private static function makeMpdf(): Mpdf
{
return new Mpdf([
'mode' => 'utf-8',
'format' => 'A4',
'margin_left' => 15,
'margin_right' => 15,
'margin_top' => 35,
'margin_bottom' => 25,
'margin_header' => 3,
'margin_footer' => 0,
'default_font' => 'arial',
]);
}
private static function masterListHeaderHtml(string $logoPath, string $testSessionDescriptor): string
{
return '
<table style="width:180mm;">
<tr>
<td style="width:32mm; vertical-align:top;">
<img src="' . htmlspecialchars($logoPath) . '" style="width:30mm; height:25mm;" />
</td>
<td style="vertical-align:top; font-family:Arial;">
<div style="font-weight:bold; font-size:10pt;">JSE Master List</div>
<div style="font-size:10pt; margin-top:2mm;">' . htmlspecialchars($testSessionDescriptor) . '</div>
</td>
</tr>
</table>
<div style="border-bottom:0.3mm solid #000000; margin-top:1mm; line-height:0; font-size:0;"> </div>
';
}
/**
* Builds the registrations table for one section. Uses a native HTML
* <thead>, so mPDF automatically repeats the column header row on every
* page break within the table — replacing the manual page-break /
* re-draw-header logic from the tc-lib-pdf version.
*/
private static function sectionTableHtml(array $rows): string
{
$html = '<table style="width:178mm; border-collapse:collapse; font-family:Arial; font-size:10pt;" cellpadding="2">';
$html .= '<thead><tr>';
foreach (self::COL_HEADERS as $i => $label) {
$html .= '<td style="width:' . self::COL_WIDTHS[$i] . 'mm; background-color:#d0d0ff; border:0.3mm solid #0000ff; font-weight:bold; text-align:center; padding:2mm 1mm;">'
. htmlspecialchars($label) . '</td>';
}
$html .= '</tr></thead><tbody>';
$fill = false;
foreach ($rows as $row) {
$bg = $fill ? '#e0ebff' : '#ffffff';
$html .= '<tr>';
for ($i = 0; $i < 7; $i++) {
$cell = $i < 6 ? mb_substr((string) ($row[$i] ?? ''), 0, self::COL_MAXLEN[$i]) : '';
$html .= '<td style="width:' . self::COL_WIDTHS[$i] . 'mm; background-color:' . $bg . '; border:0.3mm solid #0000ff; text-align:left; padding:1.5mm 1mm;">'
. htmlspecialchars($cell) . '</td>';
}
$html .= '</tr>';
$fill = !$fill;
}
$html .= '</tbody></table>';
return $html;
}
}