exam draft fix
This commit is contained in:
@@ -28,6 +28,10 @@ class ClassProgressController extends BaseController
|
||||
'db_subject' => 'Quran/Arabic',
|
||||
],
|
||||
];
|
||||
|
||||
/** Unit column for teacher custom Islamic / Quran rows; must match teacher form JS. Segment: "Custom / {text}". */
|
||||
public const CUSTOM_UNIT_ROW_LABEL = 'Custom';
|
||||
|
||||
protected ClassProgressReportModel $reportModel;
|
||||
protected ClassProgressAttachmentModel $attachmentModel;
|
||||
protected TeacherClassModel $teacherClassModel;
|
||||
@@ -705,6 +709,37 @@ class ClassProgressController extends BaseController
|
||||
return $flags ? json_encode($flags) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Split stored unit_title into curriculum lines vs custom teacher-typed subjects ("Custom / …").
|
||||
* Keep in sync with teacher form, {@see buildUnitChapterSummary()}, and admin views.
|
||||
*
|
||||
* @return array{curriculum: list<string>, custom: list<string>}
|
||||
*/
|
||||
public static function splitUnitTitleForDisplay(string $unitTitle): array
|
||||
{
|
||||
$unitTitle = trim($unitTitle);
|
||||
if ($unitTitle === '') {
|
||||
return ['curriculum' => [], 'custom' => []];
|
||||
}
|
||||
|
||||
$segments = preg_split('/\s*;\s*/', $unitTitle, -1, PREG_SPLIT_NO_EMPTY);
|
||||
$curriculum = [];
|
||||
$custom = [];
|
||||
foreach ($segments as $seg) {
|
||||
$seg = trim((string) $seg);
|
||||
if ($seg === '') {
|
||||
continue;
|
||||
}
|
||||
if (preg_match('/^Custom\s*\/\s*(.+)$/iu', $seg, $m)) {
|
||||
$custom[] = trim($m[1]);
|
||||
} else {
|
||||
$curriculum[] = $seg;
|
||||
}
|
||||
}
|
||||
|
||||
return ['curriculum' => $curriculum, 'custom' => $custom];
|
||||
}
|
||||
|
||||
protected function buildUnitChapterSummary(string $slug): ?string
|
||||
{
|
||||
$unitValues = array_map('trim', (array) $this->request->getPost("unit_$slug"));
|
||||
@@ -717,9 +752,12 @@ class ClassProgressController extends BaseController
|
||||
if ($unit === '' && $chapter === '') {
|
||||
continue;
|
||||
}
|
||||
if (strcasecmp($unit, self::CUSTOM_UNIT_ROW_LABEL) === 0 && $chapter !== '') {
|
||||
$unit = self::CUSTOM_UNIT_ROW_LABEL;
|
||||
}
|
||||
$segment = $unit;
|
||||
if ($chapter !== '') {
|
||||
$segment = $segment !== '' ? $segment . ' / ' . $chapter : $chapter;
|
||||
$segment = $segment !== '' ? $unit . ' / ' . $chapter : $chapter;
|
||||
}
|
||||
if ($segment === '') {
|
||||
continue;
|
||||
@@ -730,17 +768,23 @@ class ClassProgressController extends BaseController
|
||||
return null;
|
||||
}
|
||||
$summary = implode(' ; ', $parts);
|
||||
|
||||
return mb_strlen($summary) > 120 ? mb_substr($summary, 0, 120) : $summary;
|
||||
}
|
||||
|
||||
protected function hasIslamicUnitSelection(): bool
|
||||
{
|
||||
$unitValues = array_map('trim', (array) $this->request->getPost('unit_islamic'));
|
||||
foreach ($unitValues as $value) {
|
||||
if ($value !== '') {
|
||||
$chapterValues = array_map('trim', (array) $this->request->getPost('chapter_islamic'));
|
||||
$count = max(count($unitValues), count($chapterValues));
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$unit = $unitValues[$i] ?? '';
|
||||
$chapter = $chapterValues[$i] ?? '';
|
||||
if ($unit !== '' || $chapter !== '') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -759,9 +803,19 @@ class ClassProgressController extends BaseController
|
||||
if ($segment === '') {
|
||||
continue;
|
||||
}
|
||||
if (preg_match('/^Custom\s*\/\s*(.+)$/iu', $segment, $m)) {
|
||||
$units[] = self::CUSTOM_UNIT_ROW_LABEL;
|
||||
$chapters[] = trim($m[1]);
|
||||
|
||||
continue;
|
||||
}
|
||||
$parts = preg_split('/\s*\/\s*/', $segment, 2);
|
||||
if (count($parts) === 2) {
|
||||
$units[] = trim($parts[0]);
|
||||
$u = trim($parts[0]);
|
||||
if (strcasecmp($u, self::CUSTOM_UNIT_ROW_LABEL) === 0) {
|
||||
$u = self::CUSTOM_UNIT_ROW_LABEL;
|
||||
}
|
||||
$units[] = $u;
|
||||
$chapters[] = trim($parts[1]);
|
||||
} else {
|
||||
$units[] = $segment;
|
||||
|
||||
@@ -763,7 +763,13 @@ class AdministratorController extends BaseController
|
||||
|
||||
[$progressExpectedWeeks, $progressSubmittedBySection] = $this->buildClassProgressStats($sectionIds);
|
||||
$examDraftCounts = [];
|
||||
$examDraftDeadline = $this->resolveExamDraftDeadline($semester, $schoolYear);
|
||||
$examDraftDeadline = $this->resolveTeacherDashboardExamDraftDeadline($semester, $schoolYear);
|
||||
$examDraftDeadlineConfig = trim((string) ($this->configModel->getConfig('exam_draft_deadline') ?? ''));
|
||||
$examDraftDeadlineFormatted = '';
|
||||
if ($examDraftDeadlineConfig !== '') {
|
||||
$parsedUi = $this->parseExamDraftDeadlineConfigValue();
|
||||
$examDraftDeadlineFormatted = $parsedUi !== null ? $parsedUi->format('M j, Y') : '';
|
||||
}
|
||||
$homeworkCounts = [];
|
||||
if (! empty($sectionIds)) {
|
||||
$draftBuilder = $examDrafts
|
||||
@@ -1066,6 +1072,8 @@ class AdministratorController extends BaseController
|
||||
'notificationHistory' => $historyMap,
|
||||
'summary' => $summary,
|
||||
'lowProgressSectionIds' => $lowProgressSectionIds,
|
||||
'examDraftDeadlineConfig' => $examDraftDeadlineConfig,
|
||||
'examDraftDeadlineFormatted' => $examDraftDeadlineFormatted,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -1299,6 +1307,7 @@ class AdministratorController extends BaseController
|
||||
$progressUrl = site_url('teacher/progress/history');
|
||||
$examDraftUrl = site_url('teacher/exam-drafts');
|
||||
$homeworkUrl = site_url('teacher/addHomework');
|
||||
$examDraftDeadlineEmailHtml = $this->buildExamDraftDeadlineEmailHtml();
|
||||
$sentCount = 0;
|
||||
$failCount = 0;
|
||||
|
||||
@@ -1347,7 +1356,8 @@ class AdministratorController extends BaseController
|
||||
} else {
|
||||
$draftLabel = 'exam draft';
|
||||
}
|
||||
$examDraftNote = "<p>" . ucfirst($draftLabel) . " submissions can be updated at <a href=\"{$examDraftUrl}\">Teacher Exam Drafts</a>.</p>";
|
||||
$examDraftNote = "<p>" . ucfirst($draftLabel) . " submissions can be updated at <a href=\"{$examDraftUrl}\">Teacher Exam Drafts</a>.</p>"
|
||||
. $examDraftDeadlineEmailHtml;
|
||||
}
|
||||
$homeworkNote = '';
|
||||
if (in_array('homework', $missingItems, true)) {
|
||||
@@ -1485,6 +1495,60 @@ class AdministratorController extends BaseController
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Exam draft due date for the teacher submissions dashboard: prefers the configuration key
|
||||
* `exam_draft_deadline` (same as automated reminders); otherwise fall/spring exam deadlines.
|
||||
*/
|
||||
private function resolveTeacherDashboardExamDraftDeadline(string $semester, string $schoolYear): ?\DateTimeImmutable
|
||||
{
|
||||
$fromExamDraftKey = $this->parseExamDraftDeadlineConfigValue();
|
||||
if ($fromExamDraftKey !== null) {
|
||||
return $fromExamDraftKey;
|
||||
}
|
||||
|
||||
return $this->resolveExamDraftDeadline($semester, $schoolYear);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the `exam_draft_deadline` configuration value using the application timezone (midnight that calendar day).
|
||||
*/
|
||||
private function parseExamDraftDeadlineConfigValue(): ?\DateTimeImmutable
|
||||
{
|
||||
$raw = trim((string) ($this->configModel->getConfig('exam_draft_deadline') ?? ''));
|
||||
if ($raw === '') {
|
||||
return null;
|
||||
}
|
||||
$tz = new \DateTimeZone(config('App')->appTimezone ?? 'UTC');
|
||||
try {
|
||||
$deadline = new \DateTimeImmutable($raw, $tz);
|
||||
} catch (\Throwable $e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $deadline->setTime(0, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* HTML snippet for reminder emails when exam draft is included (deadline from exam_draft_deadline config).
|
||||
*/
|
||||
private function buildExamDraftDeadlineEmailHtml(): string
|
||||
{
|
||||
$raw = trim((string) ($this->configModel->getConfig('exam_draft_deadline') ?? ''));
|
||||
if ($raw === '') {
|
||||
return '';
|
||||
}
|
||||
$parsed = $this->parseExamDraftDeadlineConfigValue();
|
||||
$display = $parsed !== null
|
||||
? htmlspecialchars($parsed->format('l, F j, Y'), ENT_QUOTES, 'UTF-8')
|
||||
: htmlspecialchars($raw, ENT_QUOTES, 'UTF-8');
|
||||
$rawEsc = htmlspecialchars($raw, ENT_QUOTES, 'UTF-8');
|
||||
|
||||
return '<p><strong>Exam draft submission deadline</strong> (<code>exam_draft_deadline</code>): '
|
||||
. "<strong>{$display}</strong>"
|
||||
. ($parsed !== null && $rawEsc !== $display ? " <span style=\"color:#555;\">(configured value: {$rawEsc})</span>" : '')
|
||||
. '.</p>';
|
||||
}
|
||||
|
||||
private function resolveExamDraftDeadline(string $semester, string $schoolYear): ?\DateTimeImmutable
|
||||
{
|
||||
$semesterKey = strtolower(trim($semester));
|
||||
|
||||
@@ -6,6 +6,7 @@ use App\Controllers\BaseController;
|
||||
use App\Models\ClassSectionModel;
|
||||
use App\Models\ConfigurationModel;
|
||||
use App\Models\ExamDraftModel;
|
||||
use App\Models\StudentClassModel;
|
||||
use App\Models\TeacherClassModel;
|
||||
use App\Models\UserModel;
|
||||
use CodeIgniter\HTTP\Files\UploadedFile;
|
||||
@@ -16,6 +17,7 @@ class ExamDraftController extends BaseController
|
||||
protected ExamDraftModel $examDraftModel;
|
||||
protected TeacherClassModel $teacherClassModel;
|
||||
protected ClassSectionModel $classSectionModel;
|
||||
protected StudentClassModel $studentClassModel;
|
||||
protected UserModel $userModel;
|
||||
protected ConfigurationModel $configModel;
|
||||
|
||||
@@ -57,6 +59,7 @@ class ExamDraftController extends BaseController
|
||||
$this->examDraftModel = new ExamDraftModel();
|
||||
$this->teacherClassModel = new TeacherClassModel();
|
||||
$this->classSectionModel = new ClassSectionModel();
|
||||
$this->studentClassModel = new StudentClassModel();
|
||||
$this->userModel = new UserModel();
|
||||
$this->configModel = new ConfigurationModel();
|
||||
$this->db = Database::connect();
|
||||
@@ -419,6 +422,52 @@ class ExamDraftController extends BaseController
|
||||
}
|
||||
unset($group);
|
||||
|
||||
$classSectionsById = [];
|
||||
foreach ($classSections as $cs) {
|
||||
$csId = (int) ($cs['class_section_id'] ?? 0);
|
||||
if ($csId <= 0) {
|
||||
continue;
|
||||
}
|
||||
$classSectionsById[$csId] = $cs;
|
||||
}
|
||||
|
||||
$studentCounts = $this->studentClassModel->getStudentCountsBySection($this->schoolYear);
|
||||
$visibleClasses = [];
|
||||
foreach ($studentCounts as $csId => $count) {
|
||||
$csId = (int) $csId;
|
||||
if ($csId <= 0 || $count <= 0) {
|
||||
continue;
|
||||
}
|
||||
$classData = $classSectionsById[$csId] ?? $this->classSectionModel->where('class_section_id', $csId)->first();
|
||||
if ($classData === null) {
|
||||
$classData = [
|
||||
'class_section_id' => $csId,
|
||||
'class_section_name' => 'Class ' . $csId,
|
||||
];
|
||||
}
|
||||
$classData['student_count'] = $count;
|
||||
$visibleClasses[$csId] = $classData;
|
||||
}
|
||||
uasort($visibleClasses, static fn ($a, $b): int => strcasecmp($a['class_section_name'] ?? '', $b['class_section_name'] ?? ''));
|
||||
|
||||
$classDraftGroups = [];
|
||||
foreach ($drafts as $draft) {
|
||||
$cid = (int) ($draft['class_section_id'] ?? 0);
|
||||
if ($cid <= 0) {
|
||||
continue;
|
||||
}
|
||||
$classDraftGroups[$cid][] = $draft;
|
||||
}
|
||||
$newSubmissionClasses = [];
|
||||
foreach ($classDraftGroups as $cid => $group) {
|
||||
foreach ($group as $draft) {
|
||||
if (strtolower((string) ($draft['status'] ?? '')) === 'submitted') {
|
||||
$newSubmissionClasses[$cid] = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return view('administrator/exam_drafts', [
|
||||
'drafts' => $drafts,
|
||||
'statusBadges' => $this->statusBadgeMap(),
|
||||
@@ -430,6 +479,9 @@ class ExamDraftController extends BaseController
|
||||
'classSections' => $classSections,
|
||||
'legacyByClass' => $legacyByClass,
|
||||
'printableDraftIds' => $printableIds,
|
||||
'visibleClasses' => $visibleClasses,
|
||||
'classDraftGroups' => $classDraftGroups,
|
||||
'newSubmissionClasses' => $newSubmissionClasses,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -657,6 +709,9 @@ class ExamDraftController extends BaseController
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($status === 'legacy') {
|
||||
$this->prepareLegacyPdfVersion($update, $draft);
|
||||
}
|
||||
if ($this->hasIsLegacyColumn) {
|
||||
$update['is_legacy'] = strtolower($status) === 'legacy' ? 1 : 0;
|
||||
}
|
||||
@@ -875,7 +930,7 @@ class ExamDraftController extends BaseController
|
||||
$teacherName = 'Teacher #' . $teacherId;
|
||||
}
|
||||
|
||||
$class = $this->classSectionModel->find($classSectionId) ?? [];
|
||||
$class = $this->classSectionModel->where('class_section_id', (int) $classSectionId)->first() ?? [];
|
||||
$className = $class['class_section_name'] ?? ('Class ' . $classSectionId);
|
||||
|
||||
$subject = 'Exam draft submitted: ' . $className;
|
||||
@@ -1321,4 +1376,44 @@ class ExamDraftController extends BaseController
|
||||
}
|
||||
return $destName;
|
||||
}
|
||||
|
||||
private function prepareLegacyPdfVersion(array &$update, array $draft): void
|
||||
{
|
||||
$finalFile = $update['final_file'] ?? $draft['final_file'] ?? null;
|
||||
if (empty($finalFile)) {
|
||||
$teacherFile = $this->draftTeacherFile($draft);
|
||||
if (!empty($teacherFile)) {
|
||||
$copied = $this->copyDraftToFinal($teacherFile);
|
||||
if ($copied !== null) {
|
||||
$finalFile = $copied;
|
||||
$update['final_file'] = $copied;
|
||||
$update['final_filename'] = $this->draftTeacherFilename($draft) ?? $teacherFile;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (empty($finalFile)) {
|
||||
return;
|
||||
}
|
||||
$ext = strtolower(pathinfo($finalFile, PATHINFO_EXTENSION));
|
||||
$pdfName = $this->ensurePdfExists($finalFile, $ext);
|
||||
if ($pdfName === null) {
|
||||
return;
|
||||
}
|
||||
$filename = $update['final_filename'] ?? $draft['final_filename'] ?? '';
|
||||
$baseName = '';
|
||||
if ($filename !== '') {
|
||||
$baseName = pathinfo($filename, PATHINFO_FILENAME);
|
||||
}
|
||||
if ($baseName === '') {
|
||||
$baseName = pathinfo($pdfName, PATHINFO_FILENAME);
|
||||
}
|
||||
if ($baseName === '') {
|
||||
$baseName = 'exam';
|
||||
}
|
||||
$update['final_file'] = $pdfName;
|
||||
$update['final_filename'] = $baseName . '.pdf';
|
||||
if ($this->hasFinalPdfColumn) {
|
||||
$update['final_pdf_file'] = $pdfName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,6 +95,11 @@ class LandingPageController extends BaseController
|
||||
return null;
|
||||
}
|
||||
|
||||
// Teacher class sections only apply to the teacher role; other roles have no teacher_class rows.
|
||||
if (strtolower(trim((string) $this->getUserRole())) !== 'teacher') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get all class assignments for this teacher in the current term
|
||||
$assignments = $this->teacherClassModel->getClassAssignmentsByUserId(
|
||||
(int)$user_id,
|
||||
@@ -106,7 +111,7 @@ class LandingPageController extends BaseController
|
||||
$ids = array_values(array_filter(array_unique($ids)));
|
||||
|
||||
if (empty($ids)) {
|
||||
log_message('error', "No class section found for user ID: $user_id");
|
||||
log_message('warning', "No class section found for user ID: $user_id");
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -868,8 +873,12 @@ class LandingPageController extends BaseController
|
||||
|
||||
protected function getUserRole()
|
||||
{
|
||||
// Assuming you have a session variable storing the user role
|
||||
return session()->get('user_role', 'guest'); // Default to guest if not set
|
||||
$active = session()->get('active_role');
|
||||
if ($active !== null && $active !== '') {
|
||||
return (string) $active;
|
||||
}
|
||||
|
||||
return (string) (session()->get('role') ?? 'guest');
|
||||
}
|
||||
|
||||
protected function getUserRoleFromDatabase($user_id)
|
||||
|
||||
Reference in New Issue
Block a user