fix draft exam upload

This commit is contained in:
root
2026-05-08 00:33:40 -04:00
parent 76866ffe1e
commit 9aacbe8972
3 changed files with 81 additions and 20 deletions
+3
View File
@@ -745,6 +745,9 @@ $routes->post('/administrator/teacher-submissions/notify', 'View\AdministratorCo
$routes->get('/administrator/exam-drafts', 'View\ExamDraftController::adminIndex', ['filter' => 'auth:admin']);
$routes->post('/administrator/exam-drafts/review', 'View\ExamDraftController::adminReview', ['filter' => 'auth:admin']);
$routes->post('/administrator/exam-drafts/upload-legacy', 'View\ExamDraftController::adminUploadLegacy', ['filter' => 'auth:admin']);
$routes->get('/principal/exam-drafts', 'View\ExamDraftController::principalIndex', ['filter' => 'auth:admin']);
$routes->post('/principal/exam-drafts/review', 'View\ExamDraftController::principalReview', ['filter' => 'auth:admin']);
$routes->post('/principal/exam-drafts/upload-legacy', 'View\ExamDraftController::principalUploadLegacy', ['filter' => 'auth:admin']);
/*
* --------------------------------------------------------------------
+72 -17
View File
@@ -348,6 +348,16 @@ class ExamDraftController extends BaseController
}
public function adminIndex()
{
return $this->reviewIndex();
}
public function principalIndex()
{
return $this->reviewIndex();
}
public function reviewIndex()
{
if ($this->reviewerIdColumn !== '') {
$allDrafts = $this->examDraftModel
@@ -487,10 +497,23 @@ class ExamDraftController extends BaseController
'visibleClasses' => $visibleClasses,
'classDraftGroups' => $classDraftGroups,
'newSubmissionClasses' => $newSubmissionClasses,
'reviewActionUrl' => base_url($this->reviewRoutePrefix() . '/exam-drafts/review'),
'legacyUploadUrl' => base_url($this->reviewRoutePrefix() . '/exam-drafts/upload-legacy'),
'reviewPortalLabel' => $this->reviewPortalLabel(),
]);
}
public function adminUploadLegacy()
{
return $this->reviewUploadLegacy();
}
public function principalUploadLegacy()
{
return $this->reviewUploadLegacy();
}
public function reviewUploadLegacy()
{
$adminId = (int) (session()->get('user_id') ?? 0);
if ($adminId <= 0) {
@@ -571,13 +594,23 @@ class ExamDraftController extends BaseController
}
if ($saved > 0) {
return redirect()->to('/administrator/exam-drafts')->with('success', 'Old exam uploaded successfully.');
return redirect()->to($this->reviewDashboardPath())->with('success', 'Old exam uploaded successfully.');
}
return redirect()->back()->withInput()->with('error', 'Unable to save the old exam.');
}
public function adminReview()
{
return $this->reviewSubmission();
}
public function principalReview()
{
return $this->reviewSubmission();
}
public function reviewSubmission()
{
$draftId = (int) ($this->request->getPost('draft_id') ?? 0);
if ($draftId <= 0) {
@@ -690,11 +723,6 @@ class ExamDraftController extends BaseController
if ($this->hasAcceptanceTypeColumn) {
$newRow['acceptance_type'] = $status === 'accepted' ? $acceptanceType : null;
}
$teacherFile = $this->draftTeacherFile($draft);
if (!empty($teacherFile)) {
$newRow[$this->authorFileColumn] = $teacherFile;
$newRow[$this->authorFilenameColumn] = $this->draftTeacherFilename($draft) ?? $teacherFile;
}
if ($this->hasIsLegacyColumn && !empty($draft['is_legacy'])) {
$newRow['is_legacy'] = 1;
}
@@ -711,16 +739,6 @@ class ExamDraftController extends BaseController
return redirect()->back()->with('success', 'Review saved successfully.');
}
return redirect()->back()->with('error', 'Unable to save the review.');
} elseif (strtolower($status) === 'accepted' && !empty($this->draftTeacherFile($draft))) {
$copied = $this->copyDraftToFinal($this->draftTeacherFile($draft));
if ($copied !== null) {
$update['final_file'] = $copied;
$update['final_filename'] = $this->draftTeacherFilename($draft) ?? $this->draftTeacherFile($draft);
$pdfName = $this->ensurePdfExists($copied, pathinfo($copied, PATHINFO_EXTENSION));
if ($pdfName !== null && $this->hasFinalPdfColumn) {
$update['final_pdf_file'] = $pdfName;
}
}
}
if ($status === 'legacy') {
$this->prepareLegacyPdfVersion($update, $draft);
@@ -1010,7 +1028,7 @@ class ExamDraftController extends BaseController
. '<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>';
. '<p>Review drafts: <a href="' . esc(base_url($this->reviewRoutePrefix() . '/exam-drafts')) . '">' . esc($this->reviewPortalLabel()) . ' exam drafts</a></p>';
$mailer = \Config\Services::emailService();
$mailer->send($principalEmail, $subject, $body, 'notifications');
@@ -1019,6 +1037,38 @@ class ExamDraftController extends BaseController
}
}
private function reviewDashboardPath(): string
{
return '/' . $this->reviewRoutePrefix() . '/exam-drafts';
}
private function reviewRoutePrefix(): string
{
return $this->isPrincipalReviewer() ? 'principal' : 'administrator';
}
private function reviewPortalLabel(): string
{
return $this->isPrincipalReviewer() ? 'Principal' : 'Administrator';
}
private function isPrincipalReviewer(): bool
{
$roles = session()->get('roles');
if (!is_array($roles)) {
$roles = $roles === null ? [] : [$roles];
}
foreach ($roles as $role) {
$normalized = strtolower(trim(str_replace([' ', '-'], '_', (string) $role)));
if ($normalized === 'principal') {
return true;
}
}
return false;
}
private function nextDraftVersion(array $draft): int
{
$query = $this->examDraftModel
@@ -1373,6 +1423,11 @@ class ExamDraftController extends BaseController
$base = pathinfo($sourcePath, PATHINFO_FILENAME);
$targetPath = $targetDir . '/' . $base . '.pdf';
if (!function_exists('exec')) {
log_message('warning', 'ExamDraftController::convertDocToPdf skipped because exec() is unavailable.');
return is_file($targetPath) ? basename($targetPath) : null;
}
// Attempt conversion via LibreOffice if available
$cmd = 'soffice --headless --convert-to pdf --outdir ' . escapeshellarg($targetDir) . ' ' . escapeshellarg($sourcePath) . ' 2>/dev/null';
@\exec($cmd);
+6 -3
View File
@@ -13,6 +13,9 @@ $schoolYear = $schoolYear ?? '';
$semester = $semester ?? '';
$maxUploadBytes = $maxUploadBytes ?? (12 * 1024 * 1024);
$allowedExtensions = $allowedExtensions ?? ['doc', 'docx', 'pdf'];
$reviewActionUrl = $reviewActionUrl ?? base_url('administrator/exam-drafts/review');
$legacyUploadUrl = $legacyUploadUrl ?? base_url('administrator/exam-drafts/upload-legacy');
$reviewPortalLabel = $reviewPortalLabel ?? 'Administrator';
$renderBadge = static function (string $status, array $badges): string {
$b = $badges[$status] ?? ['label' => $status, 'class' => 'bg-secondary text-white'];
@@ -27,7 +30,7 @@ $fileAccept = implode(',', array_map(static fn ($x) => '.' . $x, $allowedExtensi
<div>
<h1 class="h3 mb-1">Exam draft submissions</h1>
<p class="text-muted mb-0 small">
<?= esc($semester ?: 'Semester') ?> <?= esc($schoolYear ?: '') ?>
<?= esc($reviewPortalLabel) ?> review portal · <?= esc($semester ?: 'Semester') ?> <?= esc($schoolYear ?: '') ?>
</p>
</div>
<div class="text-muted small d-flex flex-column align-items-lg-end">
@@ -205,7 +208,7 @@ $fileAccept = implode(',', array_map(static fn ($x) => '.' . $x, $allowedExtensi
</div>
</td>
<td style="min-width: 280px;">
<?= form_open_multipart(base_url('administrator/exam-drafts/review'), ['class' => 'vstack gap-2', 'id' => $formId]) ?>
<?= form_open_multipart($reviewActionUrl, ['class' => 'vstack gap-2', 'id' => $formId]) ?>
<?= csrf_field() ?>
<input type="hidden" name="draft_id" value="<?= (int) ($draft['id'] ?? 0) ?>">
<?php $selectedStatus = ''; ?>
@@ -276,7 +279,7 @@ $fileAccept = implode(',', array_map(static fn ($x) => '.' . $x, $allowedExtensi
<span class="badge bg-primary-subtle text-primary">Admin only</span>
</div>
<div class="card-body">
<?= form_open_multipart(base_url('administrator/exam-drafts/upload-legacy'), ['class' => 'row g-3']) ?>
<?= form_open_multipart($legacyUploadUrl, ['class' => 'row g-3']) ?>
<?= csrf_field() ?>
<div class="col-md-4">
<label class="form-label small">Class sections</label>