recreate project
This commit is contained in:
@@ -0,0 +1,390 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers\View;
|
||||
|
||||
class StickersController extends PrintablesBaseController
|
||||
{
|
||||
public function stickerForm()
|
||||
{
|
||||
$data['students'] = $this->studentModel->findAll();
|
||||
$data['classes'] = $this->classSectionModel->findAll();
|
||||
$data['stickers'] = $this->stickerPresets();
|
||||
return view('printables_reports/sticker_form', $data);
|
||||
}
|
||||
|
||||
public function getByClass($classId)
|
||||
{
|
||||
$builder = $this->db->table('student_class sc')
|
||||
->select('s.id, s.firstname, s.lastname, s.registration_grade, s.gender')
|
||||
->join('students s', 's.id = sc.student_id')
|
||||
->where('sc.class_section_id', $classId)
|
||||
->where('sc.school_year', $this->schoolYear)
|
||||
->orderBy('s.lastname', 'asc');
|
||||
|
||||
$students = $builder->get()->getResultArray();
|
||||
|
||||
return $this->response->setJSON($students);
|
||||
}
|
||||
|
||||
/** Preset sticker sizes for the view (robust keys) */
|
||||
private function stickerPresets(): array
|
||||
{
|
||||
$rows = [
|
||||
['value' => '100x24.5', 'label' => 'Xsmall - 100x24.5 mm', 'ppg' => 20],
|
||||
['value' => '60x30', 'label' => 'Small - 60x30 mm', 'ppg' => 24],
|
||||
['value' => '102x34', 'label' => 'Medium - 102x34 mm', 'ppg' => 14],
|
||||
['value' => '90x45', 'label' => 'Large - 90x45 mm', 'ppg' => 10],
|
||||
];
|
||||
|
||||
// Add aliases so different views will not break (title/text/per_page)
|
||||
return array_map(static function ($r) {
|
||||
$r['title'] = $r['title'] ?? ($r['label'] ?? ($r['value'] . ' mm'));
|
||||
$r['text'] = $r['text'] ?? $r['title'];
|
||||
$r['per_page'] = $r['per_page'] ?? ($r['ppg'] ?? null);
|
||||
return $r;
|
||||
}, $rows);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate name stickers as a PDF.
|
||||
*
|
||||
* Can be used for different sticker sizes/sheets by passing parameters
|
||||
* via function args (from routes) or via GET/POST form fields.
|
||||
*
|
||||
* Accepted request params (POST takes precedence over GET):
|
||||
* - class_id : int
|
||||
* - student_id : int
|
||||
* - print_all : 0|1
|
||||
* - sticker_size : string like "102x34" (accepts upper/lower case X or the unicode multiply sign)
|
||||
* - sticker_width : number (mm)
|
||||
* - sticker_height : number (mm)
|
||||
* - stickers_per_page : int (optional cap; grid still derived from size/margins)
|
||||
* - page_size : "A4" (default) | "Letter" | any FPDF size token
|
||||
* - orientation : "P" (default) | "L"
|
||||
* - margin_left : number (mm)
|
||||
* - margin_top : number (mm)
|
||||
* - margin_right : number (mm; defaults to margin_left)
|
||||
* - margin_bottom : number (mm; default 10)
|
||||
* - gap_x : number (mm; horizontal gap between stickers)
|
||||
* - gap_y : number (mm; vertical gap between stickers)
|
||||
* - copies[<student_id>] : int per-student copies in batch mode
|
||||
* - single_copies : int copies for single student mode
|
||||
*/
|
||||
public function generateStickers(
|
||||
?int $studentId = null,
|
||||
?float $stickerWidthArg = null,
|
||||
?float $stickerHeightArg = null,
|
||||
?int $stickersPerPageArg = null,
|
||||
?string $pageSizeArg = null,
|
||||
?string $orientationArg = null,
|
||||
?float $marginLeftArg = null,
|
||||
?float $marginTopArg = null,
|
||||
?float $marginRightArg = null,
|
||||
?float $marginBottomArg = null,
|
||||
?float $gapXArg = null,
|
||||
?float $gapYArg = null
|
||||
) {
|
||||
$request = service('request');
|
||||
$method = strtolower($request->getMethod());
|
||||
$classId = $request->getPost('class_id') ?: $request->getGet('class_id');
|
||||
$studentId = $studentId ?? ($request->getPost('student_id') ?: $request->getGet('student_id'));
|
||||
|
||||
// Safety cast
|
||||
$classId = $classId !== null ? (int) $classId : null;
|
||||
$studentId = $studentId !== null ? (int) $studentId : null;
|
||||
|
||||
// Sanity log for school year
|
||||
if (empty($this->schoolYear)) {
|
||||
log_message('error', 'generateStickers(): $this->schoolYear is empty. Load it from ConfigurationModel.');
|
||||
} else {
|
||||
log_message('debug', 'generateStickers(): schoolYear=' . $this->schoolYear);
|
||||
}
|
||||
|
||||
// Load classes that actually have students in this school year
|
||||
$classes = $this->classSectionModel
|
||||
->select('classSection.class_section_id, classSection.class_section_name')
|
||||
->join('student_class', 'student_class.class_section_id = classSection.class_section_id', 'inner')
|
||||
->where('student_class.school_year', $this->schoolYear)
|
||||
->groupBy('classSection.class_section_id, classSection.class_section_name')
|
||||
->orderBy('classSection.class_section_name', 'ASC')
|
||||
->findAll();
|
||||
|
||||
// ---------- GET: render form ----------
|
||||
if ($method === 'get') {
|
||||
$students = [];
|
||||
|
||||
if (!empty($classId)) {
|
||||
$students = $this->fetchStudentsByClass((int) $classId, $this->schoolYear);
|
||||
} else {
|
||||
$students = $this->studentModel
|
||||
->select('students.id, students.firstname, students.lastname')
|
||||
->join('student_class', 'student_class.student_id = students.id', 'inner')
|
||||
->where('student_class.school_year', $this->schoolYear)
|
||||
->groupBy('students.id, students.firstname, students.lastname')
|
||||
->orderBy('students.firstname', 'ASC')
|
||||
->orderBy('students.lastname', 'ASC')
|
||||
->findAll(500);
|
||||
}
|
||||
|
||||
// Presets (now with robust keys expected by the view)
|
||||
$presetRows = [
|
||||
['value' => '60x30', 'label' => 'Small - 60x30 mm', 'ppg' => 24],
|
||||
['value' => '102x34', 'label' => 'Medium - 102x34 mm', 'ppg' => 14],
|
||||
['value' => '90x45', 'label' => 'Large - 90x45 mm', 'ppg' => 10],
|
||||
];
|
||||
// Normalize to include 'title', 'text', and 'per_page' so views using any of those will not error.
|
||||
$stickers = array_map(static function (array $r): array {
|
||||
$r['title'] = $r['title'] ?? ($r['label'] ?? ($r['value'] . ' mm'));
|
||||
$r['text'] = $r['text'] ?? $r['title'];
|
||||
$r['per_page'] = $r['per_page'] ?? ($r['ppg'] ?? null);
|
||||
return $r;
|
||||
}, $presetRows);
|
||||
|
||||
return view('printables_reports/sticker_form', [
|
||||
'classes' => $classes,
|
||||
'students' => $students,
|
||||
'stickers' => $stickers,
|
||||
]);
|
||||
}
|
||||
|
||||
// ---------- POST: build student list ----------
|
||||
$printAll = (int) ($request->getPost('print_all') ?? $request->getGet('print_all') ?? 0) === 1;
|
||||
|
||||
$students = [];
|
||||
if (!empty($studentId)) {
|
||||
$student = $this->studentModel->find((int) $studentId);
|
||||
if (!$student) {
|
||||
return 'Student not found.';
|
||||
}
|
||||
$students = [$student];
|
||||
} elseif (!empty($classId)) {
|
||||
$students = $this->fetchStudentsByClass((int) $classId, $this->schoolYear);
|
||||
if (empty($students)) {
|
||||
return 'No students found in this class.';
|
||||
}
|
||||
} elseif ($printAll) {
|
||||
$students = $this->studentModel
|
||||
->select('students.id, students.firstname, students.lastname, sc.class_section_id, cs.class_section_name, c.id AS class_id')
|
||||
->join('student_class sc', 'sc.student_id = students.id', 'inner')
|
||||
->join('classSection cs', 'cs.class_section_id = sc.class_section_id', 'inner')
|
||||
->join('classes c', 'c.id = cs.class_id', 'inner')
|
||||
->where('sc.school_year', $this->schoolYear)
|
||||
->groupBy('students.id, students.firstname, students.lastname, sc.class_section_id, cs.class_section_name, c.id')
|
||||
->orderBy('c.id', 'ASC')
|
||||
->orderBy('cs.class_section_name', 'ASC')
|
||||
->orderBy('students.firstname', 'ASC')
|
||||
->orderBy('students.lastname', 'ASC')
|
||||
->findAll(5000);
|
||||
|
||||
// Exclude Youth and KG
|
||||
$students = array_values(array_filter($students, static function ($s) {
|
||||
$g = trim((string) ($s['class_section_name'] ?? ''));
|
||||
return $g !== '' && !preg_match('/^(youth|kg)(?:\b|-)/i', $g);
|
||||
}));
|
||||
|
||||
if (empty($students)) {
|
||||
return 'No students found for this school year.';
|
||||
}
|
||||
} else {
|
||||
return 'Please select a student or class.';
|
||||
}
|
||||
|
||||
// ---- Expand list according to per-student copy counts ----
|
||||
$copiesPosted = $request->getPost('copies'); // array|null
|
||||
$singleCopies = (int) ($request->getPost('single_copies') ?? $request->getGet('single_copies') ?? 1);
|
||||
if ($singleCopies < 0) $singleCopies = 0;
|
||||
|
||||
$printList = [];
|
||||
if (!empty($studentId)) {
|
||||
$qty = max(0, $singleCopies);
|
||||
for ($k = 0; $k < $qty; $k++) {
|
||||
$printList[] = $students[0];
|
||||
}
|
||||
} else {
|
||||
$hasCopies = is_array($copiesPosted);
|
||||
$defaultQty = $hasCopies ? 0 : 1; // if copies[] present, only print IDs provided
|
||||
foreach ($students as $stu) {
|
||||
$id = (int) ($stu['id'] ?? 0);
|
||||
$qty = $defaultQty;
|
||||
if ($hasCopies && array_key_exists((string)$id, $copiesPosted)) {
|
||||
$qty = (int) $copiesPosted[(string)$id];
|
||||
}
|
||||
$qty = max(0, $qty);
|
||||
for ($k = 0; $k < $qty; $k++) {
|
||||
$printList[] = $stu;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (empty($printList)) {
|
||||
return 'Nothing to print. All quantities are zero.';
|
||||
}
|
||||
|
||||
// ---------- Layout parameters (request/args/defaults) ----------
|
||||
$sizeStrReq = trim((string) ($request->getPost('sticker_size') ?? $request->getGet('sticker_size') ?? ''));
|
||||
$stickerWidth = $stickerWidthArg ?? (float) ($request->getPost('sticker_width') ?? $request->getGet('sticker_width') ?? 0);
|
||||
$stickerHeight = $stickerHeightArg ?? (float) ($request->getPost('sticker_height') ?? $request->getGet('sticker_height') ?? 0);
|
||||
|
||||
// parse size string if provided (supports decimals; accepts x/X/unicode multiply)
|
||||
if (($stickerWidth <= 0 || $stickerHeight <= 0) && $sizeStrReq !== '') {
|
||||
if (preg_match('/^\\s*(\\d+(?:\\.\\d+)?)\\s*[xX\\x{00D7}]\\s*(\\d+(?:\\.\\d+)?)\\s*$/u', $sizeStrReq, $m)) {
|
||||
$stickerWidth = (float) $m[1];
|
||||
$stickerHeight = (float) $m[2];
|
||||
}
|
||||
}
|
||||
|
||||
// fallbacks to controller properties or sane defaults (mm)
|
||||
if ($stickerWidth <= 0) {
|
||||
$stickerWidth = isset($this->stickerWidth) ? (float)$this->stickerWidth : 102.0;
|
||||
}
|
||||
if ($stickerHeight <= 0) {
|
||||
$stickerHeight = isset($this->stickerHeight) ? (float)$this->stickerHeight : 34.0;
|
||||
}
|
||||
|
||||
$stickersPerPageReq = $stickersPerPageArg ?? (int) ($request->getPost('stickers_per_page') ?? $request->getGet('stickers_per_page') ?? 0);
|
||||
$pageSize = $pageSizeArg ?? (string) ($request->getPost('page_size') ?? $request->getGet('page_size') ?? 'A4');
|
||||
$orientation = $orientationArg ?? (string) ($request->getPost('orientation') ?? $request->getGet('orientation') ?? 'P');
|
||||
|
||||
$marginLeftProvided = $marginLeftArg !== null || $request->getPost('margin_left') !== null || $request->getGet('margin_left') !== null;
|
||||
$marginTopProvided = $marginTopArg !== null || $request->getPost('margin_top') !== null || $request->getGet('margin_top') !== null;
|
||||
$marginRightProvided = $marginRightArg !== null || $request->getPost('margin_right') !== null || $request->getGet('margin_right') !== null;
|
||||
$marginBottomProvided = $marginBottomArg !== null || $request->getPost('margin_bottom') !== null || $request->getGet('margin_bottom') !== null;
|
||||
$gapXProvided = $gapXArg !== null || $request->getPost('gap_x') !== null || $request->getGet('gap_x') !== null;
|
||||
$gapYProvided = $gapYArg !== null || $request->getPost('gap_y') !== null || $request->getGet('gap_y') !== null;
|
||||
|
||||
$marginLeft = $marginLeftArg ?? (float) ($request->getPost('margin_left') ?? $request->getGet('margin_left') ?? ($this->marginL ?? 6));
|
||||
$marginTop = $marginTopArg ?? (float) ($request->getPost('margin_top') ?? $request->getGet('margin_top') ?? ($this->marginT ?? 6));
|
||||
$marginRight = $marginRightArg ?? (float) ($request->getPost('margin_right') ?? $request->getGet('margin_right') ?? $marginLeft);
|
||||
$marginBottom = $marginBottomArg ?? (float) ($request->getPost('margin_bottom') ?? $request->getGet('margin_bottom') ?? 10);
|
||||
|
||||
$gapX = $gapXArg ?? (float) ($request->getPost('gap_x') ?? $request->getGet('gap_x') ?? ($this->gapX ?? 0));
|
||||
$gapY = $gapYArg ?? (float) ($request->getPost('gap_y') ?? $request->getGet('gap_y') ?? ($this->gapY ?? 0));
|
||||
|
||||
// If using the 100x24.5 preset (small labels) and nothing custom was provided,
|
||||
// default to tight margins and zero gaps to fit 20 per page (2 columns * 10+ rows).
|
||||
$normalizedSize = strtolower(str_replace(['×', ' '], ['x', ''], $sizeStrReq));
|
||||
$isXSmallPreset = $normalizedSize === '100x24.5' || (abs($stickerWidth - 100.0) < 0.6 && abs($stickerHeight - 24.5) < 0.6);
|
||||
if ($isXSmallPreset) {
|
||||
if (!$marginLeftProvided) {
|
||||
$marginLeft = 4.0;
|
||||
if (!$marginRightProvided) {
|
||||
$marginRight = 4.0;
|
||||
}
|
||||
}
|
||||
if (!$marginRightProvided && $marginRight < 4.0) {
|
||||
$marginRight = 4.0;
|
||||
}
|
||||
if (!$gapXProvided) {
|
||||
$gapX = 0.0;
|
||||
}
|
||||
if (!$gapYProvided) {
|
||||
$gapY = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- FPDF setup ----------
|
||||
$pdf = new \FPDF($orientation, 'mm', $pageSize);
|
||||
$pdf->SetMargins($marginLeft, $marginTop, $marginRight);
|
||||
$pdf->SetAutoPageBreak(true, $marginBottom);
|
||||
$pdf->AddPage();
|
||||
$pdf->SetFont('Arial', '', 12);
|
||||
|
||||
// Printable area
|
||||
$pageW = (float) $pdf->GetPageWidth();
|
||||
$pageH = (float) $pdf->GetPageHeight();
|
||||
|
||||
$usableW = max(0.0, $pageW - ($marginLeft + $marginRight));
|
||||
$usableH = max(0.0, $pageH - ($marginTop + $marginBottom));
|
||||
|
||||
// Compute columns & rows from physical dimensions and gaps.
|
||||
$cols = (int) floor(($usableW + max(0.0, $gapX)) / (max(0.1, $stickerWidth) + max(0.0, $gapX)));
|
||||
$rows = (int) floor(($usableH + max(0.0, $gapY)) / (max(0.1, $stickerHeight) + max(0.0, $gapY)));
|
||||
|
||||
$cols = max(1, $cols);
|
||||
$rows = max(1, $rows);
|
||||
|
||||
// Optional cap: stickers_per_page
|
||||
$capacity = $cols * $rows;
|
||||
if ($stickersPerPageReq > 0 && $stickersPerPageReq < $capacity) {
|
||||
$rows = (int) ceil($stickersPerPageReq / $cols);
|
||||
$maxRowsFit = (int) floor(($usableH + max(0.0, $gapY)) / (max(0.1, $stickerHeight) + max(0.0, $gapY)));
|
||||
$rows = max(1, min($rows, max(1, $maxRowsFit)));
|
||||
$capacity = $cols * $rows;
|
||||
}
|
||||
|
||||
// Starting positions
|
||||
$xStart = $marginLeft;
|
||||
$yStart = $marginTop;
|
||||
$x = $xStart;
|
||||
$y = $yStart;
|
||||
|
||||
$pt2mm = static function (float $pt): float {
|
||||
return $pt * 0.352778;
|
||||
};
|
||||
|
||||
// Draw loop
|
||||
$i = 0;
|
||||
$perPage = $capacity;
|
||||
|
||||
foreach ($printList as $stu) {
|
||||
if ($i > 0 && $i % $perPage === 0) {
|
||||
$pdf->AddPage();
|
||||
$x = $xStart;
|
||||
$y = $yStart;
|
||||
}
|
||||
|
||||
// Background sticker rectangle (optional; fill white)
|
||||
$pdf->SetFillColor(255, 255, 255);
|
||||
$pdf->Rect($x, $y, $stickerWidth, $stickerHeight, 'F');
|
||||
|
||||
// Centered name with dynamic font size
|
||||
$fontFamily = 'Arial';
|
||||
$fontStyle = 'B';
|
||||
$maxPt = 16;
|
||||
$minPt = 8;
|
||||
$hPad = 2;
|
||||
|
||||
$fullName = trim(($stu['firstname'] ?? '') . ' ' . ($stu['lastname'] ?? '')) ?: 'Student';
|
||||
|
||||
$bestPt = $maxPt;
|
||||
$textW = 0.0;
|
||||
while ($bestPt >= $minPt) {
|
||||
$pdf->SetFont($fontFamily, $fontStyle, $bestPt);
|
||||
$textW = (float) $pdf->GetStringWidth($fullName);
|
||||
if ($textW <= ($stickerWidth - 2 * $hPad)) break;
|
||||
$bestPt -= 0.5;
|
||||
}
|
||||
if ($bestPt < $minPt) {
|
||||
$bestPt = $minPt;
|
||||
$pdf->SetFont($fontFamily, $fontStyle, $bestPt);
|
||||
$textW = (float) $pdf->GetStringWidth($fullName);
|
||||
}
|
||||
$textH = $pt2mm($bestPt);
|
||||
|
||||
$cellX = $x + (($stickerWidth - $textW) / 2);
|
||||
$cellY = $y + (($stickerHeight - $textH) / 2);
|
||||
|
||||
$pdf->SetXY($cellX, $cellY);
|
||||
$pdf->Cell($textW, $textH, $fullName, 0, 0, 'L');
|
||||
|
||||
// Advance cell position within the grid
|
||||
$i++;
|
||||
$posInRow = ($i - 1) % $cols;
|
||||
if ($posInRow === ($cols - 1)) {
|
||||
$x = $xStart;
|
||||
$y += $stickerHeight + $gapY;
|
||||
} else {
|
||||
$x += $stickerWidth + $gapX;
|
||||
}
|
||||
}
|
||||
|
||||
// Output
|
||||
$pdfContent = $pdf->Output('S');
|
||||
return $this->response
|
||||
->setHeader('Content-Type', 'application/pdf')
|
||||
->setHeader('Content-Disposition', 'inline; filename="Student_Stickers.pdf"')
|
||||
->setHeader('Cache-Control', 'private, max-age=0, must-revalidate')
|
||||
->setHeader('Pragma', 'public')
|
||||
->setHeader('Content-Length', (string) strlen($pdfContent))
|
||||
->setBody($pdfContent);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user