This commit is contained in:
@@ -2,15 +2,19 @@
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Exceptions\SchoolYear\InvalidSchoolYearSelectionException;
|
||||
use App\Exceptions\SchoolYear\SchoolYearConfigurationException;
|
||||
use App\Exceptions\SchoolYear\SchoolYearNotFoundException;
|
||||
use App\Models\SchoolYearModel;
|
||||
use App\Support\SchoolYear\SchoolYearStatus;
|
||||
use App\Support\SchoolYear\SchoolYearContext;
|
||||
use CodeIgniter\HTTP\IncomingRequest;
|
||||
use RuntimeException;
|
||||
|
||||
final class SchoolYearContextService
|
||||
{
|
||||
public function __construct(
|
||||
private readonly SchoolYearModel $schoolYearModel,
|
||||
private readonly SchoolYearAccessPolicy $accessPolicy = new SchoolYearAccessPolicy(),
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -18,29 +22,28 @@ final class SchoolYearContextService
|
||||
IncomingRequest $request,
|
||||
?int $routeSchoolYearId = null
|
||||
): SchoolYearContext {
|
||||
$requestedId = $routeSchoolYearId
|
||||
?? $this->normalizeInt($request->getGet('school_year_id'));
|
||||
|
||||
$requestedName = trim((string) $request->getGet('school_year'));
|
||||
$requestedId = $routeSchoolYearId ?? $this->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 RuntimeException('Selected school-year parameters conflict.');
|
||||
throw new InvalidSchoolYearSelectionException('Selected school-year parameters conflict.');
|
||||
}
|
||||
|
||||
return $this->fromRow($row, true);
|
||||
return $this->authorizedContext($row, $userId, true);
|
||||
}
|
||||
|
||||
if ($requestedId !== null) {
|
||||
$row = $this->schoolYearModel->find($requestedId);
|
||||
|
||||
if ($row === null) {
|
||||
throw new RuntimeException('Selected school year was not found.');
|
||||
throw new SchoolYearNotFoundException('Selected school year was not found.');
|
||||
}
|
||||
|
||||
return $this->fromRow($row, true);
|
||||
return $this->authorizedContext($row, $userId, true);
|
||||
}
|
||||
|
||||
if ($requestedName !== '') {
|
||||
@@ -49,10 +52,10 @@ final class SchoolYearContextService
|
||||
->first();
|
||||
|
||||
if ($row === null) {
|
||||
throw new RuntimeException('Selected school year was not found.');
|
||||
throw new SchoolYearNotFoundException('Selected school year was not found.');
|
||||
}
|
||||
|
||||
return $this->fromRow($row, true);
|
||||
return $this->authorizedContext($row, $userId, true);
|
||||
}
|
||||
|
||||
$sessionYearId = session('selected_school_year_id');
|
||||
@@ -60,15 +63,73 @@ final class SchoolYearContextService
|
||||
if (is_numeric($sessionYearId)) {
|
||||
$row = $this->schoolYearModel->find((int) $sessionYearId);
|
||||
|
||||
if ($row !== null) {
|
||||
return $this->fromRow($row, false);
|
||||
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 RuntimeException('No active school year is configured.');
|
||||
throw new SchoolYearConfigurationException('Exactly one active school year must be configured.');
|
||||
}
|
||||
|
||||
return $this->fromRow($active, false);
|
||||
@@ -83,12 +144,61 @@ final class SchoolYearContextService
|
||||
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: (string) $row['status'],
|
||||
status: strtolower((string) $row['status']),
|
||||
explicitSelection: $explicit,
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user