exam draft notification and submission
This commit is contained in:
@@ -26,6 +26,9 @@ class ExamDraftController extends BaseController
|
||||
protected bool $hasAcceptanceTypeColumn = false;
|
||||
protected bool $hasAdminIdColumn = false;
|
||||
protected bool $hasReviewRevisionColumn = false;
|
||||
protected bool $hasReviewerCommentColumn = false;
|
||||
protected bool $hasReviewerCommentsColumn = false;
|
||||
protected bool $hasAdminCommentsColumn = false;
|
||||
protected string $authorIdColumn = 'teacher_id';
|
||||
protected string $authorFileColumn = 'teacher_file';
|
||||
protected string $authorFilenameColumn = 'teacher_filename';
|
||||
@@ -65,6 +68,9 @@ class ExamDraftController extends BaseController
|
||||
$this->hasAcceptanceTypeColumn = $this->schemaHasColumn('exam_drafts', 'acceptance_type');
|
||||
$this->hasAdminIdColumn = $this->schemaHasColumn('exam_drafts', 'admin_id');
|
||||
$this->hasReviewRevisionColumn = $this->schemaHasColumn('exam_drafts', 'review_revision');
|
||||
$this->hasReviewerCommentColumn = $this->schemaHasColumn('exam_drafts', 'reviewer_comment');
|
||||
$this->hasReviewerCommentsColumn = $this->schemaHasColumn('exam_drafts', 'reviewer_comments');
|
||||
$this->hasAdminCommentsColumn = $this->schemaHasColumn('exam_drafts', 'admin_comments');
|
||||
$this->authorIdColumn = $this->schemaHasColumn('exam_drafts', 'teacher_id') ? 'teacher_id' : 'author_id';
|
||||
$this->authorFileColumn = $this->schemaHasColumn('exam_drafts', 'teacher_file') ? 'teacher_file' : 'author_file';
|
||||
$this->authorFilenameColumn = $this->schemaHasColumn('exam_drafts', 'teacher_filename') ? 'teacher_filename' : 'author_filename';
|
||||
@@ -73,7 +79,9 @@ class ExamDraftController extends BaseController
|
||||
: ($this->schemaHasColumn('exam_drafts', 'reviewer_id') ? 'reviewer_id' : '');
|
||||
$this->reviewerCommentColumn = $this->schemaHasColumn('exam_drafts', 'reviewer_comment')
|
||||
? 'reviewer_comment'
|
||||
: ($this->schemaHasColumn('exam_drafts', 'admin_comments') ? 'admin_comments' : '');
|
||||
: ($this->schemaHasColumn('exam_drafts', 'reviewer_comments')
|
||||
? 'reviewer_comments'
|
||||
: ($this->schemaHasColumn('exam_drafts', 'admin_comments') ? 'admin_comments' : ''));
|
||||
|
||||
helper(['form', 'url', 'date']);
|
||||
}
|
||||
@@ -116,19 +124,22 @@ class ExamDraftController extends BaseController
|
||||
// Keep all submissions visible; legacy ones are also surfaced in a separate tab
|
||||
$drafts = $allDrafts;
|
||||
|
||||
$legacyQuery = $this->examDraftModel
|
||||
->select($this->draftSelectColumns())
|
||||
->select('cs.class_section_name')
|
||||
->join('classSection cs', 'cs.class_section_id = exam_drafts.class_section_id', 'left')
|
||||
->where('exam_drafts.is_legacy', 1)
|
||||
->where('exam_drafts.status', 'legacy')
|
||||
->where('exam_drafts.final_file IS NOT NULL', null, false);
|
||||
|
||||
if (!empty($classSectionIds)) {
|
||||
$legacyExams = $this->examDraftModel
|
||||
->select($this->draftSelectColumns())
|
||||
->select('cs.class_section_name')
|
||||
->join('classSection cs', 'cs.class_section_id = exam_drafts.class_section_id', 'left')
|
||||
->whereIn('exam_drafts.class_section_id', $classSectionIds)
|
||||
->where('exam_drafts.is_legacy', 1)
|
||||
->where('exam_drafts.status', 'legacy')
|
||||
->where('exam_drafts.final_file IS NOT NULL', null, false)
|
||||
->orderBy('cs.class_section_name', 'ASC')
|
||||
->orderBy('exam_drafts.created_at', 'DESC')
|
||||
->findAll();
|
||||
$legacyQuery = $legacyQuery->whereIn('exam_drafts.class_section_id', $classSectionIds);
|
||||
}
|
||||
|
||||
$legacyExams = $legacyQuery
|
||||
->orderBy('cs.class_section_name', 'ASC')
|
||||
->orderBy('exam_drafts.created_at', 'DESC')
|
||||
->findAll();
|
||||
} else {
|
||||
// Legacy column absent, show all drafts and skip legacy tab query
|
||||
$drafts = $allDrafts;
|
||||
@@ -283,11 +294,12 @@ class ExamDraftController extends BaseController
|
||||
'status' => 'submitted',
|
||||
];
|
||||
$this->applyAuthorFilePayload($updateSubmit, $teacherFile, $teacherFilename);
|
||||
if ($this->examDraftModel->update($draftRowId, $updateSubmit)) {
|
||||
$saved = $this->ensureDraftStatus($draftRowId, 'submitted');
|
||||
$this->notifyExamDraftEvent($saved, 'submitted');
|
||||
return redirect()->to('/teacher/exam-drafts')->with('success', 'Exam draft submitted for review.');
|
||||
}
|
||||
if ($this->examDraftModel->update($draftRowId, $updateSubmit)) {
|
||||
$saved = $this->ensureDraftStatus($draftRowId, 'submitted');
|
||||
$this->notifyPrincipalExamDraftSubmitted($saved, $teacherId, $classSectionId);
|
||||
$this->notifyExamDraftEvent($saved, 'submitted');
|
||||
return redirect()->to('/teacher/exam-drafts')->with('success', 'Exam draft submitted for review.');
|
||||
}
|
||||
return redirect()->back()->withInput()->with('error', 'Unable to submit the exam draft.');
|
||||
}
|
||||
|
||||
@@ -319,6 +331,7 @@ class ExamDraftController extends BaseController
|
||||
if ($this->examDraftModel->insert($payload)) {
|
||||
$newId = (int) $this->examDraftModel->getInsertID();
|
||||
$saved = $newId > 0 ? $this->ensureDraftStatus($newId, 'submitted') : null;
|
||||
$this->notifyPrincipalExamDraftSubmitted($saved, $teacherId, $classSectionId);
|
||||
$this->notifyExamDraftEvent($saved, 'submitted');
|
||||
return redirect()->to('/teacher/exam-drafts')->with('success', 'Exam draft submitted for review.');
|
||||
}
|
||||
@@ -556,8 +569,16 @@ class ExamDraftController extends BaseController
|
||||
'status' => $status,
|
||||
'reviewed_at' => utc_now(),
|
||||
];
|
||||
if ($this->reviewerCommentColumn !== '') {
|
||||
$update[$this->reviewerCommentColumn] = $reviewerComment === '' ? null : $reviewerComment;
|
||||
$existingComment = (string) ($draft['reviewer_comment'] ?? $draft['reviewer_comments'] ?? $draft['admin_comments'] ?? '');
|
||||
$commentValue = $reviewerComment === '' ? null : $this->appendReviewerComment($existingComment, $reviewerComment);
|
||||
if ($this->hasReviewerCommentColumn) {
|
||||
$update['reviewer_comment'] = $commentValue;
|
||||
}
|
||||
if ($this->hasReviewerCommentsColumn) {
|
||||
$update['reviewer_comments'] = $commentValue;
|
||||
}
|
||||
if ($this->hasAdminCommentsColumn) {
|
||||
$update['admin_comments'] = $commentValue;
|
||||
}
|
||||
if ($this->reviewerIdColumn !== '') {
|
||||
$update[$this->reviewerIdColumn] = (int) (session()->get('user_id') ?? 0);
|
||||
@@ -589,8 +610,14 @@ class ExamDraftController extends BaseController
|
||||
if ($this->hasReviewRevisionColumn) {
|
||||
$newRow['review_revision'] = $reviewRevision;
|
||||
}
|
||||
if ($this->reviewerCommentColumn !== '') {
|
||||
$newRow[$this->reviewerCommentColumn] = $update[$this->reviewerCommentColumn] ?? null;
|
||||
if ($this->hasReviewerCommentColumn) {
|
||||
$newRow['reviewer_comment'] = $commentValue;
|
||||
}
|
||||
if ($this->hasReviewerCommentsColumn) {
|
||||
$newRow['reviewer_comments'] = $commentValue;
|
||||
}
|
||||
if ($this->hasAdminCommentsColumn) {
|
||||
$newRow['admin_comments'] = $commentValue;
|
||||
}
|
||||
if ($this->reviewerIdColumn !== '') {
|
||||
$newRow[$this->reviewerIdColumn] = $update[$this->reviewerIdColumn] ?? null;
|
||||
@@ -831,6 +858,46 @@ class ExamDraftController extends BaseController
|
||||
return $saved;
|
||||
}
|
||||
|
||||
private function notifyPrincipalExamDraftSubmitted(?array $draft, int $teacherId, int $classSectionId): void
|
||||
{
|
||||
$principalEmail = trim((string) (env('PRINCIPAL_EMAIL') ?: ''));
|
||||
if ($principalEmail === '' || !filter_var($principalEmail, FILTER_VALIDATE_EMAIL)) {
|
||||
return;
|
||||
}
|
||||
if (empty($draft)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$teacher = $this->userModel->find($teacherId) ?? [];
|
||||
$teacherName = trim(($teacher['firstname'] ?? '') . ' ' . ($teacher['lastname'] ?? ''));
|
||||
if ($teacherName === '') {
|
||||
$teacherName = 'Teacher #' . $teacherId;
|
||||
}
|
||||
|
||||
$class = $this->classSectionModel->find($classSectionId) ?? [];
|
||||
$className = $class['class_section_name'] ?? ('Class ' . $classSectionId);
|
||||
|
||||
$subject = 'Exam draft submitted: ' . $className;
|
||||
$submittedAt = $draft['created_at'] ?? $draft['updated_at'] ?? '';
|
||||
$body = '<p>A teacher has submitted a new exam draft.</p>'
|
||||
. '<table style="border-collapse:collapse;">'
|
||||
. '<tr><td style="padding:4px 8px;"><strong>Teacher</strong></td><td style="padding:4px 8px;">' . esc($teacherName) . '</td></tr>'
|
||||
. '<tr><td style="padding:4px 8px;"><strong>Class</strong></td><td style="padding:4px 8px;">' . esc($className) . '</td></tr>'
|
||||
. '<tr><td style="padding:4px 8px;"><strong>Exam type</strong></td><td style="padding:4px 8px;">' . esc($draft['exam_type'] ?? 'N/A') . '</td></tr>'
|
||||
. '<tr><td style="padding:4px 8px;"><strong>Version</strong></td><td style="padding:4px 8px;">' . esc((string) ($draft['version'] ?? '')) . '</td></tr>'
|
||||
. '<tr><td style="padding:4px 8px;"><strong>Status</strong></td><td style="padding:4px 8px;">' . esc((string) ($draft['status'] ?? 'submitted')) . '</td></tr>'
|
||||
. '<tr><td style="padding:4px 8px;"><strong>Submitted at</strong></td><td style="padding:4px 8px;">' . esc((string) $submittedAt) . '</td></tr>'
|
||||
. '</table>'
|
||||
. '<p>Review drafts: <a href="' . esc(base_url('administrator/exam-drafts')) . '">Admin exam drafts</a></p>';
|
||||
|
||||
$mailer = \Config\Services::emailService();
|
||||
$mailer->send($principalEmail, $subject, $body, 'notifications');
|
||||
} catch (\Throwable $e) {
|
||||
log_message('error', 'Failed to send exam draft notification: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private function nextDraftVersion(array $draft): int
|
||||
{
|
||||
$query = $this->examDraftModel
|
||||
@@ -887,6 +954,36 @@ class ExamDraftController extends BaseController
|
||||
return $current + 1;
|
||||
}
|
||||
|
||||
private function appendReviewerComment(string $existing, string $incoming): string
|
||||
{
|
||||
$existing = trim($existing);
|
||||
$incoming = trim($incoming);
|
||||
if ($incoming === '') {
|
||||
return $existing;
|
||||
}
|
||||
|
||||
if ($existing !== '' && str_starts_with($incoming, $existing)) {
|
||||
$remainder = trim(substr($incoming, strlen($existing)));
|
||||
if ($remainder === '') {
|
||||
return $existing;
|
||||
}
|
||||
$incoming = $remainder;
|
||||
}
|
||||
|
||||
$lines = $existing === '' ? [] : preg_split('/\R/', $existing);
|
||||
$lastLine = $lines ? trim((string) end($lines)) : '';
|
||||
if ($lastLine !== '') {
|
||||
$lastBody = preg_replace('/^\d+\-\s*/', '', $lastLine);
|
||||
if ($lastBody !== null && trim($lastBody) === $incoming) {
|
||||
return $existing;
|
||||
}
|
||||
}
|
||||
$nextIndex = count($lines) + 1;
|
||||
$incoming = trim(preg_replace('/\R+/', ' ', $incoming) ?? $incoming);
|
||||
$lines[] = $nextIndex . '- ' . $incoming;
|
||||
return implode("\n", $lines);
|
||||
}
|
||||
|
||||
private function statusOptions(): array
|
||||
{
|
||||
return ['submitted', 'accepted', 'review needed', 'rejected', 'canceled', 'under review', 'legacy'];
|
||||
@@ -1001,6 +1098,7 @@ class ExamDraftController extends BaseController
|
||||
$grouped[$key]['_is_review_row'] = true;
|
||||
$grouped[$key]['review_files'] = [];
|
||||
$grouped[$key]['final_version'] = null;
|
||||
$grouped[$key]['_latest_review_comment'] = null;
|
||||
}
|
||||
$grouped[$key]['review_files'][] = [
|
||||
'review_revision' => $reviewRev,
|
||||
@@ -1008,6 +1106,16 @@ class ExamDraftController extends BaseController
|
||||
'final_filename' => $row['final_filename'] ?? null,
|
||||
'status' => $row['status'] ?? null,
|
||||
];
|
||||
$reviewComment = $row['reviewer_comment'] ?? $row['admin_comments'] ?? null;
|
||||
if ($reviewComment !== null && $reviewComment !== '') {
|
||||
$latestComment = $grouped[$key]['_latest_review_comment'] ?? null;
|
||||
if ($latestComment === null || $reviewRev > (int) ($latestComment['review_revision'] ?? 0)) {
|
||||
$grouped[$key]['_latest_review_comment'] = [
|
||||
'review_revision' => $reviewRev,
|
||||
'comment' => $reviewComment,
|
||||
];
|
||||
}
|
||||
}
|
||||
if (strtolower((string) ($row['status'] ?? '')) === 'accepted') {
|
||||
$currentFinal = $grouped[$key]['final_version'] ?? null;
|
||||
if ($currentFinal === null || $reviewRev > (int) ($currentFinal['review_revision'] ?? 0)) {
|
||||
@@ -1029,8 +1137,12 @@ class ExamDraftController extends BaseController
|
||||
|
||||
if (!empty($grouped[$key]['_is_review_row'])) {
|
||||
$reviewFiles = $grouped[$key]['review_files'] ?? [];
|
||||
$latestReviewComment = $grouped[$key]['_latest_review_comment'] ?? null;
|
||||
$grouped[$key] = $row;
|
||||
$grouped[$key]['review_files'] = $reviewFiles;
|
||||
if (!empty($latestReviewComment['comment'])) {
|
||||
$grouped[$key]['reviewer_comment'] = $latestReviewComment['comment'];
|
||||
}
|
||||
unset($grouped[$key]['_is_review_row']);
|
||||
}
|
||||
}
|
||||
@@ -1043,7 +1155,11 @@ class ExamDraftController extends BaseController
|
||||
$row['final_file'] = $row['final_version']['final_file'] ?? $row['final_file'] ?? null;
|
||||
$row['final_filename'] = $row['final_version']['final_filename'] ?? $row['final_filename'] ?? null;
|
||||
}
|
||||
if (empty($row['reviewer_comment']) && !empty($row['_latest_review_comment']['comment'])) {
|
||||
$row['reviewer_comment'] = $row['_latest_review_comment']['comment'];
|
||||
}
|
||||
unset($row['_is_review_row']);
|
||||
unset($row['_latest_review_comment']);
|
||||
}
|
||||
unset($row);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user