roles if the table is * unavailable. */ private static function adminRoleTokens(): array { static $cache = null; if ($cache !== null) return $cache; $normalize = static function (string $v): string { $v = strtolower(trim($v)); return str_replace([' ', '-'], '_', $v); }; $tokens = []; try { $model = new RoleModel(); $rows = $model ->select('name, slug') ->where('is_active', 1) ->findAll(); // Slugs that are explicitly NOT admin-like $excluded = ['guest','parent','student','teacher','teacher_assistant','ta']; $excluded = array_map($normalize, $excluded); foreach ($rows as $r) { $name = $normalize((string)($r['name'] ?? '')); $slug = $normalize((string)($r['slug'] ?? $name)); if ($name === '' && $slug === '') continue; if (in_array($slug, $excluded, true) || in_array($name, $excluded, true)) { continue; } if ($name !== '') $tokens[$name] = true; if ($slug !== '') $tokens[$slug] = true; } // Safety net: ensure common admin labels are present if defined foreach (['administrator','admin','principal','vice_principal','vice-principal'] as $s) { $tokens[$normalize($s)] = true; } } catch (\Throwable $e) { // Fallback: use Roles config (if provided) try { $cfg = config('Roles'); if ($cfg && is_array($cfg->roles ?? null)) { foreach ($cfg->roles as $r) { $tokens[$normalize((string)$r)] = true; } } } catch (\Throwable $e2) { // minimal fallback foreach (['administrator','admin','principal','vice_principal'] as $r) { $tokens[$normalize($r)] = true; } } } return $cache = array_keys($tokens); } public static function canManageEarlyDismissals(array $userRoles): bool { $normalizedRoles = array_map(static function ($r) { $r = strtolower((string) $r); $r = str_replace([' ', '-'], '_', $r); return $r; }, $userRoles); $adminTokens = self::adminRoleTokens(); $isAdminLike = count(array_intersect($normalizedRoles, $adminTokens)) > 0; $isTeacher = in_array('teacher', $normalizedRoles, true) || in_array('teacher_assistant', $normalizedRoles, true); return $isAdminLike || $isTeacher; } public static function canEditDay(array $day, array $user): bool { // Normalize roles (e.g., "Vice Principal" -> "vice_principal") $roles = array_map(static function ($r) { $r = strtolower((string) $r); $r = str_replace([' ', '-'], '_', $r); return $r; }, (array) ($user['roles'] ?? [])); // Helper: permissions may be provided either as an associative map // ['attendance.admin_edit' => true] or as a flat list ['attendance.admin_edit'] $perms = $user['permissions'] ?? []; $hasPerm = static function (string $key) use ($perms): bool { if (is_array($perms)) { // Associative-style map if (array_key_exists($key, $perms)) { return (bool) $perms[$key]; } // Flat list of strings return in_array($key, $perms, true); } return false; }; // Determine admin-like status from roles table $adminTokens = self::adminRoleTokens(); $isAdminLike = count(array_intersect($roles, $adminTokens)) > 0; // Allow via explicit permissions as well $canAdminEditSubmitted = $hasPerm('attendance.admin_edit') || $hasPerm('attendance_admin_edit'); $canOverridePublished = $hasPerm('attendance.override_publish') || $hasPerm('attendance_override_publish'); switch ($day['status']) { case 'draft': // Open for teachers and admins return true; case 'submitted': // Teachers locked; allow admin-like users, or holders of admin-edit permission return $isAdminLike || $canAdminEditSubmitted; case 'published': // Only allow if explicit override permission is granted return $canOverridePublished; default: return false; } } }