classSectionModel = new ClassSectionModel(); $this->configModel = new ConfigurationModel(); $this->schoolYear = $this->configModel->getConfig('school_year'); } public function index() { $db = \Config\Database::connect(); $classSectionId = $this->request->getGet('class_section_id'); $schoolYear = $this->request->getGet('school_year') ?? $this->schoolYear; $classSections = $db->table('classSection cs') ->select('cs.class_section_id, cs.class_section_name') ->join('student_class sc', 'sc.class_section_id = cs.class_section_id') ->join('students s', 's.id = sc.student_id') ->where('s.is_active', 1) ->where('sc.school_year', $schoolYear) ->groupBy('cs.class_section_id, cs.class_section_name') ->having('COUNT(s.id) >', 1) ->orderBy('cs.class_section_name', 'ASC') ->get()->getResultArray(); $students = []; if ($classSectionId) { $students = $db->table('student_class sc') ->select('s.id, s.firstname, s.lastname, cs.class_section_name AS grade') ->join('students s', 's.id = sc.student_id') ->join('classSection cs', 'cs.class_section_id = sc.class_section_id') ->where('sc.class_section_id', (int) $classSectionId) ->where('s.is_active', 1) ->where('sc.school_year', $schoolYear) ->orderBy('s.lastname', 'ASC') ->orderBy('s.firstname', 'ASC') ->get()->getResultArray(); } return view('admin/certificates/index', [ 'classSections' => $classSections, 'students' => $students, 'selectedClassId' => $classSectionId, 'schoolYear' => $schoolYear, 'certDate' => date('m/d/Y'), ]); } public function generate() { $studentIds = $this->request->getPost('student_ids') ?? []; $certDate = trim($this->request->getPost('cert_date') ?? date('m/d/Y')); $classSectionId = $this->request->getPost('class_section_id'); if (empty($studentIds)) { return redirect()->to('administrator/certificates')->with('error', 'Please select at least one student.'); } // Sanitize inputs $studentIds = array_filter(array_map('intval', $studentIds)); $certDate = preg_replace('/[^0-9\/\-]/', '', $certDate); if (empty($studentIds)) { return redirect()->to('administrator/certificates')->with('error', 'Invalid student selection.'); } $db = \Config\Database::connect(); $students = []; foreach ($studentIds as $id) { if ($classSectionId) { $row = $db->table('student_class sc') ->select('s.id, s.firstname, s.lastname, cs.class_section_name AS grade') ->join('students s', 's.id = sc.student_id') ->join('classSection cs', 'cs.class_section_id = sc.class_section_id') ->where('sc.class_section_id', (int) $classSectionId) ->where('s.id', $id) ->get()->getRowArray(); } else { $row = null; } if (!$row) { $s = $db->table('students') ->select('id, firstname, lastname, registration_grade AS grade') ->where('id', $id) ->get()->getRowArray(); $row = $s ?: null; } if ($row) { $students[] = $row; } } if (empty($students)) { return redirect()->to('administrator/certificates')->with('error', 'No valid students found.'); } $pdfData = $this->buildPdf($students, $certDate); $filename = 'Certificates_' . date('Ymd_His') . '.pdf'; return $this->response ->setHeader('Content-Type', 'application/pdf') ->setHeader('Content-Disposition', 'inline; filename="' . $filename . '"') ->setBody($pdfData); } // ─── PDF generation ──────────────────────────────────────────────────────── private function buildPdf(array $students, string $certDate): string { $fontDir = FCPATH . 'assets' . DIRECTORY_SEPARATOR . 'certificates' . DIRECTORY_SEPARATOR . 'fonts' . DIRECTORY_SEPARATOR; $imgDir = FCPATH . 'assets' . DIRECTORY_SEPARATOR . 'certificates' . DIRECTORY_SEPARATOR . 'images' . DIRECTORY_SEPARATOR; // Convert TTF → TCPDF format on first run (cached in TCPDF's own fonts dir). // K_PATH_FONTS must stay pointing at the TCPDF vendor fonts directory so that // built-in fonts (helvetica, times, …) remain findable by SetFont(). $edwardianFont = \TCPDF_FONTS::addTTFfont($fontDir . 'Edwardian Script ITC Regular.ttf', 'TrueTypeUnicode', '', 32); $garamondBold = \TCPDF_FONTS::addTTFfont($fontDir . 'Garamond Bold.ttf', 'TrueTypeUnicode', '', 32); $ebGaramond = \TCPDF_FONTS::addTTFfont($fontDir . 'EBGaramond-Regular.ttf', 'TrueTypeUnicode', '', 32); // A4 landscape in points: 841.89 × 595.28 $pdf = new \TCPDF('L', 'pt', 'A4', true, 'UTF-8', false); $pdf->SetCreator('Al Rahma Sunday School'); $pdf->SetTitle('Student Certificates'); $pdf->SetMargins(0, 0, 0, true); $pdf->SetAutoPageBreak(false, 0); $pdf->setPrintHeader(false); $pdf->setPrintFooter(false); $pdf->SetHeaderMargin(0); $pdf->SetFooterMargin(0); $W = $pdf->getPageWidth(); // 841.89 pt $H = $pdf->getPageHeight(); // 595.28 pt foreach ($students as $student) { $pdf->AddPage(); $name = $student['firstname'] . ' ' . $student['lastname']; $grade = $this->formatGrade($student['grade'] ?? ''); $this->drawCertificate($pdf, $W, $H, $name, $grade, $certDate, $imgDir, $edwardianFont, $garamondBold, $ebGaramond); } return $pdf->Output('', 'S'); } private function formatGrade(string $raw): string { $clean = trim($raw); $lower = strtolower($clean); // Numeric grades 1-9 → "Grade N" if (preg_match('/^\d+$/', $clean) && (int)$clean >= 1 && (int)$clean <= 9) { return 'Grade ' . $clean; } // Youth variants if ($lower === 'youth') { return 'Youth'; } // KG → Kindergarten if ($lower === 'kg') { return 'Kindergarten'; } return $clean; } /** * Draws one certificate page. Coordinate mapping from the original C# iTextSharp code: * iTextSharp uses (x, y) with origin at bottom-left, y increases upward. * TCPDF uses (x, y) with origin at top-left, y increases downward. * Conversion: y_tcpdf = H - y_iTextSharp */ private function drawCertificate( \TCPDF $pdf, float $W, float $H, string $name, string $grade, string $certDate, string $imgDir, string $edwardianFont, string $garamondBold, string $ebGaramond ): void { // ── Title image: iTS SetAbsolutePosition(126, 450), ScaleToFit(600, 300) // Bottom at y=450 from page bottom → top at H-450 ≈ 145. Place at top of page. $pdf->Image($imgDir . 'title.png', 126, 0, 600); // ── Background image: iTS position(280, 79), ScaleToFit(2500, 340) // background.png is 336×393 px → ScaleToFit gives ≈291×340 pt. // Top in TCPDF: H-79-340 = ~176 $pdf->Image($imgDir . 'background.png', 280, 176, 291, 340); // ── Signature image: iTS position(140, 106), ScaleToFit(90, 90) // Top in TCPDF: H-106-90 = ~399 $pdf->Image($imgDir . 'signature.png', 140, 399, 90, 90); // ── "Presented to:" — iTS ALIGN_CENTER baseline y=420 → TCPDF y≈155 $pdf->SetFont('times', 'B', 24); $pdf->SetTextColor(0, 0, 0); $pdf->SetXY(0, 151); $pdf->Cell($W, 24, 'Presented to:', 0, 0, 'C'); // ── Student name — iTS baseline y=334, 38 pt Edwardian Script → TCPDF y≈219 // Bold effect: fill the glyph AND add a thin stroke around it (Edwardian has no bold variant). $pdf->SetFont($edwardianFont, '', 38); $pdf->SetDrawColor(0, 0, 0); $pdf->setTextRenderingMode(0.4, true, false); $pdf->SetXY(0, 219); $pdf->Cell($W, 38, $name, 0, 0, 'C'); $pdf->setTextRenderingMode(0, true, false); // ── Line under name — iTS ALIGN_RIGHT at x=600, y=332 → TCPDF y≈243 $pdf->SetFont('times', '', 20); $pdf->SetXY(0, 243); $pdf->Cell(600, 20, '___________________________________', 0, 0, 'R'); // ── Description — iTS ALIGN_CENTER baseline y=302 → TCPDF y≈273 $pdf->SetFont($ebGaramond, '', 20); $pdf->SetXY(0, 273); $pdf->Cell($W, 20, 'for successfully completing the requirements of', 0, 0, 'C'); // ── Grade — iTS ALIGN_CENTER baseline y=265 → TCPDF y≈310 $pdf->SetFont($garamondBold, '', 20); $pdf->SetXY(0, 310); $pdf->Cell($W, 20, $grade, 0, 0, 'C'); // ── "at" — iTS ALIGN_CENTER baseline y=232 → TCPDF y≈343 $pdf->SetFont('times', '', 20); $pdf->SetXY(0, 343); $pdf->Cell($W, 20, 'at', 0, 0, 'C'); // ── School name — iTS ALIGN_CENTER baseline y=200 → TCPDF y≈375 $pdf->SetFont($garamondBold, '', 20); $pdf->SetXY(0, 375); $pdf->Cell($W, 20, 'Al Rahma Sunday School', 0, 0, 'C'); // ── Certificate date — iTS DrawGradientText at (598, 116), 26 pt Edwardian // TCPDF y ≈ H-116-26 = ~453 $this->drawGradientText($pdf, $edwardianFont, 26, $certDate, 598, 453); // ── Date underline — iTS ALIGN_RIGHT at x=742, y=110 → TCPDF y≈465 $pdf->SetFont('times', '', 20); $pdf->SetXY(0, 463); $pdf->Cell(742, 20, '_________________', 0, 0, 'R'); // ── "Date" label — iTS at (690, 78) → TCPDF y≈497, x≈650 $pdf->SetXY(650, 492); $pdf->Cell(80, 20, 'Date', 0, 0, 'C'); // ── Signature underline — iTS ALIGN_LEFT at x=106, y=110 → TCPDF y≈465 $pdf->SetXY(106, 463); $pdf->Cell(200, 20, '_________________', 0, 0, 'L'); // ── "Signature" label — iTS at (190, 78) → TCPDF y≈497 $pdf->SetXY(106, 492); $pdf->Cell(168, 20, 'Signature', 0, 0, 'C'); } /** * Mimics the C# DrawGradientText — shadow passes give depth, final pass is solid black. */ private function drawGradientText(\TCPDF $pdf, string $fontName, float $fontSize, string $text, float $x, float $y): void { $pdf->SetFont($fontName, '', $fontSize); // Shadow passes (progressively more opaque, slightly offset) for ($i = 0; $i < 6; $i++) { $alpha = ($i + 1) * 25 / 255; // 0.098 → 0.588 $pdf->setAlpha($alpha); $pdf->SetTextColor(0, 0, 0); $pdf->Text($x + $i / 5, $y + $i / 5, $text); } // Main solid pass $pdf->setAlpha(1); $pdf->SetTextColor(0, 0, 0); $pdf->Text($x, $y, $text); } }