Files
2026-02-10 22:11:06 -05:00

851 lines
33 KiB
PHP

<?php
namespace App\Controllers\View;
class BadgesController extends PrintablesBaseController
{
//Badges
public function badge()
{
// 1) Collect & sanitize IDs
$userIds = $this->request->getPost('user_ids') ?? [];
if (!is_array($userIds)) $userIds = [$userIds];
$userIds = array_values(array_filter(array_map(static function ($v) {
$v = is_string($v) ? trim($v) : $v;
return ($v !== '' && $v !== null) ? (int)$v : null;
}, $userIds), static fn($v) => $v !== null));
$userIds = array_values(array_unique($userIds));
// 2) Consistent school_year
$schoolYear = $this->request->getPost('school_year')
?? $this->request->getGet('school_year')
?? ($this->schoolYear ?? null);
// 2a) Posted maps from the view
$rolesMap = $this->request->getPost('roles') ?? [];
$classesMap = $this->request->getPost('classes') ?? [];
if (!is_array($rolesMap)) $rolesMap = [];
if (!is_array($classesMap)) $classesMap = [];
// 2b) Normalizer to mirror the view's formatting
$formatRole = static function (?string $role): string {
$role = (string)$role;
$role = str_replace(['-', '_'], ' ', $role);
$role = preg_replace('/\s+/', ' ', trim($role));
if ($role === '') return '';
$out = [];
foreach (explode(' ', $role) as $w) {
if ($w === '') continue;
if (preg_match('/^[A-Za-z]{1,3}$/', $w)) {
$out[] = strtoupper($w);
} else {
$out[] = ucfirst(strtolower($w));
}
}
return implode(' ', $out);
};
// 3) PDF setup
$pdf = new \FPDF('P', 'mm', 'A4');
$pdf->SetAutoPageBreak(false);
// Layout: 2 cols - 5 rows = 10 per page
$badgeW = 95;
$badgeH = 67;
$marginL = 14;
$marginT = 6;
$gutterX = 0;
$gutterY = 0;
$cols = 2;
$rows = 4;
$perPage = $cols * $rows;
$pagesAdded = 0;
$i = 0;
$norm = static function (?string $s): string {
$s = (string)$s;
$s = str_replace(["\xC2\xA0", "\xA0"], ' ', $s);
$s = preg_replace('/\s+/u', ' ', $s) ?? $s;
$s = preg_replace('/[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F]/u', '', $s) ?? $s;
return trim($s);
};
$resolveRole = static function (array $info) use ($norm): string {
$candidates = [
'role',
'active_role',
'role_name',
function ($r) {
if (!empty($r['roles'])) {
$parts = array_filter(array_map('trim', explode(',', (string)$r['roles'])));
return $parts[0] ?? '';
}
return '';
},
'job_title',
'title',
'position',
'staff_role',
'department_role',
'dept_role'
];
foreach ($candidates as $key) {
if (is_callable($key)) {
$v = $key($info);
if ($norm($v) !== '') return $norm($v);
} else {
if (!empty($info[$key])) {
$v = $norm((string)$info[$key]);
if ($v !== '') return $v;
}
}
}
return 'STAFF';
};
$seen = [];
foreach ($userIds as $uid) {
$info = $this->getUserInfoById($uid, $schoolYear);
if (!$info || !is_array($info)) continue;
$userId = isset($info['user_id']) ? (int)$info['user_id']
: (isset($info['id']) ? (int)$info['id']
: (isset($info['users.id']) ? (int)$info['users.id'] : $uid));
$name = $norm($info['name'] ?? (($info['firstname'] ?? '') . ' ' . ($info['lastname'] ?? '')));
// Prefer posted formatted role; else fallback and format server-side
$postedRole = $rolesMap[$userId] ?? null;
$roleResolved = $postedRole !== null ? $postedRole : $resolveRole($info);
$roleResolved = $formatRole((string)$roleResolved);
$info['role_resolved'] = $roleResolved;
// Prefer posted class name if provided (keeps what the user saw)
if (!empty($classesMap[$userId])) {
$info['class_section_name'] = (string)$classesMap[$userId];
}
$class = $norm($info['class_section_name'] ?? '');
$badgeKey = strtolower(trim($userId . '|' . $name . '|' . $roleResolved . '|' . $class));
if (isset($seen[$badgeKey])) continue;
$seen[$badgeKey] = true;
if (($i % $perPage) === 0) {
$pdf->AddPage();
$pagesAdded++;
}
$indexOnPage = $i % $perPage;
$row = intdiv($indexOnPage, $cols);
$col = $indexOnPage % $cols;
$x = $marginL + $col * ($badgeW + $gutterX);
$y = $marginT + $row * ($badgeH + $gutterY);
// drawBadgeInCell expects 'role_resolved' and (optionally) 'class_section_name'
$this->drawBadgeInCell($pdf, $info, $x, $y, $badgeW, $badgeH);
$i++;
}
if ($pagesAdded === 0) {
$pdf->AddPage();
$pdf->SetFont('Arial', '', 10);
$pdf->SetXY(10, 20);
$pdf->MultiCell(0, 6, "No valid staff selected or data not found.", 0, 'L');
}
if (ob_get_length()) {
ob_end_clean();
}
$pdfString = $pdf->Output('S');
// 4) Track prints (best-effort; swallow errors if table missing)
try {
$actorId = (int) (session()->get('user_id') ?? 0) ?: null;
$this->badgePrintLogModel->logPrints(
$userIds,
$actorId,
is_string($schoolYear) ? $schoolYear : null,
is_array($rolesMap) ? $rolesMap : [],
is_array($classesMap) ? $classesMap : [],
1
);
} catch (\Throwable $e) {
log_message('error', 'Failed to log badge prints: ' . $e->getMessage());
}
return $this->response
->setHeader('Content-Type', 'application/pdf')
->setHeader('Content-Disposition', 'inline; filename="Staff_Badges.pdf"')
->setBody($pdfString);
}
/**
* API: Return print status for given users (count + last print time).
* GET: user_ids[] or CSV in user_ids, optional school_year
* Response: { ok: true, data: { <id>: { count, last_printed_at, last_printed_by } } }
*/
public function badgePrintStatus()
{
$ids = $this->request->getGet('user_ids');
if (is_string($ids)) {
$ids = array_filter(array_map('trim', explode(',', $ids)), 'strlen');
}
if (!is_array($ids)) $ids = $ids ? [$ids] : [];
$ids = array_values(array_unique(array_map(static fn($v) => (int)$v, $ids)));
$schoolYear = $this->request->getGet('school_year') ?? $this->schoolYear;
try {
$status = $this->badgePrintLogModel->getStatus($ids, is_string($schoolYear) ? $schoolYear : null);
} catch (\Throwable $e) {
return $this->response->setJSON(['ok' => false, 'error' => 'Failed to query status']);
}
return $this->response->setJSON([
'ok' => true,
'data' => $status,
'csrf_token' => csrf_token(),
'csrf_hash' => csrf_hash(),
]);
}
/**
* API: Log a set of prints explicitly (optional; PDF endpoint already logs).
* POST: user_ids[], optional school_year, roles[ID], classes[ID]
*/
public function logBadgePrint()
{
$ids = $this->request->getPost('user_ids') ?? [];
if (!is_array($ids)) $ids = $ids ? [$ids] : [];
$ids = array_values(array_unique(array_map(static fn($v) => (int)$v, $ids)));
$roles = $this->request->getPost('roles') ?? [];
$class = $this->request->getPost('classes') ?? [];
if (!is_array($roles)) $roles = [];
if (!is_array($class)) $class = [];
$schoolYear = $this->request->getPost('school_year') ?? $this->schoolYear;
$actorId = (int) (session()->get('user_id') ?? 0) ?: null;
try {
$cnt = $this->badgePrintLogModel->logPrints(
$ids,
$actorId,
is_string($schoolYear) ? $schoolYear : null,
$roles,
$class,
1
);
return $this->response->setJSON([
'ok' => true,
'inserted' => $cnt,
'csrf_token' => csrf_token(),
'csrf_hash' => csrf_hash(),
]);
} catch (\Throwable $e) {
return $this->response->setJSON(['ok' => false, 'error' => $e->getMessage()]);
}
}
protected function drawBadgeInCell(\FPDF $pdf, array $data, float $x, float $y, float $w, float $h): void
{
// Background & border
$pdf->SetFillColor(255, 255, 255);
$pdf->Rect($x, $y, $w, $h, 'F');
$pdf->SetDrawColor(200, 200, 200);
$pdf->Rect($x, $y, $w, $h);
// Logos
$logoSize = 6;
if (!empty($data['school_logo']) && file_exists($data['school_logo'])) {
$pdf->Image($data['school_logo'], $x + $w - 6 - $logoSize, $y + 4, $logoSize + 3, $logoSize - 1);
}
if (!empty($data['isgl_logo']) && file_exists($data['isgl_logo'])) {
$pdf->Image($data['isgl_logo'], $x + 4, $y + 4, $logoSize + 3, $logoSize - 1);
}
// Helpers
$padX = 4;
$cursorY = $y + 4 + $logoSize + 2;
$norm = static function (?string $s): string {
$s = (string)$s;
$s = str_replace(["\xC2\xA0", "\xA0"], ' ', $s);
$s = preg_replace('/\s+/u', ' ', $s) ?? $s;
$s = preg_replace('/[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F]/u', '', $s) ?? $s;
return trim($s);
};
$toPdf = static function (string $s): string {
if (function_exists('iconv')) {
$o = @iconv('UTF-8', 'ISO-8859-1//TRANSLIT', $s);
if ($o !== false && $o !== '') return $o;
}
if (function_exists('mb_convert_encoding')) {
$o = @mb_convert_encoding($s, 'ISO-8859-1', 'UTF-8');
if ($o !== '') return $o;
}
$o = @utf8_decode($s);
return $o !== '' ? $o : 'STAFF';
};
$fitText = static function (\FPDF $pdf, string $text, int $startSize, int $minSize, float $maxWidth): int {
$size = $startSize;
while ($size >= $minSize) {
$pdf->SetFontSize($size);
if ($pdf->GetStringWidth($text) <= $maxWidth) return $size;
$size--;
}
return $minSize;
};
// === Role formatter (fixes "OF" -> "Of", "HEAD" -> "Head"; keeps TA/PTA/HR/KG/IT/DEPT in ALL-CAPS) ===
$formatRole = static function (string $role): string {
// Normalize separators & spaces
$role = str_replace(['-', '_'], ' ', $role);
$role = preg_replace('/\s+/u', ' ', trim($role));
if ($role === '') return '';
// Special-casing map (case-insensitive keys)
// - "of" must be lower-case
// - "head" should be Title-case
$special = [
'of' => 'of',
'head' => 'Head',
];
$tokens = explode(' ', $role);
$out = [];
foreach ($tokens as $tok) {
if ($tok === '') continue;
// Strip leading/trailing NON-letters for the decision; reattach afterwards
$core = preg_replace('/^\P{L}+|\P{L}+$/u', '', $tok);
$start = strpos($tok, $core);
if ($start === false) { // no letter content
$out[] = ucfirst(mb_strtolower($tok, 'UTF-8'));
continue;
}
$pre = substr($tok, 0, $start);
$suf = substr($tok, $start + strlen($core));
$lw = mb_strtolower($core, 'UTF-8');
if (isset($special[$lw])) {
// Use the exact desired casing from the map
$coreFmt = $special[$lw]; // "of" -> "of", "head" -> "Head"
} elseif (preg_match('/^\p{L}{1,4}$/u', $core)) {
// 1-4 letters -> ALL CAPS (TA, PTA, HR, KG, IT, DEPT)
$coreFmt = mb_strtoupper($core, 'UTF-8');
} else {
// Title-case longer words
$coreFmt = preg_replace_callback(
'/\p{L}+/u',
static fn($m) => mb_strtoupper(mb_substr($m[0], 0, 1, 'UTF-8'), 'UTF-8')
. mb_strtolower(mb_substr($m[0], 1, null, 'UTF-8'), 'UTF-8'),
mb_strtolower($core, 'UTF-8')
);
}
$out[] = $pre . $coreFmt . $suf;
}
return implode(' ', $out);
};
// Class label formatter (KG stays KG; Youth title-cased; otherwise pass through)
$formatClass = static function (string $class): string {
$c = trim($class);
if ($c === '') return '';
$lc = strtolower($c);
if ($lc === 'kg' || $lc === 'kindergarten') return 'KG';
if ($lc === 'youth') return 'Youth';
return $c; // e.g., "1-A", "3", "HS-2"
};
// School name
$schoolName = strtoupper($norm($data['school_name'] ?? 'AL RAHMA SUNDAY SCHOOL'));
$pdf->SetFont('Arial', '', 16);
$pdf->SetXY($x + $padX, $cursorY);
$pdf->MultiCell($w - 2 * $padX, 5, $toPdf($schoolName), 0, 'C');
// Name
$pdf->Ln(9);
$pdf->SetFont('Arial', 'B', 14);
$pdf->SetX($x + $padX);
$name = strtoupper($norm($data['name'] ?? (($data['firstname'] ?? '') . ' ' . ($data['lastname'] ?? 'STAFF'))));
$pdf->Cell($w - 2 * $padX, 6, $toPdf($name), 0, 1, 'C');
// Role (prefer preformatted; then fix with formatter to guarantee "Of"/"Head" etc.)
$roleResolved = $norm((string)($data['role_resolved'] ?? ''));
if ($roleResolved === '') {
// fallback: find first available role-like field
$candidates = [
'role',
'active_role',
'role_name',
function ($r) {
if (!empty($r['roles'])) {
$parts = array_filter(array_map('trim', explode(',', (string)$r['roles'])));
return $parts[0] ?? '';
}
return '';
},
'job_title',
'title',
'position',
'staff_role',
'department_role',
'dept_role'
];
foreach ($candidates as $key) {
$v = '';
if (is_callable($key)) {
$v = (string)$key($data);
} elseif (!empty($data[$key])) {
$v = (string)$data[$key];
}
$v = $norm($v);
if ($v !== '') {
$roleResolved = $v;
break;
}
}
}
// Final fix to ensure exceptions and acronyms are correct even if upstream passed "OF"
if ($roleResolved !== '') {
$roleResolved = $formatRole($roleResolved);
}
// Class
$classRaw = $norm($data['class_section_name'] ?? '');
$class = $formatClass($classRaw);
// Teacher-ish detection (lowercased source; do NOT change printed casing)
$detectSrc = strtolower($norm(
($data['roles_raw'] ?? '') !== '' ? $data['roles_raw'] : (($data['role_name_raw'] ?? '') !== '' ? $data['role_name_raw'] : $roleResolved)
));
$isTeacherish = (strpos($detectSrc, 'teacher') !== false) || preg_match('/\bta\b/', $detectSrc);
// Compose final display (no re-casing beyond $formatRole)
if ($isTeacherish && $class !== '') {
if (strtolower($class) === 'youth') {
$display = 'Youth ' . $roleResolved;
} elseif ($class === 'KG') {
$display = 'KG ' . $roleResolved;
} else {
$display = 'Grade ' . $class . ' ' . $roleResolved;
}
} elseif ($roleResolved !== '') {
$display = $roleResolved;
} elseif ($class !== '') {
$display = $class;
} else {
$display = 'STAFF';
}
// Print role/class
$pdf->Ln(6);
$pdf->SetFont('Arial', '', 14);
$displayOut = $toPdf($display);
$maxTextWidth = $w - 2 * $padX;
$best = $fitText($pdf, $displayOut, 11, 7, $maxTextWidth);
$pdf->SetFont('Arial', '', $best + 4);
if ($pdf->GetStringWidth($displayOut) <= $maxTextWidth) {
$pdf->SetX($x + $padX);
$pdf->Cell($maxTextWidth, 5, $displayOut, 0, 1, 'C');
} else {
$pdf->SetX($x + $padX);
$pdf->MultiCell($maxTextWidth, 5, $displayOut, 0, 'C');
}
// Footer: year
$footerYear = $norm((string)($this->schoolYear ?? ($data['school_year'] ?? '')));
if ($footerYear !== '') {
$pdf->SetFont('Arial', 'I', 14);
$pdf->SetXY($x + $padX, $y + $h - 9);
$pdf->Cell($w - 2 * $padX, 4, $toPdf($footerYear), 0, 0, 'C');
}
}
// ---- Helper: get staff rows (no joins) ----
private function fetchStaffList(?string $schoolYear, array $selectedUserIds = []): array
{
// $this->userModel should be injected/constructed already
return $this->userModel->getNoParentUsersWithRole($schoolYear, $selectedUserIds);
}
// ---- Helper: is this person teacher-ish? (role_name can be CSV) ----
private static function isTeacherish(string $roleCsv): bool
{
$roles = array_filter(array_map('trim', explode(',', strtolower($roleCsv))));
foreach ($roles as $r) {
if ($r === 'teacher' || $r === 'teacher_assistant' || $r === 'teacher assistant') {
return true;
}
}
return false;
}
// ---- Helper: return ONE latest assignment (pref Teacher, else Assistant) and its class name ----
private function getLatestClassForUser(int $userId, ?string $schoolYear = null): ?array
{
// Build a single query that considers teacher or assistant rows,
$qb = $this->db->table('teacher_class tc')
->select('tc.class_section_id, cs.class_section_name')
->join('classSection cs', 'cs.class_section_id = tc.class_section_id', 'left')
->where('tc.teacher_id', $userId);
if (!empty($schoolYear)) {
$qb->where('tc.school_year', $schoolYear);
}
$qb->orderBy('FIELD(tc.position, "main", "ta")', '', false)
->orderBy('IFNULL(tc.updated_at,"0000-00-00 00:00:00") DESC', '', false)
->limit(1);
if (!empty($schoolYear)) {
$qb->where('tc.school_year', $schoolYear);
}
// Prefer a TEACHER row if one exists, then the latest update.
// MySQL treats boolean expressions as 0/1, so we can order by it.
$qb->orderBy('(tc.teacher_id = ' . $this->db->escape($userId) . ') DESC', '', false)
->orderBy('IFNULL(tc.updated_at,"0000-00-00 00:00:00") DESC, tc.id DESC', '', false)
->limit(1);
$row = $qb->get()->getRowArray();
if (!$row || empty($row['class_section_id'])) {
return null;
}
return [
'class_section_id' => (int) $row['class_section_id'],
'class_section_name' => $row['class_section_name'] ?? null,
];
}
// ---- Controller: badgeForm (simple + deterministic) ----
public function badgeForm()
{
$request = $this->request;
$schoolYear = $this->schoolYear;
if ($this->schoolYear === null || $schoolYear === '') {
$schoolYear = $this->schoolYear ?? null;
}
// --- Role formatting helpers ---
$formatRole = static function (string $role): string {
// Normalize separators & spaces
$role = str_replace(['-', '_'], ' ', $role);
$role = preg_replace('/\s+/u', ' ', trim($role));
if ($role === '') return '';
// Special-casing map (case-insensitive keys)
// - "of" must be lower-case
// - "head" should be Title-case
$special = [
'of' => 'of',
'head' => 'Head',
];
$tokens = explode(' ', $role);
$out = [];
foreach ($tokens as $tok) {
if ($tok === '') continue;
// Strip leading/trailing NON-letters for the decision; reattach afterwards
$core = preg_replace('/^\P{L}+|\P{L}+$/u', '', $tok);
$start = strpos($tok, $core);
if ($start === false) { // no letter content
$out[] = ucfirst(mb_strtolower($tok, 'UTF-8'));
continue;
}
$pre = substr($tok, 0, $start);
$suf = substr($tok, $start + strlen($core));
$lw = mb_strtolower($core, 'UTF-8');
if (isset($special[$lw])) {
// Use the exact desired casing from the map
$coreFmt = $special[$lw]; // "of" -> "of", "head" -> "Head"
} elseif (preg_match('/^\p{L}{1,4}$/u', $core)) {
// 1-4 letters -> ALL CAPS (TA, PTA, HR, KG, IT, DEPT)
$coreFmt = mb_strtoupper($core, 'UTF-8');
} else {
// Title-case longer words
$coreFmt = preg_replace_callback(
'/\p{L}+/u',
static fn($m) => mb_strtoupper(mb_substr($m[0], 0, 1, 'UTF-8'), 'UTF-8')
. mb_strtolower(mb_substr($m[0], 1, null, 'UTF-8'), 'UTF-8'),
mb_strtolower($core, 'UTF-8')
);
}
$out[] = $pre . $coreFmt . $suf;
}
return implode(' ', $out);
};
$formatRolesCsv = static function ($csv) use ($formatRole): string {
if ($csv === null) return '';
$parts = array_filter(array_map('trim', explode(',', (string)$csv)), 'strlen');
if (empty($parts)) return '';
$parts = array_map($formatRole, $parts);
return implode(', ', $parts);
};
// --- 2) Selected user IDs: accept array, single value, or CSV ---
$selectedUsers = $request->getGet('user_ids');
if (is_string($selectedUsers)) {
// Handle CSV like "12,34,56"
$selectedUsers = array_filter(array_map('trim', explode(',', $selectedUsers)), 'strlen');
}
if (!is_array($selectedUsers)) {
$selectedUsers = $selectedUsers ? [$selectedUsers] : [];
}
// Normalize to unique ints
$selectedUserIds = array_values(array_unique(array_map(static function ($v) {
return (int) $v;
}, $selectedUsers)));
// --- 3) Base staff rows (non-parent users) ---
// fetchStaffList() should call your model method getNoParentUsersWithRole($schoolYear, $selectedUserIds)
$users = $this->fetchStaffList($schoolYear, $selectedUserIds);
// Normalize keys so the view logic is consistent:
// Expect at least: id (or users.id), firstname, lastname, email, roles (CSV of non-parent roles)
foreach ($users as &$u) {
// Ensure user_id exists
if (!isset($u['user_id'])) {
if (isset($u['users.id'])) {
$u['user_id'] = (int) $u['users.id'];
} elseif (isset($u['id'])) {
$u['user_id'] = (int) $u['id'];
} elseif (isset($u['users_id'])) {
$u['user_id'] = (int) $u['users_id'];
}
}
// Ensure roles string exists (model may already provide it as 'roles')
$rolesStr = $u['roles'] ?? '';
if ($rolesStr === '' && isset($u['role_name'])) {
// fallback if model returned single role
$rolesStr = (string) $u['role_name'];
}
// Keep raw + add formatted CSV
$u['roles_raw'] = $rolesStr;
$u['roles'] = $formatRolesCsv($rolesStr);
// Provide a role_name for legacy view code (pick a representative non-parent role)
if (empty($u['role_name'])) {
$firstRole = '';
if ($rolesStr !== '') {
$parts = array_filter(array_map('trim', explode(',', $rolesStr)));
$firstRole = $parts[0] ?? '';
}
$u['role_name'] = $firstRole;
}
// Keep raw + add formatted single role name
$u['role_name_raw'] = $u['role_name'];
$u['role_name'] = $formatRole((string)$u['role_name']);
// Defaults for class assignment (only filled for teacherish)
$u['class_section_id'] = $u['class_section_id'] ?? null;
$u['class_section_name'] = $u['class_section_name'] ?? null;
}
unset($u);
// --- 4) For teachers/assistants, fetch latest assignment in this year ---
foreach ($users as &$u) {
// Use RAW roles for detection to avoid any formatting side-effects
$rolesDetect = strtolower($u['roles_raw'] ?? ($u['role_name_raw'] ?? ''));
$isTeacherish = (strpos($rolesDetect, 'teacher') !== false) || preg_match('/\bta\b/', $rolesDetect);
if ($isTeacherish && !empty($u['user_id'])) {
$assign = $this->getLatestClassForUser((int) $u['user_id'], $schoolYear);
if ($assign) {
$u['class_section_id'] = $assign['class_section_id'] ?? null;
$u['class_section_name'] = $assign['class_section_name'] ?? null;
}
}
}
unset($u);
// --- 5) Build list of available school years (robust) ---
$db = \Config\Database::connect();
$schoolYears = [];
// Prefer teacher_class if available
try {
if (method_exists($db, 'tableExists') ? $db->tableExists('teacher_class') : true) {
$q1 = $db->table('teacher_class')
->select('DISTINCT school_year', false)
->where('school_year IS NOT NULL', null, false)
->orderBy('school_year', 'DESC')
->get()
->getResultArray();
$schoolYears = array_column($q1, 'school_year');
}
} catch (\Throwable $e) {
// ignore and try fallback
}
// Fallback to user_roles if teacher_class didn't produce anything
if (empty($schoolYears)) {
try {
if (method_exists($db, 'tableExists') ? $db->tableExists('user_roles') : true) {
$fields = $db->getFieldNames('user_roles');
if (in_array('school_year', $fields, true)) {
$q2 = $db->table('user_roles')
->select('DISTINCT school_year', false)
->where('school_year IS NOT NULL', null, false)
->orderBy('school_year', 'DESC')
->get()
->getResultArray();
$schoolYears = array_column($q2, 'school_year');
}
}
} catch (\Throwable $e) {
// swallow; leave $schoolYears empty
}
}
// --- 6) Active role tab (defensive default for the view) ---
$rolesTabs = [
'teacher' => 'Teachers',
'ta' => 'Teacher Assistants',
'admin' => 'Admins',
'staff' => 'Staff',
];
$requestedRole = $request->getGet('active_role') ?? $request->getPost('active_role') ?? null;
$activeRole = array_key_exists((string)$requestedRole, $rolesTabs) ? (string)$requestedRole : 'teacher';
// --- 7) Pack data for the view ---
$data = [
'users' => $users,
'schoolYears' => $schoolYears,
'selectedYear' => $schoolYear,
'selectedUserIds' => $selectedUserIds,
'rolesTabs' => $rolesTabs,
'active_role' => $activeRole, // prevents "Undefined array key 'active_role'"
];
return view('printables_reports/badge_form', $data);
}
protected function getUserInfoById($id, $schoolYear = null)
{
// ---- A) Resolve the user row
$u = $this->db->table('users')
->select('id AS user_id, firstname, lastname')
->where('id', $id)
->limit(1)
->get()->getRowArray();
if (!$u) {
return null;
}
$userId = (int) $u['user_id'];
$fullname = trim(($u['firstname'] ?? '') . ' ' . ($u['lastname'] ?? ''));
// ---- B) Get roles (non-parent) for this user
$rolesRows = $this->db->table('user_roles ur')
->select('r.id AS role_id, r.name AS role_name')
->join('roles r', 'r.id = ur.role_id', 'inner')
->where('ur.user_id', $userId)
->get()->getResultArray();
$allRoles = [];
foreach ($rolesRows as $rr) {
$name = trim((string)($rr['role_name'] ?? ''));
if ($name !== '' && strtolower($name) !== 'parent') {
// normalize to "Ucwords"
$allRoles[] = ucwords(strtolower($name));
}
}
$allRoles = array_values(array_unique($allRoles));
$rolesCsv = $allRoles ? implode(', ', $allRoles) : '';
// ---- C) Latest assignment from teacher_class
$tc = $this->db->table('teacher_class')
->select('class_section_id, updated_at, id, position, school_year')
->where('teacher_id', $userId);
if (!empty($schoolYear)) {
$tc->where('school_year', $schoolYear);
}
$assignment = $tc->orderBy('IFNULL(updated_at, "1970-01-01 00:00:00") DESC, id DESC', '', false)
->limit(1)
->get()->getRowArray();
$classId = $assignment['class_section_id'] ?? null;
$position = strtolower(trim((string)($assignment['position'] ?? '')));
$className = null;
if (!empty($classId)) {
$className = $this->resolveClassName($this->db, $classId);
}
// ---- D) Decide primary role label
$roleLabel = '';
if ($position === 'ta' || $position === 'teacher_assistant' || $position === 'assistant') {
$roleLabel = 'Teacher Assistant';
} elseif ($position === 'teacher') {
$roleLabel = 'Teacher';
} elseif (!empty($allRoles)) {
// Use first role from normalized list
$roleLabel = $allRoles[0];
} else {
$roleLabel = 'Staff';
}
// ---- E) Logos
$schoolLogo = $this->firstExisting([
FCPATH . 'assets/images/school_logo.png',
FCPATH . 'assets/images/logo.png',
]);
$isglLogo = $this->firstExisting([
FCPATH . 'assets/images/isgl_logo.png',
FCPATH . 'assets/images/isgl.png',
]);
// ---- F) Build job title / display title
$jobTitle = '';
if (!empty($roleLabel) && !empty($className)) {
$jobTitle = "{$roleLabel} - {$className}";
} elseif (!empty($roleLabel)) {
$jobTitle = $roleLabel;
} elseif (!empty($className)) {
$jobTitle = $className;
}
return [
'name' => $fullname !== '' ? $fullname : 'STAFF',
'role' => $roleLabel, // primary label
'roles' => $rolesCsv, // all roles (comma-separated)
'class_section_id' => $classId ? (int)$classId : null,
'class_section_name' => $className,
'job_title' => $jobTitle,
'school_name' => 'Al Rahma Sunday School',
'school_id' => $userId,
'school_logo' => $schoolLogo,
'isgl_logo' => $isglLogo,
'school_year' => $assignment['school_year'] ?? ($schoolYear ?? null),
];
}
}