442 lines
16 KiB
PHP
Executable File
442 lines
16 KiB
PHP
Executable File
<?php
|
|
|
|
namespace App\Controllers\View;
|
|
|
|
use App\Controllers\BaseController;
|
|
use App\Models\ClassSectionModel;
|
|
use App\Models\CompetitionClassWinnerModel;
|
|
use App\Models\CompetitionModel;
|
|
use App\Models\CompetitionScoreModel;
|
|
use App\Models\ConfigurationModel;
|
|
use App\Models\TeacherClassModel;
|
|
|
|
class CompetitionScoresController extends BaseController
|
|
{
|
|
protected $db;
|
|
protected TeacherClassModel $teacherClassModel;
|
|
protected ConfigurationModel $configModel;
|
|
protected ClassSectionModel $classSectionModel;
|
|
protected CompetitionClassWinnerModel $classWinnerModel;
|
|
protected string $classStudentTable;
|
|
protected bool $hasQuestionCount = false;
|
|
protected bool $hasClassWinnerTable = false;
|
|
|
|
public function __construct()
|
|
{
|
|
$this->db = \Config\Database::connect();
|
|
$this->teacherClassModel = new TeacherClassModel();
|
|
$this->configModel = new ConfigurationModel();
|
|
$this->classSectionModel = new ClassSectionModel();
|
|
$this->classWinnerModel = new CompetitionClassWinnerModel();
|
|
|
|
if ($this->db->tableExists('class_student')) {
|
|
$this->classStudentTable = 'class_student';
|
|
} elseif ($this->db->tableExists('student_class')) {
|
|
$this->classStudentTable = 'student_class';
|
|
} else {
|
|
$this->classStudentTable = '';
|
|
}
|
|
|
|
$this->hasClassWinnerTable = $this->db->tableExists('competition_class_winners');
|
|
$this->hasQuestionCount = $this->hasClassWinnerTable
|
|
&& $this->db->fieldExists('question_count', 'competition_class_winners');
|
|
}
|
|
|
|
public function index()
|
|
{
|
|
[$assignments, $schoolYear, $semester] = $this->getTeacherContext();
|
|
$activeClassId = $this->resolveActiveClassId($assignments);
|
|
$activeClassName = $this->getActiveClassName($assignments, $activeClassId);
|
|
|
|
if (empty($assignments) || $activeClassId <= 0) {
|
|
return view('teacher/competition_scores/index', [
|
|
'competitions' => [],
|
|
'activeClassId' => $activeClassId,
|
|
'activeClassName' => $activeClassName,
|
|
'questionCounts' => [],
|
|
'scoreCounts' => [],
|
|
'studentTotal' => 0,
|
|
'sectionMap' => $this->getClassSectionMap(),
|
|
'schoolYear' => $schoolYear,
|
|
'semester' => $semester,
|
|
'hasClasses' => false,
|
|
]);
|
|
}
|
|
|
|
$competitions = $this->getCompetitionsForClass($activeClassId, $schoolYear, $semester);
|
|
$competitionIds = array_values(array_filter(array_map(static function ($row) {
|
|
return (int) ($row['id'] ?? 0);
|
|
}, $competitions)));
|
|
|
|
$questionCounts = $this->getQuestionCounts($competitionIds, $activeClassId);
|
|
$scoreCounts = $this->getScoreCounts($competitionIds, $activeClassId);
|
|
$studentTotal = $this->getClassStudentCount($activeClassId, $schoolYear);
|
|
|
|
return view('teacher/competition_scores/index', [
|
|
'competitions' => $competitions,
|
|
'activeClassId' => $activeClassId,
|
|
'activeClassName' => $activeClassName,
|
|
'questionCounts' => $questionCounts,
|
|
'scoreCounts' => $scoreCounts,
|
|
'studentTotal' => $studentTotal,
|
|
'sectionMap' => $this->getClassSectionMap(),
|
|
'schoolYear' => $schoolYear,
|
|
'semester' => $semester,
|
|
'hasClasses' => true,
|
|
]);
|
|
}
|
|
|
|
public function edit($id)
|
|
{
|
|
[$assignments, $schoolYear, $semester] = $this->getTeacherContext();
|
|
$allowedClassIds = $this->getAllowedClassIds($assignments);
|
|
if (empty($allowedClassIds)) {
|
|
return redirect()->to('/teacher/competition-scores')
|
|
->with('error', 'No class assignments found for your account.');
|
|
}
|
|
|
|
$competitionModel = new CompetitionModel();
|
|
$scoreModel = new CompetitionScoreModel();
|
|
$competition = $competitionModel->find($id);
|
|
if (!$competition) {
|
|
return redirect()->to('/teacher/competition-scores')
|
|
->with('error', 'Competition not found.');
|
|
}
|
|
$isLocked = !empty($competition['is_locked']);
|
|
|
|
$activeClassId = $this->resolveActiveClassId($assignments);
|
|
$lockedClassId = (int) ($competition['class_section_id'] ?? 0);
|
|
$classSectionId = $lockedClassId > 0 ? $lockedClassId : $activeClassId;
|
|
if ($classSectionId <= 0) {
|
|
return redirect()->to('/teacher/competition-scores')
|
|
->with('error', 'Select a class section before entering scores.');
|
|
}
|
|
if (!in_array($classSectionId, $allowedClassIds, true)) {
|
|
return redirect()->to('/teacher/competition-scores')
|
|
->with('error', 'You are not assigned to that class.');
|
|
}
|
|
|
|
$students = $this->getStudentsForCompetition($competition, $classSectionId);
|
|
$existingScores = $scoreModel
|
|
->where('competition_id', $id)
|
|
->where('class_section_id', $classSectionId)
|
|
->findAll();
|
|
|
|
$scoreMap = [];
|
|
foreach ($existingScores as $row) {
|
|
$scoreMap[$row['student_id']] = $row['score'];
|
|
}
|
|
|
|
$questionCount = null;
|
|
if ($this->hasQuestionCount && $this->hasClassWinnerTable) {
|
|
$row = $this->classWinnerModel
|
|
->select('question_count')
|
|
->where('competition_id', $id)
|
|
->where('class_section_id', $classSectionId)
|
|
->first();
|
|
if ($row && $row['question_count'] !== null) {
|
|
$questionCount = (int) $row['question_count'];
|
|
}
|
|
}
|
|
|
|
$classStudentCount = $this->getClassStudentCount(
|
|
$classSectionId,
|
|
$competition['school_year'] ?? $schoolYear
|
|
);
|
|
|
|
$activeClassName = $this->getActiveClassName($assignments, $classSectionId);
|
|
|
|
return view('teacher/competition_scores/scores', [
|
|
'competition' => $competition,
|
|
'students' => $students,
|
|
'scoreMap' => $scoreMap,
|
|
'classSectionId' => $classSectionId,
|
|
'classSectionName' => $activeClassName,
|
|
'classStudentCount' => $classStudentCount,
|
|
'questionCount' => $questionCount,
|
|
'classSelectionLocked' => $lockedClassId > 0,
|
|
'isLocked' => $isLocked,
|
|
]);
|
|
}
|
|
|
|
public function save($id)
|
|
{
|
|
[$assignments] = $this->getTeacherContext();
|
|
$allowedClassIds = $this->getAllowedClassIds($assignments);
|
|
if (empty($allowedClassIds)) {
|
|
return redirect()->to('/teacher/competition-scores')
|
|
->with('error', 'No class assignments found for your account.');
|
|
}
|
|
|
|
$competitionModel = new CompetitionModel();
|
|
$scoreModel = new CompetitionScoreModel();
|
|
$competition = $competitionModel->find($id);
|
|
if (!$competition) {
|
|
return redirect()->to('/teacher/competition-scores')
|
|
->with('error', 'Competition not found.');
|
|
}
|
|
if (!empty($competition['is_locked'])) {
|
|
return redirect()->to("/teacher/competition-scores/{$id}")
|
|
->with('error', 'Competition is locked. You cannot update scores.');
|
|
}
|
|
|
|
$lockedClassId = (int) ($competition['class_section_id'] ?? 0);
|
|
$classSectionId = $lockedClassId > 0
|
|
? $lockedClassId
|
|
: (int) $this->request->getPost('class_section_id');
|
|
if ($classSectionId <= 0) {
|
|
return redirect()->to('/teacher/competition-scores')
|
|
->with('error', 'Select a class section before saving scores.');
|
|
}
|
|
if (!in_array($classSectionId, $allowedClassIds, true)) {
|
|
return redirect()->to('/teacher/competition-scores')
|
|
->with('error', 'You are not assigned to that class.');
|
|
}
|
|
|
|
$scores = (array) $this->request->getPost('scores');
|
|
$cleanScores = [];
|
|
$invalidScores = [];
|
|
|
|
foreach ($scores as $studentId => $scoreValue) {
|
|
$scoreValue = trim((string) $scoreValue);
|
|
if ($scoreValue === '') {
|
|
continue;
|
|
}
|
|
if (!preg_match('/^\d+$/', $scoreValue)) {
|
|
$invalidScores[] = $studentId;
|
|
continue;
|
|
}
|
|
$cleanScores[(int) $studentId] = (int) $scoreValue;
|
|
}
|
|
|
|
if (!empty($invalidScores)) {
|
|
return redirect()->to("/teacher/competition-scores/{$id}")
|
|
->with('error', 'Scores must be whole numbers (no decimals).');
|
|
}
|
|
|
|
foreach ($cleanScores as $studentId => $scoreValue) {
|
|
$existing = $scoreModel
|
|
->where('competition_id', $id)
|
|
->where('student_id', (int) $studentId)
|
|
->where('class_section_id', $classSectionId)
|
|
->first();
|
|
|
|
if ($existing) {
|
|
$scoreModel->update($existing['id'], [
|
|
'score' => $scoreValue,
|
|
'class_section_id' => $classSectionId,
|
|
]);
|
|
} else {
|
|
$scoreModel->insert([
|
|
'competition_id' => (int) $id,
|
|
'student_id' => (int) $studentId,
|
|
'class_section_id' => $classSectionId,
|
|
'score' => $scoreValue,
|
|
]);
|
|
}
|
|
}
|
|
|
|
return redirect()->to("/teacher/competition-scores/{$id}")
|
|
->with('success', 'Scores saved.');
|
|
}
|
|
|
|
private function getTeacherContext(): array
|
|
{
|
|
$userId = (int) (session()->get('user_id') ?? 0);
|
|
$schoolYear = (string) ($this->configModel->getConfig('school_year') ?? '');
|
|
$semester = (string) ($this->configModel->getConfig('semester') ?? '');
|
|
$assignments = $this->teacherClassModel->getClassAssignmentsByUserId(
|
|
$userId,
|
|
$schoolYear,
|
|
$semester
|
|
);
|
|
|
|
return [$assignments, $schoolYear, $semester];
|
|
}
|
|
|
|
private function getAllowedClassIds(array $assignments): array
|
|
{
|
|
$ids = array_map(static function ($row) {
|
|
return (int) ($row['class_section_id'] ?? 0);
|
|
}, $assignments);
|
|
|
|
$ids = array_values(array_filter(array_unique($ids)));
|
|
return $ids;
|
|
}
|
|
|
|
private function resolveActiveClassId(array $assignments): int
|
|
{
|
|
$allowed = $this->getAllowedClassIds($assignments);
|
|
$active = (int) (session()->get('class_section_id') ?? 0);
|
|
if ($active > 0 && in_array($active, $allowed, true)) {
|
|
return $active;
|
|
}
|
|
|
|
$active = $allowed[0] ?? 0;
|
|
if ($active > 0) {
|
|
session()->set('class_section_id', $active);
|
|
}
|
|
|
|
return (int) $active;
|
|
}
|
|
|
|
private function getActiveClassName(array $assignments, int $classSectionId): ?string
|
|
{
|
|
if ($classSectionId <= 0) {
|
|
return null;
|
|
}
|
|
|
|
foreach ($assignments as $row) {
|
|
if ((int) ($row['class_section_id'] ?? 0) === $classSectionId) {
|
|
return $row['class_section_name'] ?? null;
|
|
}
|
|
}
|
|
|
|
$row = $this->classSectionModel
|
|
->select('class_section_name')
|
|
->where('class_section_id', $classSectionId)
|
|
->first();
|
|
|
|
return $row['class_section_name'] ?? null;
|
|
}
|
|
|
|
private function getCompetitionsForClass(int $classSectionId, string $schoolYear, string $semester): array
|
|
{
|
|
$competitionModel = new CompetitionModel();
|
|
$builder = $competitionModel->orderBy('id', 'DESC');
|
|
|
|
if ($classSectionId > 0) {
|
|
$builder->groupStart()
|
|
->where('class_section_id', $classSectionId)
|
|
->orWhere('class_section_id', 0)
|
|
->orWhere('class_section_id IS NULL', null, false)
|
|
->groupEnd();
|
|
}
|
|
|
|
if ($schoolYear !== '') {
|
|
$builder->groupStart()
|
|
->where('school_year', $schoolYear)
|
|
->orWhere('school_year', '')
|
|
->orWhere('school_year IS NULL', null, false)
|
|
->groupEnd();
|
|
}
|
|
|
|
if ($semester !== '') {
|
|
$builder->groupStart()
|
|
->where('semester', $semester)
|
|
->orWhere('semester', '')
|
|
->orWhere('semester IS NULL', null, false)
|
|
->groupEnd();
|
|
}
|
|
|
|
return $builder->findAll();
|
|
}
|
|
|
|
private function getQuestionCounts(array $competitionIds, int $classSectionId): array
|
|
{
|
|
if (!$this->hasQuestionCount || !$this->hasClassWinnerTable || empty($competitionIds)) {
|
|
return [];
|
|
}
|
|
|
|
$rows = $this->classWinnerModel
|
|
->select('competition_id, question_count')
|
|
->where('class_section_id', $classSectionId)
|
|
->whereIn('competition_id', $competitionIds)
|
|
->findAll();
|
|
|
|
$out = [];
|
|
foreach ($rows as $row) {
|
|
$compId = (int) ($row['competition_id'] ?? 0);
|
|
if ($compId > 0 && $row['question_count'] !== null) {
|
|
$out[$compId] = (int) $row['question_count'];
|
|
}
|
|
}
|
|
|
|
return $out;
|
|
}
|
|
|
|
private function getScoreCounts(array $competitionIds, int $classSectionId): array
|
|
{
|
|
if (empty($competitionIds)) {
|
|
return [];
|
|
}
|
|
|
|
$rows = $this->db->table('competition_scores')
|
|
->select('competition_id, COUNT(*) AS total')
|
|
->where('class_section_id', $classSectionId)
|
|
->whereIn('competition_id', $competitionIds)
|
|
->groupBy('competition_id')
|
|
->get()
|
|
->getResultArray();
|
|
|
|
$out = [];
|
|
foreach ($rows as $row) {
|
|
$compId = (int) ($row['competition_id'] ?? 0);
|
|
if ($compId > 0) {
|
|
$out[$compId] = (int) ($row['total'] ?? 0);
|
|
}
|
|
}
|
|
|
|
return $out;
|
|
}
|
|
|
|
private function getClassStudentCount(int $classSectionId, ?string $schoolYear): int
|
|
{
|
|
if ($classSectionId <= 0 || $this->classStudentTable === '') {
|
|
return 0;
|
|
}
|
|
|
|
$builder = $this->db->table($this->classStudentTable)
|
|
->select('COUNT(*) AS total')
|
|
->where('class_section_id', $classSectionId);
|
|
|
|
if ($schoolYear && $this->db->fieldExists('school_year', $this->classStudentTable)) {
|
|
$builder->where('school_year', $schoolYear);
|
|
}
|
|
|
|
$row = $builder->get()->getRowArray();
|
|
return (int) ($row['total'] ?? 0);
|
|
}
|
|
|
|
private function getStudentsForCompetition(array $competition, int $classSectionId): array
|
|
{
|
|
if ($classSectionId <= 0 || $this->classStudentTable === '') {
|
|
return [];
|
|
}
|
|
|
|
$builder = $this->db->table('students s')
|
|
->select('s.id, s.school_id, s.firstname, s.lastname')
|
|
->join($this->classStudentTable . ' cs', 'cs.student_id = s.id', 'inner')
|
|
->where('cs.class_section_id', $classSectionId);
|
|
|
|
$hasSchoolYear = $this->db->fieldExists('school_year', $this->classStudentTable);
|
|
if ($hasSchoolYear && !empty($competition['school_year'])) {
|
|
$builder->where('cs.school_year', $competition['school_year']);
|
|
}
|
|
|
|
$builder->distinct();
|
|
$builder->orderBy('s.lastname', 'ASC')
|
|
->orderBy('s.firstname', 'ASC');
|
|
|
|
return $builder->get()->getResultArray();
|
|
}
|
|
|
|
private function getClassSectionMap(): array
|
|
{
|
|
$sections = $this->classSectionModel
|
|
->select('class_section_id, class_section_name')
|
|
->orderBy('class_section_name', 'ASC')
|
|
->findAll();
|
|
|
|
$map = [];
|
|
foreach ($sections as $section) {
|
|
$id = (int) ($section['class_section_id'] ?? 0);
|
|
if ($id > 0) {
|
|
$map[$id] = $section['class_section_name'] ?? (string) $id;
|
|
}
|
|
}
|
|
|
|
return $map;
|
|
}
|
|
}
|