normalizeInt($request->getGet('school_year_id')); $requestedName = $this->legacyYearName($request); $userId = (int) (session()->get('user_id') ?? 0); if ($requestedId !== null && $requestedName !== '') { $row = $this->schoolYearModel->find($requestedId); if ($row === null || (string) ($row['name'] ?? '') !== $requestedName) { throw new InvalidSchoolYearSelectionException('Selected school-year parameters conflict.'); } return $this->authorizedContext($row, $userId, true); } if ($requestedId !== null) { $row = $this->schoolYearModel->find($requestedId); if ($row === null) { throw new SchoolYearNotFoundException('Selected school year was not found.'); } return $this->authorizedContext($row, $userId, true); } if ($requestedName !== '') { $row = $this->schoolYearModel ->where('name', $requestedName) ->first(); if ($row === null) { throw new SchoolYearNotFoundException('Selected school year was not found.'); } return $this->authorizedContext($row, $userId, true); } $sessionYearId = session('selected_school_year_id'); if (is_numeric($sessionYearId)) { $row = $this->schoolYearModel->find((int) $sessionYearId); if ($row !== null && $this->isSelectable($row, $userId)) { return $this->fromRow($row, true); } $this->clearSelection(); } return $this->active(); } public function selectableYears(bool $includeDraft = false): array { $builder = $this->schoolYearModel ->orderBy('name', 'DESC') ->orderBy('id', 'DESC'); if (! $includeDraft) { $builder->where('status !=', SchoolYearStatus::DRAFT); } $userId = (int) (session()->get('user_id') ?? 0); return array_values(array_filter( $builder->findAll(), fn (array $row): bool => $this->isSelectable($row, $userId) )); } public function select(int $schoolYearId): SchoolYearContext { $row = $this->schoolYearModel->find($schoolYearId); if ($row === null) { throw new SchoolYearNotFoundException('Selected school year was not found.'); } $userId = (int) (session()->get('user_id') ?? 0); $context = $this->authorizedContext($row, $userId, true); $previousSelection = session('selected_school_year_id'); if ($context->isActive()) { $this->clearSelection(); $context = $this->fromRow($row, false); } else { session()->set('selected_school_year_id', $context->id()); session()->remove('selected_school_year'); } if ((string) $previousSelection !== (string) session('selected_school_year_id')) { $this->clearDependentSessionState(); } return $context; } public function clearSelection(): void { session()->remove('selected_school_year_id'); session()->remove('selected_school_year'); } public function active(): SchoolYearContext { $active = $this->schoolYearModel->active(); if ($active === null) { throw new SchoolYearConfigurationException('Exactly one active school year must be configured.'); } return $this->fromRow($active, false); } private function normalizeInt(mixed $value): ?int { if ($value === null || $value === '' || ! ctype_digit((string) $value)) { return null; } return (int) $value; } private function legacyYearName(IncomingRequest $request): string { $names = []; foreach (['school_year', 'schoolYear', 'year'] as $key) { $value = trim((string) ($request->getGet($key) ?? '')); if ($value !== '') { $names[$key] = $value; } } if (count(array_unique($names)) > 1) { throw new InvalidSchoolYearSelectionException('Conflicting school-year parameters were provided.'); } if (isset($names['schoolYear']) || isset($names['year'])) { log_message('notice', 'Legacy school-year request alias used: {aliases}', [ 'aliases' => implode(',', array_keys($names)), ]); } return $names === [] ? '' : (string) reset($names); } private function authorizedContext(array $row, int $userId, bool $explicit): SchoolYearContext { if (! $this->isSelectable($row, $userId)) { throw new SchoolYearNotFoundException('Selected school year was not found.'); } return $this->fromRow($row, $explicit); } private function isSelectable(array $row, int $userId): bool { return $this->accessPolicy->canSelect($userId > 0 ? $userId : null, $row); } private function clearDependentSessionState(): void { session()->remove([ 'class_section_id', 'semester', 'active_semester', 'teacher_scores_selected_semester', 'grading_selected_semester', ]); } private function fromRow(array $row, bool $explicit): SchoolYearContext { return new SchoolYearContext( id: (int) $row['id'], yearName: (string) $row['name'], status: strtolower((string) $row['status']), explicitSelection: $explicit, ); } }