659 lines
24 KiB
PHP
Executable File
659 lines
24 KiB
PHP
Executable File
<?php
|
|
|
|
namespace App\Controllers\View;
|
|
use App\Controllers\BaseController;
|
|
|
|
use App\Models\CalendarModel;
|
|
use App\Models\ConfigurationModel;
|
|
use App\Models\TeacherModel;
|
|
use App\Models\UserModel;
|
|
use App\Models\ParentMeetingScheduleModel;
|
|
use App\Services\EmailService;
|
|
|
|
|
|
class SchoolCalendarController extends BaseController
|
|
{
|
|
protected $calendarModel;
|
|
protected $configModel;
|
|
protected $semester;
|
|
protected $schoolYear;
|
|
protected $db;
|
|
protected $eventTypes = [
|
|
'1st Semester Scores',
|
|
'2nd Semester Scores',
|
|
'Break',
|
|
'Event',
|
|
'Final',
|
|
'Final Exam Draft',
|
|
'Midterm',
|
|
'Midterm Exam Draft',
|
|
];
|
|
|
|
protected EmailService $emailService;
|
|
protected UserModel $userModel;
|
|
protected TeacherModel $teacherModel;
|
|
protected ParentMeetingScheduleModel $meetingModel;
|
|
|
|
public function __construct()
|
|
{
|
|
// Load the models
|
|
$this->calendarModel = new CalendarModel();
|
|
$this->configModel = new ConfigurationModel();
|
|
$this->userModel = new UserModel();
|
|
$this->teacherModel = new TeacherModel();
|
|
$this->emailService = new EmailService();
|
|
$this->meetingModel = new ParentMeetingScheduleModel();
|
|
$this->db = \Config\Database::connect();
|
|
$this->schoolYear = $this->configModel->getConfig('school_year');
|
|
$this->semester = $this->configModel->getConfig('semester');
|
|
|
|
// Load helpers
|
|
helper(['form', 'url']);
|
|
}
|
|
|
|
public function index()
|
|
{
|
|
// Get school_year / semester from query string or defaults
|
|
$syParam = $this->request->getGet('school_year');
|
|
$semParam = $this->request->getGet('semester');
|
|
$schoolYear = $syParam ?: $this->schoolYear;
|
|
$semester = $semParam ?: '';
|
|
|
|
if ($semester !== '') {
|
|
$events = $this->calendarModel->getEventsBySchoolYearAndSemester($schoolYear, $semester);
|
|
} else {
|
|
$events = $this->calendarModel->getEventsBySchoolYear($schoolYear);
|
|
}
|
|
|
|
return view('administrator/calendar_view', [
|
|
'events' => $events,
|
|
'schoolYear' => $schoolYear,
|
|
'semester' => $semester,
|
|
]);
|
|
}
|
|
|
|
|
|
public function edit($id)
|
|
{
|
|
$event = $this->calendarModel->find($id);
|
|
|
|
if (!$event) {
|
|
return redirect()->to('/administrator/calendar_view')->with('error', 'Event not found');
|
|
}
|
|
|
|
return view('administrator/calendar_edit', [
|
|
'event' => $event,
|
|
'eventTypes' => $this->eventTypes,
|
|
]);
|
|
}
|
|
|
|
|
|
public function update($id)
|
|
{
|
|
// Fetch the event by ID
|
|
$event = $this->calendarModel->find($id);
|
|
|
|
if (!$event) {
|
|
return redirect()->to('/administrator/calendar_view')->with('error', 'Event not found.');
|
|
}
|
|
|
|
if (strtolower($this->request->getMethod()) === 'post') {
|
|
$validation = $this->validate([
|
|
'title' => 'required|min_length[3]',
|
|
'start' => 'required|valid_date[Y-m-d]',
|
|
]);
|
|
|
|
if (!$validation) {
|
|
return view('administrator/calendar_edit', [
|
|
'validation' => $this->validator,
|
|
'event' => $event,
|
|
'eventTypes' => $this->eventTypes,
|
|
]);
|
|
}
|
|
|
|
$emailTargets = $this->emailNotificationTargetsFromRequest();
|
|
|
|
$data = [
|
|
'title' => $this->request->getPost('title'),
|
|
'description' => $this->request->getPost('description'),
|
|
'event_type' => $this->request->getPost('event_type') ?: null,
|
|
'date' => $this->request->getPost('start'),
|
|
'notify_parent' => $this->request->getPost('notify_parent') ? 1 : 0,
|
|
'notify_teacher' => $this->request->getPost('notify_teacher') ? 1 : 0,
|
|
'notify_admin' => $this->request->getPost('notify_admin') ? 1 : 0,
|
|
'no_school' => $this->request->getPost('no_school') ? 1 : 0,
|
|
'school_year' => $this->request->getPost('school_year') ?: $this->schoolYear,
|
|
'semester' => $this->request->getPost('semester') ?: $this->semester,
|
|
];
|
|
if (!$this->calendarModel->supportsEventType()) {
|
|
unset($data['event_type']);
|
|
}
|
|
|
|
if ($this->calendarModel->update($id, $data)) {
|
|
$updatedEvent = $this->calendarModel->find($id);
|
|
$emailStatus = $this->dispatchCalendarNotificationEmails($updatedEvent, $emailTargets);
|
|
$filters = [
|
|
'school_year' => $data['school_year'],
|
|
'semester' => $data['semester'],
|
|
];
|
|
$redirect = redirect()->to($this->buildCalendarViewUrl($filters))->with('success', 'Event updated successfully');
|
|
if ($emailFlash = $this->buildEmailFlash($emailStatus)) {
|
|
$redirect = $redirect
|
|
->with('email_status', $emailFlash['message'])
|
|
->with('email_status_class', $emailFlash['class']);
|
|
}
|
|
return $redirect;
|
|
} else {
|
|
return redirect()->back()->with('error', 'Failed to update the event.');
|
|
}
|
|
}
|
|
|
|
return view('administrator/calendar_edit', [
|
|
'event' => $event,
|
|
'eventTypes' => $this->eventTypes,
|
|
]);
|
|
}
|
|
|
|
public function addEvent()
|
|
{
|
|
if (strtolower($this->request->getMethod()) === 'post') {
|
|
$validation = $this->validate([
|
|
'title' => 'required|min_length[3]',
|
|
'start' => 'required|valid_date[Y-m-d]',
|
|
]);
|
|
|
|
if (!$validation) {
|
|
return view('administrator/calendar_add_event', [
|
|
'validation' => $this->validator,
|
|
'eventTypes' => $this->eventTypes,
|
|
]);
|
|
}
|
|
|
|
$emailTargets = $this->emailNotificationTargetsFromRequest();
|
|
|
|
// Prepare data
|
|
$data = [
|
|
'title' => $this->request->getPost('title'),
|
|
'description' => $this->request->getPost('description'),
|
|
'event_type' => $this->request->getPost('event_type') ?: null,
|
|
'date' => $this->request->getPost('start'),
|
|
'notify_parent' => $this->request->getPost('notify_parent') ? 1 : 0,
|
|
'notify_teacher' => $this->request->getPost('notify_teacher') ? 1 : 0,
|
|
'notify_admin' => $this->request->getPost('notify_admin') ? 1 : 0,
|
|
'no_school' => $this->request->getPost('no_school') ? 1 : 0,
|
|
'school_year' => $this->request->getPost('school_year') ?: $this->schoolYear,
|
|
'semester' => $this->request->getPost('semester') ?: $this->semester,
|
|
];
|
|
if (!$this->calendarModel->supportsEventType()) {
|
|
unset($data['event_type']);
|
|
}
|
|
|
|
if ($this->calendarModel->save($data)) {
|
|
$insertedId = $this->calendarModel->getInsertID();
|
|
$savedEvent = $this->calendarModel->find($insertedId);
|
|
$emailStatus = $this->dispatchCalendarNotificationEmails($savedEvent, $emailTargets);
|
|
log_message('info', 'Event saved successfully: ' . print_r($data, true));
|
|
$filters = [
|
|
'school_year' => $data['school_year'],
|
|
'semester' => $data['semester'],
|
|
];
|
|
$redirect = redirect()->to($this->buildCalendarViewUrl($filters))->with('success', 'Event added successfully');
|
|
if ($emailFlash = $this->buildEmailFlash($emailStatus)) {
|
|
$redirect = $redirect
|
|
->with('email_status', $emailFlash['message'])
|
|
->with('email_status_class', $emailFlash['class']);
|
|
}
|
|
return $redirect;
|
|
} else {
|
|
log_message('error', 'Failed to save event: ' . print_r($data, true));
|
|
return redirect()->back()->with('error', 'Failed to save the event.');
|
|
}
|
|
}
|
|
|
|
return view('administrator/calendar_add_event', [
|
|
'eventTypes' => $this->eventTypes,
|
|
]);
|
|
}
|
|
|
|
protected function emailNotificationTargetsFromRequest(): array
|
|
{
|
|
return [
|
|
'parents' => (bool) $this->request->getPost('send_email_parent'),
|
|
'teachers' => (bool) $this->request->getPost('send_email_teacher'),
|
|
'admins' => (bool) $this->request->getPost('send_email_admin'),
|
|
];
|
|
}
|
|
|
|
protected function dispatchCalendarNotificationEmails(array $event = null, array $targets = []): array
|
|
{
|
|
if (empty($event) || empty(array_filter($targets))) {
|
|
return [];
|
|
}
|
|
|
|
$subject = $this->buildCalendarEmailSubject($event);
|
|
$innerBody = view('emails/calendar_notification', [
|
|
'event' => $event,
|
|
'calendarUrl' => base_url('administrator/calendar_view'),
|
|
]);
|
|
$body = view('emails/_wrap_layout', [
|
|
'title' => $subject,
|
|
'subject' => $subject,
|
|
'body_html' => $innerBody,
|
|
]);
|
|
|
|
$results = [];
|
|
foreach ($targets as $group => $enabled) {
|
|
if (!$enabled) {
|
|
continue;
|
|
}
|
|
|
|
$emails = $this->collectEmailsForGroup($group);
|
|
if (empty($emails)) {
|
|
log_message('warning', "SchoolCalendarController: no recipient emails for {$group}.");
|
|
$results[$group] = null;
|
|
continue;
|
|
}
|
|
|
|
$ok = $this->emailService->sendBcc($emails, $subject, $body, 'general');
|
|
if (!$ok) {
|
|
log_message('warning', "SchoolCalendarController: failed to send calendar notification to {$group}.");
|
|
}
|
|
$results[$group] = $ok;
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
protected function collectEmailsForGroup(string $group): array
|
|
{
|
|
switch ($group) {
|
|
case 'parents':
|
|
return $this->normalizeEmailAddresses($this->userModel->getParents());
|
|
case 'teachers':
|
|
return $this->normalizeEmailAddresses($this->teacherModel->getAllTeachers());
|
|
case 'admins':
|
|
return $this->normalizeEmailAddresses($this->fetchAdminRows());
|
|
default:
|
|
return [];
|
|
}
|
|
}
|
|
|
|
protected function normalizeEmailAddresses(array $rows): array
|
|
{
|
|
$emails = [];
|
|
foreach ($rows as $row) {
|
|
$email = trim((string) ($row['email'] ?? ''));
|
|
if ($email === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
|
continue;
|
|
}
|
|
$emails[$email] = true;
|
|
}
|
|
return array_values(array_keys($emails));
|
|
}
|
|
|
|
protected function fetchAdminRows(): array
|
|
{
|
|
$excluded = ['guest', 'teacher', 'teacher_assistant', 'parent'];
|
|
$escaped = array_map(static fn($value) => "'" . addslashes($value) . "'", $excluded);
|
|
$excludedList = implode(', ', $escaped);
|
|
|
|
$builder = $this->db->table('users u')
|
|
->select("u.id, u.firstname, u.lastname, u.email, MAX(CASE WHEN LOWER(COALESCE(r.slug, r.name)) NOT IN({$excludedList}) THEN 1 ELSE 0 END) AS is_admin", false)
|
|
->join('user_roles ur', 'ur.user_id = u.id')
|
|
->join('roles r', 'r.id = ur.role_id')
|
|
->where('r.is_active', 1)
|
|
->where('u.email IS NOT NULL')
|
|
->where('u.email !=', '')
|
|
->groupBy('u.id, u.firstname, u.lastname, u.email')
|
|
->having('is_admin', 1);
|
|
|
|
return $builder->get()->getResultArray();
|
|
}
|
|
|
|
protected function buildCalendarEmailSubject(array $event): string
|
|
{
|
|
$title = trim((string) ($event['title'] ?? 'School Calendar Update'));
|
|
$typeSegment = !empty($event['event_type']) ? ' (' . $event['event_type'] . ')' : '';
|
|
return 'School Calendar: ' . $title . $typeSegment;
|
|
}
|
|
|
|
protected function buildEmailFlash(array $status): ?array
|
|
{
|
|
if (empty($status)) {
|
|
return null;
|
|
}
|
|
|
|
$sent = [];
|
|
$failed = [];
|
|
$missing = [];
|
|
|
|
foreach ($status as $group => $result) {
|
|
if ($result === true) {
|
|
$sent[] = $group;
|
|
} elseif ($result === false) {
|
|
$failed[] = $group;
|
|
} elseif ($result === null) {
|
|
$missing[] = $group;
|
|
}
|
|
}
|
|
|
|
$parts = [];
|
|
if (!empty($sent)) {
|
|
$parts[] = 'Email sent to ' . $this->humanizeGroupList($sent) . '.';
|
|
}
|
|
if (!empty($failed)) {
|
|
$parts[] = 'Sending failed for ' . $this->humanizeGroupList($failed) . '.';
|
|
}
|
|
if (!empty($missing)) {
|
|
$parts[] = 'No recipient emails for ' . $this->humanizeGroupList($missing) . '.';
|
|
}
|
|
|
|
if (empty($parts)) {
|
|
return null;
|
|
}
|
|
|
|
return [
|
|
'message' => implode(' ', $parts),
|
|
'class' => empty($failed) && empty($missing) ? 'info' : 'warning',
|
|
];
|
|
}
|
|
|
|
protected function humanizeGroupList(array $groups): string
|
|
{
|
|
$groups = array_map('strval', $groups);
|
|
if (empty($groups)) {
|
|
return '';
|
|
}
|
|
if (count($groups) === 1) {
|
|
return $groups[0];
|
|
}
|
|
$last = array_pop($groups);
|
|
return implode(', ', $groups) . ' and ' . $last;
|
|
}
|
|
|
|
protected function buildCalendarViewUrl(array $filters = []): string
|
|
{
|
|
$allowed = ['school_year', 'semester'];
|
|
$params = [];
|
|
foreach ($allowed as $key) {
|
|
$value = $filters[$key] ?? '';
|
|
if ($value !== '') {
|
|
$params[$key] = $value;
|
|
}
|
|
}
|
|
$query = http_build_query($params);
|
|
return '/administrator/calendar_view' . ($query !== '' ? ('?' . $query) : '');
|
|
}
|
|
|
|
public function delete($id)
|
|
{
|
|
|
|
// Check if the event exists
|
|
$event = $this->calendarModel->find($id);
|
|
if (!$event) {
|
|
return redirect()->to('/administrator/calendar_view')->with('error', 'Event not found.');
|
|
}
|
|
|
|
// Delete the event
|
|
if ($this->calendarModel->delete($id)) {
|
|
return redirect()->to('/administrator/calendar_view')->with('success', 'Event deleted successfully');
|
|
} else {
|
|
return redirect()->to('/administrator/calendar_view')->with('error', 'Failed to delete the event.');
|
|
}
|
|
}
|
|
|
|
public function calendarParentView()
|
|
{
|
|
// Transform the events into the format FullCalendar expects
|
|
$formattedEvents = $this->calendarView('parent');
|
|
|
|
// Pass the formatted events to the view
|
|
return view('/parent/calendar', ['events' => $formattedEvents]);
|
|
}
|
|
|
|
public function calendarTeacherView()
|
|
{
|
|
// Transform the events into the format FullCalendar expects
|
|
$formattedEvents = $this->calendarView('teacher');
|
|
|
|
// Pass the formatted events to the view
|
|
return view('/teacher/calendar', ['events' => $formattedEvents]);
|
|
}
|
|
|
|
public function calendarAdminView()
|
|
{
|
|
// Transform the events into the format FullCalendar expects
|
|
$formattedEvents = $this->calendarView('admin');
|
|
|
|
// Pass the formatted events to the view
|
|
return view('administrator/calendar', ['events' => $formattedEvents]);
|
|
}
|
|
|
|
private function calendarView(?string $audience = null)
|
|
{
|
|
// Filter events by school year
|
|
$events = $this->calendarModel->getEventsBySchoolYear($this->schoolYear);
|
|
|
|
// Apply audience visibility rules:
|
|
// - If no checkboxes selected (all notify_* = 0) => visible to everyone
|
|
// - Otherwise visible only to the selected audience
|
|
if (in_array($audience, ['parent', 'teacher', 'admin'], true)) {
|
|
$events = array_values(array_filter($events, function ($event) use ($audience) {
|
|
$p = (int) ($event['notify_parent'] ?? 0);
|
|
$t = (int) ($event['notify_teacher'] ?? 0);
|
|
$a = (int) ($event['notify_admin'] ?? 0);
|
|
$ns = (int) ($event['no_school'] ?? 0);
|
|
$hasAny = ($p + $t + $a) > 0;
|
|
|
|
// If marked as no_school, it should be visible to all audiences
|
|
if ($ns === 1) {
|
|
return true;
|
|
}
|
|
|
|
if (!$hasAny) {
|
|
// No audience explicitly selected: show to everyone
|
|
return true;
|
|
}
|
|
|
|
// Audience explicitly selected: restrict to matching audience
|
|
if ($audience === 'parent') { return $p === 1; }
|
|
if ($audience === 'teacher') { return $t === 1; }
|
|
if ($audience === 'admin') { return $a === 1; }
|
|
return true;
|
|
}));
|
|
}
|
|
|
|
// Format for FullCalendar
|
|
$aud = $audience; // capture for closure
|
|
$formattedEvents = array_map(function ($event) use ($aud) {
|
|
$p = (int) ($event['notify_parent'] ?? 0);
|
|
$t = (int) ($event['notify_teacher'] ?? 0);
|
|
$a = (int) ($event['notify_admin'] ?? 0);
|
|
$ns = (int) ($event['no_school'] ?? 0);
|
|
|
|
$backgroundColor = null;
|
|
// Color no-school events yellow on teacher and parent calendars
|
|
if (in_array($aud, ['teacher', 'parent'], true) && $ns === 1) {
|
|
$backgroundColor = '#ffc107'; // yellow
|
|
}
|
|
|
|
// Highlight events dedicated to staff (teachers/admins) only in green
|
|
// Definition: visible to staff but not to parents, and not a no-school marker
|
|
if (in_array($aud, ['teacher', 'admin'], true) && $backgroundColor === null) {
|
|
$isStaffOnly = ($p === 0 && ($t === 1 || $a === 1) && $ns === 0);
|
|
if ($isStaffOnly) {
|
|
// Use a distinct green to differentiate from the default blue
|
|
$backgroundColor = '#28a745';
|
|
}
|
|
}
|
|
|
|
// Title-casing for "no school" phrase when marked as no_school
|
|
$title = $event['title'] ?? 'Untitled';
|
|
if ($ns === 1 && is_string($title)) {
|
|
// Replace common variants with Title Case
|
|
$title = preg_replace('/\bno\s*[- ]\s*school\b/i', 'No School', $title);
|
|
}
|
|
|
|
return [
|
|
'id' => $event['id'],
|
|
'title' => $title,
|
|
'start' => $event['date'] ?? '', // FullCalendar expects 'start'
|
|
'description' => $event['description'] ?? '',
|
|
'extendedProps' => [
|
|
'semester' => $event['semester'] ?? '',
|
|
'school_year' => $event['school_year'] ?? '',
|
|
'notify_parent' => $p,
|
|
'notify_teacher' => $t,
|
|
'notify_admin' => $a,
|
|
'no_school' => $ns,
|
|
'backgroundColor' => $backgroundColor,
|
|
]
|
|
];
|
|
}, $events);
|
|
|
|
// Append parent meeting schedules
|
|
$meetingEvents = $this->formatMeetingEvents($audience);
|
|
if (!empty($meetingEvents)) {
|
|
$formattedEvents = array_values(array_merge($formattedEvents, $meetingEvents));
|
|
}
|
|
|
|
return $formattedEvents;
|
|
}
|
|
|
|
private function formatMeetingEvents(?string $audience = null): array
|
|
{
|
|
$aud = $audience ?? '';
|
|
$builder = $this->meetingModel
|
|
->where('school_year', $this->schoolYear)
|
|
->groupStart()
|
|
->where('status', 'scheduled')
|
|
->orWhere('status', 'Scheduled')
|
|
->orWhere('status', '')
|
|
->orWhere('status', null)
|
|
->groupEnd();
|
|
|
|
if ($aud === 'parent') {
|
|
$parentUserId = (int) (session()->get('user_id') ?? 0);
|
|
if ($parentUserId <= 0) {
|
|
return [];
|
|
}
|
|
$studentIds = $this->getStudentIdsForParent($parentUserId);
|
|
if (!empty($studentIds)) {
|
|
$builder = $builder->groupStart()
|
|
->where('parent_user_id', $parentUserId)
|
|
->orWhereIn('student_id', $studentIds)
|
|
->groupEnd();
|
|
} else {
|
|
$builder = $builder->where('parent_user_id', $parentUserId);
|
|
}
|
|
}
|
|
|
|
$rows = $builder->orderBy('date', 'ASC')->findAll();
|
|
if (empty($rows)) {
|
|
// Fallback: show any meetings if school_year was missing/mismatched
|
|
$fallback = $this->meetingModel
|
|
->groupStart()
|
|
->where('status', 'scheduled')
|
|
->orWhere('status', 'Scheduled')
|
|
->orWhere('status', '')
|
|
->orWhere('status', null)
|
|
->groupEnd();
|
|
|
|
if ($aud === 'parent') {
|
|
$parentUserId = (int) (session()->get('user_id') ?? 0);
|
|
if ($parentUserId > 0) {
|
|
$studentIds = $this->getStudentIdsForParent($parentUserId);
|
|
if (!empty($studentIds)) {
|
|
$fallback = $fallback->groupStart()
|
|
->where('parent_user_id', $parentUserId)
|
|
->orWhereIn('student_id', $studentIds)
|
|
->groupEnd();
|
|
} else {
|
|
$fallback = $fallback->where('parent_user_id', $parentUserId);
|
|
}
|
|
}
|
|
}
|
|
|
|
$rows = $fallback->orderBy('date', 'ASC')->findAll();
|
|
}
|
|
if (empty($rows)) {
|
|
return [];
|
|
}
|
|
|
|
return array_map(function ($row) use ($aud) {
|
|
$parentName = (string)($row['parent_name'] ?? 'Parent');
|
|
$studentName = (string)($row['student_name'] ?? 'Student');
|
|
$time = trim((string)($row['time'] ?? ''));
|
|
$timeLabel = $time !== '' ? (' ' . $time) : '';
|
|
|
|
if ($aud === 'admin') {
|
|
$title = 'Admin Meeting';
|
|
} else {
|
|
$title = 'Parent Meeting';
|
|
}
|
|
|
|
$descParts = [];
|
|
$descParts[] = 'Student: ' . $studentName;
|
|
$descParts[] = 'Parent: ' . $parentName;
|
|
if (!empty($row['class_section_name'])) {
|
|
$descParts[] = 'Section: ' . $row['class_section_name'];
|
|
}
|
|
$descParts[] = 'Date/Time: ' . ($row['date'] ?? '') . $timeLabel;
|
|
if (!empty($row['notes'])) {
|
|
$descParts[] = 'Notes: ' . $row['notes'];
|
|
}
|
|
|
|
$start = (string)($row['date'] ?? '');
|
|
if ($start !== '' && $time !== '') {
|
|
$start = $start . 'T' . $time;
|
|
}
|
|
|
|
return [
|
|
'id' => 'pm-' . (int)($row['id'] ?? 0),
|
|
'title' => $title,
|
|
'start' => $start,
|
|
'description' => implode("\n", $descParts),
|
|
'extendedProps' => [
|
|
'semester' => $row['semester'] ?? '',
|
|
'school_year' => $row['school_year'] ?? '',
|
|
'notify_parent' => 1,
|
|
'notify_admin' => 1,
|
|
'notify_teacher' => 0,
|
|
'no_school' => 0,
|
|
'backgroundColor' => '#6f42c1',
|
|
],
|
|
];
|
|
}, $rows);
|
|
}
|
|
|
|
private function getStudentIdsForParent(int $parentUserId): array
|
|
{
|
|
try {
|
|
$rows = $this->db->query(
|
|
"SELECT DISTINCT fs.student_id
|
|
FROM family_guardians fg
|
|
JOIN family_students fs ON fs.family_id = fg.family_id
|
|
WHERE fg.user_id = ?",
|
|
[$parentUserId]
|
|
)->getResultArray();
|
|
$ids = array_map(static fn($r) => (int)($r['student_id'] ?? 0), $rows);
|
|
|
|
// Legacy fallback: students.parent_id
|
|
$rows2 = $this->db->query(
|
|
"SELECT id AS student_id
|
|
FROM students
|
|
WHERE parent_id = ?",
|
|
[$parentUserId]
|
|
)->getResultArray();
|
|
foreach ($rows2 as $r) {
|
|
$ids[] = (int)($r['student_id'] ?? 0);
|
|
}
|
|
|
|
return array_values(array_filter($ids, static fn($id) => $id > 0));
|
|
} catch (\Throwable $e) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
}
|