add certificate generation
This commit is contained in:
@@ -0,0 +1,296 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers\View;
|
||||
|
||||
use App\Controllers\BaseController;
|
||||
use App\Models\ClassSectionModel;
|
||||
use App\Models\ConfigurationModel;
|
||||
|
||||
class CertificateController extends BaseController
|
||||
{
|
||||
protected $classSectionModel;
|
||||
protected $configModel;
|
||||
protected $schoolYear;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user