roleModel = new RoleModel(); $this->userModel = new UserModel(); $this->configModel = new ConfigurationModel(); $this->userRoleModel = new UserRoleModel(); $this->semester = getSemester(); $this->schoolYear = $this->configModel->getConfig('school_year'); $this->staffAttendanceModel = new StaffAttendanceModel(); } /** * Compute all allowed absence dates (future Sundays within Sep..May for the configured school year) */ private function allowedAbsenceDates(): array { $todayStr = local_date(utc_now(), 'Y-m-d'); try { $today = new \DateTimeImmutable($todayStr); } catch (\Throwable $e) { $today = new \DateTimeImmutable(); } $sy = (string)($this->schoolYear ?? ''); $startYear = null; $endYear = null; if (preg_match('/^(\d{4})\D+(\d{4})$/', $sy, $m)) { $startYear = (int)$m[1]; $endYear = (int)$m[2]; } else { $cy = (int)date('Y'); $cm = (int)date('n'); if ($cm >= 9) { // Sep-Dec $startYear = $cy; $endYear = $cy + 1; } else { $startYear = $cy - 1; $endYear = $cy; } } try { $start = new \DateTimeImmutable(sprintf('%04d-09-01', $startYear)); $end = new \DateTimeImmutable(sprintf('%04d-05-31', $endYear)); } catch (\Throwable $e) { return []; } if ($start < $today) { $start = $today; } $dates = []; for ($cursor = $start; $cursor <= $end; $cursor = $cursor->modify('+1 day')) { if ((int)$cursor->format('w') === 0) { // Sunday $dates[] = $cursor->format('Y-m-d'); } } return $dates; } /** * Admin self-service absence/vacation page (same features as teacher page) */ public function absenceFormAdmin() { $userId = (int)(session()->get('user_id') ?? 0); if ($userId <= 0) { return redirect()->to('login')->with('error', 'Please log in first.'); } $admin = $this->userModel->find($userId); $displayName = $admin ? trim(($admin['firstname'] ?? '') . ' ' . ($admin['lastname'] ?? '')) : 'Administrator'; $semester = (string)($this->semester ?? ''); $schoolYear = (string)($this->schoolYear ?? ''); $existing = $this->staffAttendanceModel ->where('user_id', $userId) ->where('semester', $semester) ->where('school_year', $schoolYear) ->orderBy('date', 'DESC') ->findAll(); return view('administrator/absence_vacation', [ 'admin_name' => $displayName, 'semester' => $semester, 'schoolYear' => $schoolYear, 'existing' => $existing, 'availableDates' => $this->allowedAbsenceDates(), ]); } /** * Handle admin absence submission (upserts staff_attendance like the teacher flow) */ public function submitAbsenceAdmin() { $userId = (int)(session()->get('user_id') ?? 0); if ($userId <= 0) { return redirect()->to('login')->with('error', 'Please log in first.'); } $semester = (string)($this->semester ?? ''); $schoolYear = (string)($this->schoolYear ?? ''); if ($schoolYear === '') { return redirect()->to('/administrator/absence')->with('status', 'error')->with('message', 'Semester or school year not configured.'); } $semesterResolver = new SemesterRangeService($this->configModel); $dates = (array)($this->request->getPost('dates') ?? []); $reasonType = trim((string)($this->request->getPost('reason_type') ?? '')); $reasonText = trim((string)($this->request->getPost('reason') ?? '')); if ($reasonText === '') { return redirect()->to('/administrator/absence') ->with('status', 'error') ->with('message', 'Reason is required.'); } $reasonBase = ($reasonType !== '') ? strtolower($reasonType) : ''; $reason = $reasonBase !== '' ? ($reasonBase . ': ' . $reasonText) : $reasonText; $allowedDates = $this->allowedAbsenceDates(); $allowedSet = array_fill_keys($allowedDates, true); $roleName = $this->userModel->getUserRole($userId) ?: null; $saved = 0; $invalid = []; $savedDates = []; $dates = array_values(array_unique(array_map('strval', $dates))); foreach ($dates as $d) { $d = trim((string)$d); if ($d === '') continue; $dt = date_create_from_format('Y-m-d', $d); if (!$dt || $dt->format('Y-m-d') !== $d || empty($allowedSet[$d])) { $invalid[] = $d; continue; } $semesterForDate = $semesterResolver->getSemesterForDate($d); if ($semesterForDate === '') { $semesterForDate = $semester; } $ok = $this->staffAttendanceModel->upsertOne( userId: $userId, roleName: $roleName, date: $d, semester: $semesterForDate, schoolYear: $schoolYear, status: StaffAttendanceModel::STATUS_ABSENT, reason: $reason, editorId: $userId ); if ($ok) { $saved++; $savedDates[] = $d; } } if (!empty($invalid)) { return redirect()->to('/administrator/absence') ->with('status', 'error') ->with('message', 'Invalid dates: ' . implode(', ', $invalid)); } // Send notification email to principal with request details try { $user = $this->userModel->find($userId) ?: []; $fullName = trim(($user['firstname'] ?? '') . ' ' . ($user['lastname'] ?? '')) ?: 'Administrator'; $userEmail = $user['email'] ?? ''; $role = $roleName ?: 'admin'; $dateList = !empty($savedDates) ? implode(', ', $savedDates) : implode(', ', $dates); $subject = sprintf('TimeOff Request: %s (%s) — %s', $fullName, ucfirst((string)$role), $dateList ?: 'No dates'); $submittedAt = utc_now(); $body = '
' . '

New TimeOff Request

' . '

A staff time-off request was submitted from the administrator portal.

' . '' . '' . '' . '' . '' . '' . '' . '' . '' . '' . '
Name' . esc($fullName) . '
Email' . esc($userEmail) . '
Role' . esc((string)$role) . '
Semester' . esc($semester) . '
School Year' . esc($schoolYear) . '
Reason Type' . esc($reasonType ?: '-') . '
Reason' . esc($reasonText) . '
Dates' . esc($dateList ?: '-') . '
Submitted At' . esc($submittedAt) . '
' . '
'; $linkService = new StaffTimeOffLinkService(); $token = $linkService->createToken([ 'uid' => $userId, 'email' => $userEmail, 'name' => $fullName, 'role' => $role, 'dates' => $dateList ?: '-', 'reason' => $reasonText, 'reason_type' => $reasonType, 'submitted_at' => $submittedAt, 'origin' => 'administrator portal', ]); $notifyUrl = base_url('timeoff/notify/' . rawurlencode($token)); $body .= '

Click Send confirmation email to ' . esc($fullName) . ' so the staff member is notified automatically. ' . 'This link expires in 14 days.

'; $mailer = \Config\Services::emailService(); $principalEmail = env('PRINCIPAL_EMAIL') ?: 'principal@alrahmaisgl.org'; $mailer->send($principalEmail, $subject, $body, 'notifications'); } catch (\Throwable $e) { log_message('error', 'Failed to send TimeOff email (admin): ' . $e->getMessage()); } return redirect()->to('/administrator/absence') ->with('status', 'success') ->with('message', $saved . ' day(s) saved as absent.'); } public function index() { $data = [ 'users' => $this->userModel->findAll(), 'roles' => $this->roleModel->findAll(), ]; return view('administrator/dashboard', $data); } public function isadministrator() { $session = session(); $allowedRoles = [ 'administrator', 'admin', 'principal', //'vice_principal', //'vice principal', ]; $role = strtolower((string) $session->get('role')); if ($role !== '' && in_array($role, $allowedRoles, true)) { return true; } $roles = $session->get('roles'); if (is_array($roles)) { foreach ($roles as $candidate) { $candidate = strtolower((string) $candidate); if ($candidate !== '' && in_array($candidate, $allowedRoles, true)) { return true; } } } return false; } public function teachers() { return view('administrator/teachers'); // This is the correct view path } public function classes() { return view('administrator/classes'); } public function subjects() { return view('administrator/subjects'); } public function reports() { return view('administrator/reports'); } public function teacherDashboard() { return view('teacher/dashboard'); } public function parentDashboard() { return view('parent/dashboard'); } public function CourseMaterials() { return view('administrator/course_materials'); } public function administratorProfiles() { return view('administrator/administrator_profiles'); } public function payrollManagement() { return view('administrator/payroll_management'); } public function administratorAttendanceRecords() { return view('administrator/administrator_attendance_records'); } public function performanceReviews() { return view('administrator/performance_reviews'); } public function communicationLogs() { return view('administrator/communication_logs'); } public function feePaymentRecords() { return view('administrator/fee_payment_records'); } public function feedbackComplaints() { return view('administrator/feedback_complaints'); } public function feeCollection() { return view('administrator/fee_collection'); } public function expenseManagement() { return view('administrator/expense_management'); } public function budgetReports() { return view('administrator/budget_reports'); } public function scholarshipInformation() { return view('administrator/scholarship_information'); } public function attendanceRecords() { return view('administrator/attendance_records'); } public function academicPerformance() { return view('administrator/academic_performance'); } public function behaviorReports() { return view('administrator/behavior_reports'); } public function healthRecords() { return view('administrator/health_records'); } public function contactInformation() { return view('administrator/contact_information'); } private function redirectWithoutLegacyTermFilters(string $route): ?\CodeIgniter\HTTP\RedirectResponse { $legacyTermKeys = ['school_year', 'schoolYear', 'year', 'semester']; $query = $this->request->getGet(); $hasLegacyTermFilter = false; foreach ($legacyTermKeys as $key) { if (array_key_exists($key, $query)) { unset($query[$key]); $hasLegacyTermFilter = true; } } if (!$hasLegacyTermFilter) { return null; } $target = site_url($route) . (!empty($query) ? '?' . http_build_query($query) : ''); return redirect()->to($target); } public function manageUsers() {// Fetch all users $users = $this->userModel->findAll(); // Fetch roles for each user foreach ($users as &$user) { $userRoles = ($this->userRoleModel)->where('user_id', $user['id'])->findAll(); $user['roles'] = []; foreach ($userRoles as $userRole) { $role = $this->roleModel->find($userRole['role_id']); if ($role) { $user['roles'][] = $role['name']; } } } $data['users'] = $users; return view('administrator/manage_users', $data); } public function editUser($userId) {$user = $this->userModel->find($userId); // Fetch roles assigned to the user $userRoles = $this->userRoleModel->where('user_id', $userId)->findAll(); $assignedRoles = []; foreach ($userRoles as $userRole) { $role = $this->roleModel->find($userRole['role_id']); if ($role) { $assignedRoles[] = $role['id']; } } $roles = $this->roleModel->findAll(); $data = [ 'user' => $user, 'roles' => $roles, 'assignedRoles' => $assignedRoles, ]; return view('administrator/edit_user', $data); } public function updateUser() {$userId = $this->request->getPost('id'); $roleIds = $this->request->getPost('role_ids'); // Update user details $data = [ 'id' => $userId, 'firstname' => $this->request->getPost('firstname'), 'lastname' => $this->request->getPost('lastname'), 'email' => $this->request->getPost('email'), ]; $this->userModel->save($data); // Update user roles $this->userRoleModel->where('user_id', $userId)->delete(); foreach ($roleIds as $roleId) { $this->userRoleModel->save([ 'user_id' => $userId, 'role_id' => $roleId, ]); } service('staffDirectorySync')->syncUser((int) $userId); return redirect()->to('/administrator/manage-users'); } public function deleteUser($userId) {// Delete user roles first $this->userRoleModel->where('user_id', $userId)->delete(); // Delete the user $this->userModel->delete($userId); return redirect()->to('/administrator/manage-users'); } private function canManageAdminNotifications(): bool { $session = session(); if (! $session->get('is_logged_in')) { return false; } $role = trim((string) ($session->get('role') ?? '')); if ($role === '') { return false; } $excluded = array_map( fn ($value) => strtolower(trim((string) $value)), service('adminNotificationSettings')->excludedRoles() ); return !in_array(strtolower($role), $excluded, true); } public function administratorDashboard() { helper('url'); $searchData = service('administratorDashboard')->search((string) $this->request->getGet('query')); return view('administrator/administratordashboard', array_merge($searchData, [ 'dashboardEndpoint' => site_url('api/administrator/dashboard'), ])); } public function dashboardMetrics() { return $this->response->setJSON( service('administratorDashboard')->metrics((string) $this->schoolYear, (string) $this->semester) ); } public function userSearch() { $data = service('administratorDashboard')->search((string) $this->request->getGet('query')); return view('administrator/search_results', $data); } public function teacherSubmissionsReport() { $semester = (string) ($this->semester !== '' ? $this->semester : (getSemester() ?? $this->semester ?? '')); $schoolYear = trim((string) ($this->request->getGet('school_year') ?? '')); if ($schoolYear === '') { $schoolYear = $this->currentSchoolYearName((string) ($this->schoolYear ?? '')); } $lowProgressRaw = (string) $this->request->getGet('low_progress_sections'); $lowProgressSectionIds = array_values(array_unique(array_filter(array_map( 'intval', preg_split('/\s*,\s*/', $lowProgressRaw, -1, PREG_SPLIT_NO_EMPTY) )))); return view( 'administrator/teacher_submissions', service('teacherSubmissionReport')->buildReport($semester, $schoolYear, $lowProgressSectionIds) ); } public function sendTeacherSubmissionNotifications() { $result = service('teacherSubmissionReport')->sendNotifications( (array) $this->request->getPost(), (string) ($this->semester !== '' ? $this->semester : (getSemester() ?? '')), (int) (session()->get('user_id') ?? 0) ); if (($result['redirect'] ?? '') === 'login') { return redirect()->to('/login'); } return redirect()->back()->with((string) ($result['type'] ?? 'info'), (string) ($result['message'] ?? '')); } public function notificationsAlerts() { if (!$this->canManageAdminNotifications()) { return redirect()->to('/login'); } return view('administrator/notifications_alerts', service('adminNotificationSettings')->alertsPage()); } public function saveNotificationSubjects() { if (!$this->canManageAdminNotifications()) { return redirect()->to('/login'); } $result = service('adminNotificationSettings')->saveSubjects($this->request->getPost('subjects')); return redirect()->to('/administrator/notifications_alerts') ->with((string) ($result['type'] ?? 'info'), (string) ($result['message'] ?? '')); } public function printNotificationRecipients() { if (!$this->canManageAdminNotifications()) { return redirect()->to('/login'); } return view('administrator/print_notification_admins', service('adminNotificationSettings')->printRecipientsPage()); } public function savePrintNotificationRecipients() { if (!$this->canManageAdminNotifications()) { return redirect()->to('/login'); } $result = service('adminNotificationSettings')->savePrintRecipients((array) $this->request->getPost('notify')); return redirect()->to('/administrator/print-notifications') ->with((string) ($result['type'] ?? 'info'), (string) ($result['message'] ?? '')); } public function studentProfiles() { $selectedYear = $this->currentSchoolYearName((string) ($this->schoolYear ?? '')); return view( 'administrator/student_profiles', service('administratorDirectory')->studentProfiles($selectedYear) ); } public function parentProfiles() { if ($redirect = $this->redirectWithoutLegacyTermFilters('administrator/parent_profiles')) { return $redirect; } return view('administrator/parent_profile', service('administratorDirectory')->parentProfiles()); } public function showEnrollmentWithdrawalPage() { try { $schoolYearContext = $this->resolveSchoolYearContext(); $selectedYear = $schoolYearContext->yearName(); $payload = service('enrollmentWithdrawal')->buildRoster($selectedYear, (string) $this->semester); return view('enroll_withdraw/enrollment_withdrawal', [ 'students' => $payload['students'], 'classes' => $payload['classes'], 'selectedYear' => $selectedYear, 'currentYear' => $selectedYear, 'isCurrentYear' => ! $schoolYearContext->isReadonly(), 'missingYear' => $selectedYear === '', ]); } catch (\Throwable $e) { log_message('error', 'Enrollment/Withdrawal page error: {msg}', ['msg' => $e->getMessage()]); return view('errors/html/error_500'); } } public function enrollmentWithdrawalData() { try { $selectedYear = $this->currentSchoolYearName((string) ($this->schoolYear ?? '')); $payload = service('enrollmentWithdrawal')->buildRoster($selectedYear, (string) $this->semester); return $this->response->setJSON([ 'students' => $payload['students'], 'classes' => $payload['classes'], 'csrfHash' => csrf_hash(), 'semester' => (string) $this->semester, 'school_year' => (string) $selectedYear, ]); } catch (\Throwable $e) { log_message('error', 'enrollmentWithdrawalData error: {msg}', ['msg' => $e->getMessage()]); return $this->response->setStatusCode(500)->setJSON(['error' => 'Server error']); } } public function showNewStudents() { return view( 'enroll_withdraw/new-students', service('enrollmentWithdrawal')->newStudents((string) $this->schoolYear) ); } public function adminEnrollmentWithdrawalHandler() { $result = service('enrollmentWithdrawal')->updateStatuses( $this->request->getPost('enrollment_status'), (string) $this->schoolYear, (string) $this->semester, (int) (session()->get('user_id') ?? 0) ?: null ); return redirect()->to(base_url('enroll_withdraw/enrollment_withdrawal')) ->with(!empty($result['ok']) ? 'success' : 'error', (string) ($result['message'] ?? '')); } }