332 lines
12 KiB
PHP
Executable File
332 lines
12 KiB
PHP
Executable File
<?php
|
|
|
|
namespace App\Http\Controllers\Api;
|
|
|
|
use App\Models\Calendar;
|
|
use App\Models\Configuration;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
|
|
class SchoolCalendarController extends BaseApiController
|
|
{
|
|
protected Calendar $calendar;
|
|
protected Configuration $config;
|
|
protected string $schoolYear;
|
|
protected string $semester;
|
|
|
|
public function __construct()
|
|
{
|
|
parent::__construct();
|
|
$this->calendar = model(Calendar::class);
|
|
$this->config = model(Configuration::class);
|
|
$this->schoolYear = (string) ($this->config->getConfig('school_year') ?? '');
|
|
$this->semester = (string) ($this->config->getConfig('semester') ?? '');
|
|
}
|
|
|
|
/**
|
|
* GET /api/v1/school-calendar
|
|
* Get calendar events filtered by school_year and optional semester
|
|
*/
|
|
public function index()
|
|
{
|
|
$schoolYear = (string) ($this->request->getGet('school_year') ?? $this->schoolYear);
|
|
$semester = $this->request->getGet('semester');
|
|
|
|
try {
|
|
$query = $this->calendar->newQuery();
|
|
|
|
if ($schoolYear !== '') {
|
|
$query->where('school_year', $schoolYear);
|
|
}
|
|
|
|
if ($semester !== null && $semester !== '') {
|
|
$events = $query->where('semester', $semester)->orderBy('date', 'ASC')->get()->toArray();
|
|
} else {
|
|
$events = $query->orderBy('date', 'ASC')->get()->toArray();
|
|
}
|
|
|
|
return $this->success([
|
|
'events' => $events,
|
|
'school_year' => $schoolYear,
|
|
'semester' => $semester,
|
|
], 'Calendar events retrieved successfully');
|
|
} catch (\Throwable $e) {
|
|
log_message('error', 'School calendar error: ' . $e->getMessage());
|
|
return $this->respondError('Failed to retrieve calendar events', Response::HTTP_INTERNAL_SERVER_ERROR);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* GET /api/v1/school-calendar/{id}
|
|
* Get a single calendar event
|
|
*/
|
|
public function show($id = null)
|
|
{
|
|
$event = $this->calendar->find($id);
|
|
if (!$event) {
|
|
return $this->error('Event not found', Response::HTTP_NOT_FOUND);
|
|
}
|
|
|
|
return $this->success($event, 'Event retrieved successfully');
|
|
}
|
|
|
|
/**
|
|
* POST /api/v1/school-calendar
|
|
* Create a new calendar event
|
|
*/
|
|
public function store()
|
|
{
|
|
$payload = $this->payloadData();
|
|
if (empty($payload)) {
|
|
return $this->error('Invalid request data', Response::HTTP_BAD_REQUEST);
|
|
}
|
|
|
|
$rules = [
|
|
'title' => 'required|min_length[3]',
|
|
'start' => 'required|valid_date[Y-m-d]',
|
|
];
|
|
|
|
$errors = $this->validateRequest($payload, $rules);
|
|
if (!empty($errors)) {
|
|
return $this->respondValidationError($errors);
|
|
}
|
|
|
|
$data = [
|
|
'title' => trim((string) ($payload['title'] ?? '')),
|
|
'description' => trim((string) ($payload['description'] ?? '')),
|
|
'date' => trim((string) ($payload['start'] ?? '')),
|
|
'notify_parent' => isset($payload['notify_parent']) && $payload['notify_parent'] ? 1 : 0,
|
|
'notify_teacher' => isset($payload['notify_teacher']) && $payload['notify_teacher'] ? 1 : 0,
|
|
'notify_admin' => isset($payload['notify_admin']) && $payload['notify_admin'] ? 1 : 0,
|
|
'no_school' => isset($payload['no_school']) && $payload['no_school'] ? 1 : 0,
|
|
'school_year' => trim((string) ($payload['school_year'] ?? $this->schoolYear)),
|
|
'semester' => trim((string) ($payload['semester'] ?? $this->semester)),
|
|
];
|
|
|
|
try {
|
|
$event = $this->calendar->create($data);
|
|
log_message('info', 'Event saved successfully: ' . print_r($data, true));
|
|
return $this->success($event, 'Event added successfully', Response::HTTP_CREATED);
|
|
} catch (\Throwable $e) {
|
|
log_message('error', 'Failed to save event: ' . $e->getMessage());
|
|
return $this->respondError('Failed to save the event', Response::HTTP_INTERNAL_SERVER_ERROR);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* PATCH /api/v1/school-calendar/{id}
|
|
* Update a calendar event
|
|
*/
|
|
public function update($id = null)
|
|
{
|
|
$event = $this->calendar->find($id);
|
|
if (!$event) {
|
|
return $this->error('Event not found', Response::HTTP_NOT_FOUND);
|
|
}
|
|
|
|
$payload = $this->payloadData();
|
|
if (empty($payload)) {
|
|
return $this->error('Invalid request data', Response::HTTP_BAD_REQUEST);
|
|
}
|
|
|
|
$rules = [
|
|
'title' => 'permit_empty|min_length[3]',
|
|
'start' => 'permit_empty|valid_date[Y-m-d]',
|
|
];
|
|
|
|
$errors = $this->validateRequest($payload, $rules);
|
|
if (!empty($errors)) {
|
|
return $this->respondValidationError($errors);
|
|
}
|
|
|
|
$data = [];
|
|
|
|
if (isset($payload['title'])) {
|
|
$data['title'] = trim((string) $payload['title']);
|
|
}
|
|
if (isset($payload['description'])) {
|
|
$data['description'] = trim((string) $payload['description']);
|
|
}
|
|
if (isset($payload['start'])) {
|
|
$data['date'] = trim((string) $payload['start']);
|
|
}
|
|
if (isset($payload['notify_parent'])) {
|
|
$data['notify_parent'] = $payload['notify_parent'] ? 1 : 0;
|
|
}
|
|
if (isset($payload['notify_teacher'])) {
|
|
$data['notify_teacher'] = $payload['notify_teacher'] ? 1 : 0;
|
|
}
|
|
if (isset($payload['notify_admin'])) {
|
|
$data['notify_admin'] = $payload['notify_admin'] ? 1 : 0;
|
|
}
|
|
if (isset($payload['no_school'])) {
|
|
$data['no_school'] = $payload['no_school'] ? 1 : 0;
|
|
}
|
|
if (isset($payload['school_year'])) {
|
|
$data['school_year'] = trim((string) $payload['school_year']);
|
|
}
|
|
if (isset($payload['semester'])) {
|
|
$data['semester'] = trim((string) $payload['semester']);
|
|
}
|
|
|
|
try {
|
|
$this->calendar->update($id, $data);
|
|
$updatedEvent = $this->calendar->find($id);
|
|
return $this->success($updatedEvent, 'Event updated successfully');
|
|
} catch (\Throwable $e) {
|
|
log_message('error', 'Failed to update event: ' . $e->getMessage());
|
|
return $this->respondError('Failed to update the event', Response::HTTP_INTERNAL_SERVER_ERROR);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* DELETE /api/v1/school-calendar/{id}
|
|
* Delete a calendar event
|
|
*/
|
|
public function delete($id = null)
|
|
{
|
|
$event = $this->calendar->find($id);
|
|
if (!$event) {
|
|
return $this->error('Event not found', Response::HTTP_NOT_FOUND);
|
|
}
|
|
|
|
try {
|
|
$this->calendar->delete($id);
|
|
return $this->success(null, 'Event deleted successfully');
|
|
} catch (\Throwable $e) {
|
|
log_message('error', 'Failed to delete event: ' . $e->getMessage());
|
|
return $this->respondError('Failed to delete the event', Response::HTTP_INTERNAL_SERVER_ERROR);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* GET /api/v1/school-calendar/view/parent
|
|
* Get calendar events formatted for parent view
|
|
*/
|
|
public function calendarParentView()
|
|
{
|
|
$formattedEvents = $this->calendarView('parent');
|
|
return $this->success(['events' => $formattedEvents], 'Parent calendar view retrieved successfully');
|
|
}
|
|
|
|
/**
|
|
* GET /api/v1/school-calendar/view/teacher
|
|
* Get calendar events formatted for teacher view
|
|
*/
|
|
public function calendarTeacherView()
|
|
{
|
|
$formattedEvents = $this->calendarView('teacher');
|
|
return $this->success(['events' => $formattedEvents], 'Teacher calendar view retrieved successfully');
|
|
}
|
|
|
|
/**
|
|
* GET /api/v1/school-calendar/view/admin
|
|
* Get calendar events formatted for admin view
|
|
*/
|
|
public function calendarAdminView()
|
|
{
|
|
$formattedEvents = $this->calendarView('admin');
|
|
return $this->success(['events' => $formattedEvents], 'Admin calendar view retrieved successfully');
|
|
}
|
|
|
|
/**
|
|
* Private method to format calendar events based on audience visibility rules
|
|
*/
|
|
private function calendarView(?string $audience = null): array
|
|
{
|
|
// Filter events by school year
|
|
$query = $this->calendar->newQuery();
|
|
if ($this->schoolYear !== '') {
|
|
$query->where('school_year', $this->schoolYear);
|
|
}
|
|
$events = $query->orderBy('date', 'ASC')->get()->toArray();
|
|
|
|
// 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);
|
|
|
|
return $formattedEvents;
|
|
}
|
|
}
|