add certificates

This commit is contained in:
root
2026-05-18 00:07:30 -04:00
parent df5266c5b5
commit fa6c9519a0
18 changed files with 717 additions and 14 deletions
+7 -1
View File
@@ -111,7 +111,13 @@ if (!function_exists('ci_password_verify')) {
return false;
}
if (strpos($storedHash, ':') !== false || strpos($storedHash, '$') !== false) {
$hashInfo = password_get_info($storedHash);
if (($hashInfo['algo'] ?? null) !== null) {
return Hash::check($password, $storedHash);
}
$parts = preg_split('/[:$]/', $storedHash);
if (is_array($parts) && count($parts) === 4) {
return pbkdf2_verify($password, $storedHash);
}
@@ -0,0 +1,153 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Api\Certificates;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Models\Configuration;
use App\Services\Certificates\CertificatePdfService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class CertificateController extends BaseApiController
{
public function __construct()
{
parent::__construct();
}
/**
* GET /api/v1/administrator/certificates/form-options
*/
public function formOptions(Request $request): JsonResponse
{
$schoolYearParam = $request->query('school_year');
if ($schoolYearParam !== null) {
$schoolYear = trim((string) $schoolYearParam);
} else {
try {
$schoolYear = trim((string) (Configuration::getConfig('school_year') ?? ''));
} catch (\Throwable) {
$schoolYear = '';
}
}
$classSectionId = $request->query('class_section_id');
try {
$classSections = DB::table('classSection')
->select('class_section_id', 'class_section_name')
->orderBy('class_section_name', 'ASC')
->get()
->map(fn ($r) => (array) $r)
->all();
} catch (\Throwable $e) {
Log::error('Certificate formOptions class query failed: ' . $e->getMessage());
return $this->error('Failed to load classes: ' . $e->getMessage(), 500);
}
$students = [];
if ($classSectionId) {
try {
$studentQuery = DB::table('student_class as sc')
->select('s.id', 's.firstname', 's.lastname', 'cs.class_section_name as grade')
->join('students as s', 's.id', '=', 'sc.student_id')
->join('classSection as cs', 'cs.class_section_id', '=', 'sc.class_section_id')
->where('sc.class_section_id', (int) $classSectionId)
->where('s.is_active', 1)
->orderBy('s.lastname')
->orderBy('s.firstname');
if ($schoolYear !== '') {
$studentQuery->where('sc.school_year', $schoolYear);
}
$students = $studentQuery->get()->map(fn ($r) => (array) $r)->all();
} catch (\Throwable $e) {
Log::error('Certificate formOptions student query failed: ' . $e->getMessage());
return $this->error('Failed to load students: ' . $e->getMessage(), 500);
}
}
return $this->success([
'class_sections' => $classSections,
'students' => $students,
'school_year' => $schoolYear,
'selected_class_id' => $classSectionId,
]);
}
/**
* POST /api/v1/administrator/certificates/generate
*/
public function generate(Request $request): Response|JsonResponse
{
$studentIds = $request->input('student_ids', []);
$certDate = trim((string) ($request->input('cert_date') ?? date('m/d/Y')));
$classSectionId = $request->input('class_section_id');
if (empty($studentIds) || !is_array($studentIds)) {
return $this->error('Please select at least one student.', 422);
}
$studentIds = array_values(array_filter(array_map('intval', $studentIds)));
$certDate = preg_replace('/[^0-9\/\-]/', '', $certDate) ?? date('m/d/Y');
if (empty($studentIds)) {
return $this->error('Invalid student selection.', 422);
}
$students = [];
foreach ($studentIds as $id) {
$row = null;
if ($classSectionId) {
$row = DB::table('student_class as sc')
->select('s.id', 's.firstname', 's.lastname', 'cs.class_section_name as grade')
->join('students as s', 's.id', '=', 'sc.student_id')
->join('classSection as cs', 'cs.class_section_id', '=', 'sc.class_section_id')
->where('sc.class_section_id', (int) $classSectionId)
->where('s.id', $id)
->first();
}
if (!$row) {
$row = DB::table('students')
->select('id', 'firstname', 'lastname', 'registration_grade as grade')
->where('id', $id)
->first();
}
if ($row) {
$students[] = (array) $row;
}
}
if (empty($students)) {
return $this->error('No valid students found.', 422);
}
try {
$pdfService = app(CertificatePdfService::class);
$pdfContent = $pdfService->generate($students, $certDate);
} catch (\Throwable $e) {
Log::error('Certificate PDF generation failed: ' . $e->getMessage(), ['exception' => $e]);
return $this->error('Failed to generate certificates.', 500);
}
$filename = 'Certificates_' . date('Ymd_His') . '.pdf';
return response($pdfContent, 200, [
'Content-Type' => 'application/pdf',
'Content-Disposition' => 'inline; filename="' . $filename . '"',
]);
}
private function currentSchoolYear(): string
{
return trim((string) (Configuration::getConfig('school_year') ?? ''));
}
}
@@ -0,0 +1,143 @@
<?php
declare(strict_types=1);
namespace App\Services\Certificates;
class CertificatePdfService
{
private string $fontDir;
private string $imgDir;
public function __construct()
{
$this->fontDir = public_path('assets/certificates/fonts') . DIRECTORY_SEPARATOR;
$this->imgDir = public_path('assets/certificates/images') . DIRECTORY_SEPARATOR;
}
/**
* @param array<array{firstname: string, lastname: string, grade: string}> $students
*/
public function generate(array $students, string $certDate): string
{
$edwardianFont = \TCPDF_FONTS::addTTFfont($this->fontDir . 'Edwardian Script ITC Regular.ttf', 'TrueTypeUnicode', '', 32);
$garamondBold = \TCPDF_FONTS::addTTFfont($this->fontDir . 'Garamond Bold.ttf', 'TrueTypeUnicode', '', 32);
$ebGaramond = \TCPDF_FONTS::addTTFfont($this->fontDir . 'EBGaramond-Regular.ttf', 'TrueTypeUnicode', '', 32);
$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();
$H = $pdf->getPageHeight();
foreach ($students as $student) {
$pdf->AddPage();
$name = $student['firstname'] . ' ' . $student['lastname'];
$grade = $this->formatGrade((string) ($student['grade'] ?? ''));
$this->drawCertificate($pdf, $W, $H, $name, $grade, $certDate, $edwardianFont, $garamondBold, $ebGaramond);
}
return (string) $pdf->Output('', 'S');
}
private function drawCertificate(
\TCPDF $pdf,
float $W,
float $H,
string $name,
string $grade,
string $certDate,
string $edwardianFont,
string $garamondBold,
string $ebGaramond
): void {
$pdf->Image($this->imgDir . 'title.png', 126, 0, 600);
$pdf->Image($this->imgDir . 'background.png', 280, 176, 291, 340);
$pdf->Image($this->imgDir . 'signature.png', 140, 399, 90, 90);
$pdf->SetFont('times', 'B', 24);
$pdf->SetTextColor(0, 0, 0);
$pdf->SetXY(0, 151);
$pdf->Cell($W, 24, 'Presented to:', 0, 0, 'C');
$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);
$pdf->SetFont('times', '', 20);
$pdf->SetXY(0, 243);
$pdf->Cell(600, 20, '___________________________________', 0, 0, 'R');
$pdf->SetFont($ebGaramond, '', 20);
$pdf->SetXY(0, 273);
$pdf->Cell($W, 20, 'for successfully completing the requirements of', 0, 0, 'C');
$pdf->SetFont($garamondBold, '', 20);
$pdf->SetXY(0, 310);
$pdf->Cell($W, 20, $grade, 0, 0, 'C');
$pdf->SetFont('times', '', 20);
$pdf->SetXY(0, 343);
$pdf->Cell($W, 20, 'at', 0, 0, 'C');
$pdf->SetFont($garamondBold, '', 20);
$pdf->SetXY(0, 375);
$pdf->Cell($W, 20, 'Al Rahma Sunday School', 0, 0, 'C');
$this->drawGradientText($pdf, $edwardianFont, 26, $certDate, 598, 453);
$pdf->SetFont('times', '', 20);
$pdf->SetXY(0, 463);
$pdf->Cell(742, 20, '_________________', 0, 0, 'R');
$pdf->SetXY(650, 492);
$pdf->Cell(80, 20, 'Date', 0, 0, 'C');
$pdf->SetXY(106, 463);
$pdf->Cell(200, 20, '_________________', 0, 0, 'L');
$pdf->SetXY(106, 492);
$pdf->Cell(168, 20, 'Signature', 0, 0, 'C');
}
private function drawGradientText(\TCPDF $pdf, string $fontName, float $fontSize, string $text, float $x, float $y): void
{
$pdf->SetFont($fontName, '', $fontSize);
for ($i = 0; $i < 6; $i++) {
$pdf->setAlpha(($i + 1) * 25 / 255);
$pdf->SetTextColor(0, 0, 0);
$pdf->Text($x + $i / 5, $y + $i / 5, $text);
}
$pdf->setAlpha(1);
$pdf->SetTextColor(0, 0, 0);
$pdf->Text($x, $y, $text);
}
private function formatGrade(string $raw): string
{
$clean = trim($raw);
$lower = strtolower($clean);
if (preg_match('/^\d+$/', $clean) && (int) $clean >= 1 && (int) $clean <= 9) {
return 'Grade ' . $clean;
}
if ($lower === 'youth') {
return 'Youth';
}
if ($lower === 'kg') {
return 'Kindergarten';
}
return $clean;
}
}