617 lines
24 KiB
PHP
617 lines
24 KiB
PHP
<?php
|
|
|
|
namespace App\Controllers\View;
|
|
|
|
use App\Controllers\BaseController;
|
|
use App\Models\ClassSectionModel;
|
|
use App\Models\ConfigurationModel;
|
|
use App\Models\ExamDraftModel;
|
|
use App\Models\TeacherClassModel;
|
|
use App\Models\UserModel;
|
|
use CodeIgniter\HTTP\Files\UploadedFile;
|
|
use Config\Database;
|
|
|
|
class ExamDraftController extends BaseController
|
|
{
|
|
protected ExamDraftModel $examDraftModel;
|
|
protected TeacherClassModel $teacherClassModel;
|
|
protected ClassSectionModel $classSectionModel;
|
|
protected UserModel $userModel;
|
|
protected ConfigurationModel $configModel;
|
|
|
|
protected string $schoolYear;
|
|
protected string $semester;
|
|
protected bool $hasFinalPdfColumn = false;
|
|
protected bool $hasIsLegacyColumn = false;
|
|
|
|
// DB enum: draft, submitted, reviewed, finalized, rejected
|
|
// (string literals used to avoid excess constants)
|
|
|
|
protected const TEACHER_UPLOAD_DIR = 'exams/drafts';
|
|
protected const FINAL_UPLOAD_DIR = 'exams/finals';
|
|
protected const MAX_UPLOAD_BYTES = 12 * 1024 * 1024;
|
|
protected const ALLOWED_EXTENSIONS = ['doc', 'docx'];
|
|
protected const ADMIN_ALLOWED_EXTENSIONS = ['doc', 'docx', 'pdf'];
|
|
|
|
protected array $examTypes = [
|
|
'Final Exam',
|
|
'Midterm Exam',
|
|
'Quiz',
|
|
'Study Guide',
|
|
'Practice Exam',
|
|
'Other',
|
|
];
|
|
|
|
public function __construct()
|
|
{
|
|
$this->examDraftModel = new ExamDraftModel();
|
|
$this->teacherClassModel = new TeacherClassModel();
|
|
$this->classSectionModel = new ClassSectionModel();
|
|
$this->userModel = new UserModel();
|
|
$this->configModel = new ConfigurationModel();
|
|
$this->db = Database::connect();
|
|
|
|
$this->schoolYear = (string) ($this->configModel->getConfig('school_year') ?? '');
|
|
$this->semester = (string) ($this->configModel->getConfig('semester') ?? '');
|
|
$this->hasFinalPdfColumn = $this->schemaHasColumn('exam_drafts', 'final_pdf_file');
|
|
$this->hasIsLegacyColumn = $this->schemaHasColumn('exam_drafts', 'is_legacy');
|
|
|
|
helper(['form', 'url', 'date']);
|
|
}
|
|
|
|
public function teacherIndex()
|
|
{
|
|
$teacherId = (int) (session()->get('user_id') ?? 0);
|
|
if ($teacherId <= 0) {
|
|
return redirect()->to('/login');
|
|
}
|
|
|
|
$assignments = $this->teacherClassModel->getClassAssignmentsByUserId($teacherId, $this->schoolYear);
|
|
$selectedClass = $this->resolveSelectedClassSection($assignments);
|
|
if ($selectedClass > 0) {
|
|
session()->set('class_section_id', $selectedClass);
|
|
}
|
|
|
|
$allDrafts = $this->examDraftModel
|
|
->where('teacher_id', $teacherId)
|
|
->orderBy('created_at', 'DESC')
|
|
->findAll();
|
|
|
|
foreach ($allDrafts as &$row) {
|
|
if (empty($row['final_pdf_file'])) {
|
|
$pdf = $this->ensurePdfExists($row['final_file'] ?? '', pathinfo($row['final_file'] ?? '', PATHINFO_EXTENSION));
|
|
if ($pdf !== null) {
|
|
$row['final_pdf_file'] = $pdf;
|
|
}
|
|
}
|
|
}
|
|
unset($row);
|
|
|
|
$classSectionIds = array_map(static fn($a) => (int) $a['class_section_id'], $assignments);
|
|
$legacyExams = [];
|
|
// Guard against missing schema column in older databases
|
|
if ($this->hasIsLegacyColumn) {
|
|
// Keep all submissions visible; legacy ones are also surfaced in a separate tab
|
|
$drafts = $allDrafts;
|
|
|
|
if (!empty($classSectionIds)) {
|
|
$legacyExams = $this->examDraftModel
|
|
->select('exam_drafts.*, 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.final_file IS NOT NULL', null, false)
|
|
->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;
|
|
}
|
|
|
|
$validation = session()->getFlashdata('validation') ?? $this->validator;
|
|
|
|
return view('teacher/exam_drafts', [
|
|
'assignments' => $assignments,
|
|
'selectedClassSection' => $selectedClass,
|
|
'drafts' => $drafts,
|
|
'legacyExams' => $legacyExams,
|
|
'examTypes' => $this->examTypes,
|
|
'statusBadges' => $this->statusBadgeMap(),
|
|
'schoolYear' => $this->schoolYear,
|
|
'semester' => $this->semester,
|
|
'maxUploadBytes' => self::MAX_UPLOAD_BYTES,
|
|
'validation' => $validation,
|
|
]);
|
|
}
|
|
|
|
public function teacherStore()
|
|
{
|
|
$teacherId = (int) (session()->get('user_id') ?? 0);
|
|
if ($teacherId <= 0) {
|
|
return redirect()->to('/login');
|
|
}
|
|
|
|
$classSectionId = (int) ($this->request->getPost('class_section_id') ?? session()->get('class_section_id') ?? 0);
|
|
$examType = trim((string) $this->request->getPost('exam_type'));
|
|
$description = trim((string) $this->request->getPost('description'));
|
|
|
|
if ($classSectionId <= 0) {
|
|
return redirect()->back()->withInput()->with('error', 'Select a class section before submitting.');
|
|
}
|
|
|
|
$assignment = $this->teacherClassModel
|
|
->where('teacher_id', $teacherId)
|
|
->where('class_section_id', $classSectionId)
|
|
->first();
|
|
|
|
if (empty($assignment)) {
|
|
return redirect()->back()->withInput()->with('error', 'You are not assigned to the selected class section.');
|
|
}
|
|
|
|
session()->set('class_section_id', $classSectionId);
|
|
|
|
if ($classSectionId <= 0) {
|
|
$classSectionId = $this->resolveSelectedClassSection($assignments);
|
|
if ($classSectionId <= 0) {
|
|
return redirect()->back()->withInput()->with('error', 'Select a class section before submitting.');
|
|
}
|
|
}
|
|
|
|
$file = $this->request->getFile('draft_file');
|
|
$teacherFile = null;
|
|
$teacherFilename = null;
|
|
if ($file && $file->isValid() && !$file->hasMoved()) {
|
|
$stored = $this->storeUploadedFile($file, self::TEACHER_UPLOAD_DIR);
|
|
if ($stored === null) {
|
|
return redirect()->back()->withInput()->with('error', 'Failed to store the uploaded file.');
|
|
}
|
|
$teacherFile = $stored;
|
|
$teacherFilename = $file->getClientName();
|
|
} elseif ($file && $file->getError() !== UPLOAD_ERR_NO_FILE) {
|
|
return redirect()->back()->withInput()->with('error', 'Upload failed. Please try again.');
|
|
}
|
|
|
|
$existingDraft = $this->examDraftModel
|
|
->where('teacher_id', $teacherId)
|
|
->where('class_section_id', $classSectionId)
|
|
->where('semester', $this->semester)
|
|
->where('school_year', $this->schoolYear)
|
|
->orderBy('version', 'DESC')
|
|
->first();
|
|
|
|
$nextVersion = 1;
|
|
$previousId = null;
|
|
if (!empty($existingDraft)) {
|
|
$nextVersion = ((int) ($existingDraft['version'] ?? 1)) + 1;
|
|
$previousId = (int) ($existingDraft['id'] ?? 0) ?: null;
|
|
}
|
|
|
|
$title = $examType ?: 'Exam Draft';
|
|
|
|
$payload = [
|
|
'teacher_id' => $teacherId,
|
|
'class_section_id' => $classSectionId,
|
|
'semester' => $this->semester,
|
|
'school_year' => $this->schoolYear,
|
|
'exam_type' => $examType ?: null,
|
|
'draft_title' => $title,
|
|
'description' => $description === '' ? null : $description,
|
|
'teacher_file' => $teacherFile,
|
|
'teacher_filename' => $teacherFilename,
|
|
'status' => 'submitted',
|
|
'version' => $nextVersion,
|
|
'previous_draft_id' => $previousId,
|
|
];
|
|
|
|
if ($this->examDraftModel->insert($payload)) {
|
|
return redirect()->to('/teacher/exam-drafts')->with('success', 'Exam draft submitted for review.');
|
|
}
|
|
|
|
return redirect()->back()->withInput()->with('error', 'Unable to save the exam draft.');
|
|
}
|
|
|
|
public function adminIndex()
|
|
{
|
|
$allDrafts = $this->examDraftModel
|
|
->select('exam_drafts.*, cs.class_section_name, u.firstname AS teacher_first, u.lastname AS teacher_last, a.firstname AS admin_first, a.lastname AS admin_last')
|
|
->join('classSection cs', 'cs.class_section_id = exam_drafts.class_section_id', 'left')
|
|
->join('users u', 'u.id = exam_drafts.teacher_id', 'left')
|
|
->join('users a', 'a.id = exam_drafts.admin_id', 'left')
|
|
->orderBy('exam_drafts.created_at', 'DESC')
|
|
->findAll();
|
|
|
|
foreach ($allDrafts as &$row) {
|
|
if (empty($row['final_pdf_file'])) {
|
|
$pdf = $this->ensurePdfExists($row['final_file'] ?? '', pathinfo($row['final_file'] ?? '', PATHINFO_EXTENSION));
|
|
if ($pdf !== null) {
|
|
$row['final_pdf_file'] = $pdf;
|
|
}
|
|
}
|
|
}
|
|
unset($row);
|
|
|
|
$classSections = $this->classSectionModel
|
|
->select('class_section_id, class_section_name')
|
|
->orderBy('class_section_name', 'ASC')
|
|
->findAll();
|
|
|
|
// Group legacy uploads (admin-uploaded finalized exams) by class_section for separate tab
|
|
$legacyByClass = [];
|
|
if ($this->hasIsLegacyColumn) {
|
|
// Keep legacy items out of the main submissions list; show them in the legacy tab only.
|
|
$drafts = [];
|
|
foreach ($allDrafts as $d) {
|
|
$isLegacy = !empty($d['is_legacy']);
|
|
if ($isLegacy) {
|
|
$cid = (int)($d['class_section_id'] ?? 0);
|
|
if (!isset($legacyByClass[$cid])) {
|
|
$legacyByClass[$cid] = [
|
|
'class_section_id' => $cid,
|
|
'class_section_name' => $d['class_section_name'] ?? 'Class ' . $cid,
|
|
'items' => [],
|
|
];
|
|
}
|
|
$legacyByClass[$cid]['items'][] = $d;
|
|
continue;
|
|
}
|
|
$drafts[] = $d;
|
|
}
|
|
} else {
|
|
// Column missing: keep behavior simple and avoid legacy tab
|
|
$drafts = $allDrafts;
|
|
}
|
|
|
|
return view('administrator/exam_drafts', [
|
|
'drafts' => $drafts,
|
|
'statusBadges' => $this->statusBadgeMap(),
|
|
'statusOptions' => $this->statusOptions(),
|
|
'schoolYear' => $this->schoolYear,
|
|
'semester' => $this->semester,
|
|
'allowedExtensions' => self::ADMIN_ALLOWED_EXTENSIONS,
|
|
'maxUploadBytes' => self::MAX_UPLOAD_BYTES,
|
|
'examTypes' => $this->examTypes,
|
|
'classSections' => $classSections,
|
|
'legacyByClass' => $legacyByClass,
|
|
]);
|
|
}
|
|
|
|
public function adminUploadLegacy()
|
|
{
|
|
$adminId = (int) (session()->get('user_id') ?? 0);
|
|
if ($adminId <= 0) {
|
|
return redirect()->to('/login');
|
|
}
|
|
|
|
$classSectionIds = $this->request->getPost('class_section_ids');
|
|
if (!is_array($classSectionIds)) {
|
|
$classSectionIds = [$classSectionIds];
|
|
}
|
|
$classSectionIds = array_values(array_filter(array_map('intval', $classSectionIds)));
|
|
$schoolYear = trim((string) ($this->request->getPost('school_year') ?? $this->schoolYear));
|
|
$semester = trim((string) ($this->request->getPost('semester') ?? $this->semester));
|
|
$examType = trim((string) $this->request->getPost('exam_type'));
|
|
|
|
if (empty($classSectionIds)) {
|
|
return redirect()->back()->withInput()->with('error', 'Select at least one class section.');
|
|
}
|
|
if ($schoolYear === '') {
|
|
return redirect()->back()->withInput()->with('error', 'School year is required.');
|
|
}
|
|
if ($semester === '') {
|
|
return redirect()->back()->withInput()->with('error', 'Semester is required.');
|
|
}
|
|
|
|
$file = $this->request->getFile('old_exam_file');
|
|
if (!$file || !$file->isValid()) {
|
|
return redirect()->back()->withInput()->with('error', 'A valid file is required.');
|
|
}
|
|
|
|
$stored = $this->storeUploadedFile($file, self::FINAL_UPLOAD_DIR, self::ADMIN_ALLOWED_EXTENSIONS);
|
|
if ($stored === null) {
|
|
return redirect()->back()->withInput()->with('error', 'File type not allowed or upload failed.');
|
|
}
|
|
|
|
$pdfName = null;
|
|
if (strtolower($file->getClientExtension()) === 'pdf') {
|
|
$pdfName = $stored;
|
|
} else {
|
|
$pdfName = $this->convertDocToPdf(
|
|
$this->fullUploadPath(self::FINAL_UPLOAD_DIR, $stored),
|
|
self::FINAL_UPLOAD_DIR
|
|
);
|
|
}
|
|
$basePayload = [
|
|
'teacher_id' => $adminId, // store under admin user since legacy uploads are admin-only
|
|
'semester' => ucfirst(strtolower($semester)),
|
|
'school_year' => $schoolYear,
|
|
'exam_type' => $examType === '' ? null : $examType,
|
|
'draft_title' => $examType === '' ? 'Legacy Exam' : $examType,
|
|
'description' => null,
|
|
'final_file' => $stored,
|
|
'final_filename' => $file->getClientName(),
|
|
'status' => 'finalized',
|
|
'admin_id' => $adminId,
|
|
'reviewed_at' => utc_now(),
|
|
'version' => 1,
|
|
];
|
|
if ($this->hasIsLegacyColumn) {
|
|
$basePayload['is_legacy'] = 1;
|
|
}
|
|
if ($pdfName !== null && $this->hasFinalPdfColumn) {
|
|
$basePayload['final_pdf_file'] = $pdfName;
|
|
}
|
|
|
|
$saved = 0;
|
|
foreach ($classSectionIds as $classSectionId) {
|
|
$payload = $basePayload;
|
|
$payload['class_section_id'] = $classSectionId;
|
|
if ($this->examDraftModel->insert($payload)) {
|
|
$saved++;
|
|
}
|
|
}
|
|
|
|
if ($saved > 0) {
|
|
return redirect()->to('/administrator/exam-drafts')->with('success', 'Old exam uploaded successfully.');
|
|
}
|
|
|
|
return redirect()->back()->withInput()->with('error', 'Unable to save the old exam.');
|
|
}
|
|
|
|
public function adminReview()
|
|
{
|
|
$draftId = (int) ($this->request->getPost('draft_id') ?? 0);
|
|
if ($draftId <= 0) {
|
|
return redirect()->back()->with('error', 'Invalid submission selected.');
|
|
}
|
|
|
|
$draft = $this->examDraftModel->find($draftId);
|
|
if (empty($draft)) {
|
|
return redirect()->back()->with('error', 'Submission not found.');
|
|
}
|
|
|
|
$comments = trim((string) $this->request->getPost('admin_comments'));
|
|
$statusInput = trim((string) $this->request->getPost('review_status'));
|
|
$currentStatus = (string) ($draft['status'] ?? 'draft');
|
|
$status = $this->normalizeStatus(
|
|
$statusInput !== '' ? $statusInput : $currentStatus,
|
|
$currentStatus
|
|
);
|
|
$status = strtolower($status);
|
|
if (!in_array($status, $this->statusOptions(), true)) {
|
|
$status = 'reviewed';
|
|
}
|
|
$finalFile = null;
|
|
$finalFilename = null;
|
|
|
|
$file = $this->request->getFile('final_file');
|
|
if ($file && $file->isValid() && !$file->hasMoved()) {
|
|
$stored = $this->storeUploadedFile($file, self::FINAL_UPLOAD_DIR);
|
|
if ($stored === null) {
|
|
return redirect()->back()->with('error', 'Unable to store the final draft.')->withInput();
|
|
}
|
|
$finalFile = $stored;
|
|
$finalFilename = $file->getClientName();
|
|
// Only auto-finalize if the admin explicitly chose "finalized"
|
|
// (previously any uploaded file forced finalization)
|
|
if ($status === 'finalized') {
|
|
$status = 'finalized';
|
|
}
|
|
} elseif ($file && $file->getError() !== UPLOAD_ERR_NO_FILE) {
|
|
return redirect()->back()->with('error', 'Final file upload failed.');
|
|
}
|
|
|
|
$update = [
|
|
'status' => $status,
|
|
'admin_comments' => $comments === '' ? null : $comments,
|
|
'admin_id' => (int) (session()->get('user_id') ?? 0),
|
|
'reviewed_at' => utc_now(),
|
|
];
|
|
|
|
if ($finalFile !== null) {
|
|
$update['final_file'] = $finalFile;
|
|
$update['final_filename'] = $finalFilename;
|
|
$pdfName = $this->ensurePdfExists($finalFile, $file ? $file->getClientExtension() : null);
|
|
if ($pdfName !== null && $this->hasFinalPdfColumn) {
|
|
$update['final_pdf_file'] = $pdfName;
|
|
}
|
|
} elseif (strtolower($status) === 'finalized' && !empty($draft['teacher_file'])) {
|
|
// Auto-promote teacher file when admin finalizes without uploading a final
|
|
$copied = $this->copyDraftToFinal($draft['teacher_file']);
|
|
if ($copied !== null) {
|
|
$update['final_file'] = $copied;
|
|
$update['final_filename'] = $draft['teacher_filename'] ?? $draft['teacher_file'];
|
|
$pdfName = $this->ensurePdfExists($copied, pathinfo($copied, PATHINFO_EXTENSION));
|
|
if ($pdfName !== null && $this->hasFinalPdfColumn) {
|
|
$update['final_pdf_file'] = $pdfName;
|
|
}
|
|
}
|
|
}
|
|
if (strtolower($status) === 'finalized' && $this->hasIsLegacyColumn) {
|
|
$update['is_legacy'] = 1; // finalized exams move to Previous Exams
|
|
}
|
|
|
|
if ($this->hasIsLegacyColumn) {
|
|
// Keep existing legacy flag, do not set legacy automatically here
|
|
}
|
|
|
|
$updated = $this->examDraftModel
|
|
->set($update)
|
|
->where('id', $draftId)
|
|
->update();
|
|
|
|
if ($updated) {
|
|
return redirect()->back()->with('success', 'Review saved successfully.');
|
|
}
|
|
|
|
return redirect()->back()->with('error', 'Unable to save the review.');
|
|
}
|
|
|
|
private function resolveSelectedClassSection(array $assignments): int
|
|
{
|
|
$candidate = (int) ($this->request->getGet('class_section_id') ?? session()->get('class_section_id') ?? 0);
|
|
$validIds = array_map('intval', array_column($assignments, 'class_section_id'));
|
|
if ($candidate > 0 && in_array($candidate, $validIds, true)) {
|
|
return $candidate;
|
|
}
|
|
return $validIds[0] ?? 0;
|
|
}
|
|
|
|
private function statusBadgeMap(): array
|
|
{
|
|
return [
|
|
'draft' => [
|
|
'label' => 'Draft',
|
|
'class' => 'bg-secondary text-white',
|
|
],
|
|
'submitted' => [
|
|
'label' => 'Submitted',
|
|
'class' => 'bg-warning text-dark',
|
|
],
|
|
'reviewed' => [
|
|
'label' => 'Reviewed',
|
|
'class' => 'bg-info text-dark',
|
|
],
|
|
'finalized' => [
|
|
'label' => 'Finalized',
|
|
'class' => 'bg-success text-white',
|
|
],
|
|
'rejected' => [
|
|
'label' => 'Rejected',
|
|
'class' => 'bg-danger text-white',
|
|
],
|
|
];
|
|
}
|
|
|
|
private function statusOptions(): array
|
|
{
|
|
return ['draft', 'submitted', 'reviewed', 'finalized', 'rejected'];
|
|
}
|
|
|
|
private function normalizeStatus(string $input, string $default): string
|
|
{
|
|
$input = strtolower(trim($input));
|
|
$aliases = [
|
|
'pending' => 'submitted', // legacy value
|
|
'final' => 'finalized',
|
|
'approved' => 'finalized', // legacy value
|
|
];
|
|
if (isset($aliases[$input])) {
|
|
$input = $aliases[$input];
|
|
}
|
|
return in_array($input, $this->statusOptions(), true) ? $input : $default;
|
|
}
|
|
|
|
private function storeUploadedFile(UploadedFile $file, string $subdir, array $allowedExtensions = self::ALLOWED_EXTENSIONS): ?string
|
|
{
|
|
$ext = strtolower($file->getClientExtension());
|
|
if (!in_array($ext, $allowedExtensions, true)) {
|
|
return null;
|
|
}
|
|
if ($file->getSize() > self::MAX_UPLOAD_BYTES) {
|
|
return null;
|
|
}
|
|
|
|
$destination = WRITEPATH . 'uploads/' . trim($subdir, '/');
|
|
if (!is_dir($destination)) {
|
|
mkdir($destination, 0755, true);
|
|
}
|
|
|
|
$filename = $file->getRandomName();
|
|
try {
|
|
$file->move($destination, $filename);
|
|
return $filename;
|
|
} catch (\Throwable $e) {
|
|
log_message('error', 'ExamDraftController::storeUploadedFile error: ' . $e->getMessage());
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private function convertDocToPdf(string $sourcePath, string $targetSubdir): ?string
|
|
{
|
|
// Only attempt conversion for doc/docx
|
|
$ext = strtolower(pathinfo($sourcePath, PATHINFO_EXTENSION));
|
|
if (!in_array($ext, ['doc', 'docx'], true)) {
|
|
return null;
|
|
}
|
|
|
|
$targetDir = WRITEPATH . 'uploads/' . trim($targetSubdir, '/');
|
|
if (!is_dir($targetDir)) {
|
|
mkdir($targetDir, 0755, true);
|
|
}
|
|
|
|
$base = pathinfo($sourcePath, PATHINFO_FILENAME);
|
|
$targetPath = $targetDir . '/' . $base . '.pdf';
|
|
|
|
// Attempt conversion via LibreOffice if available
|
|
$cmd = 'soffice --headless --convert-to pdf --outdir ' . escapeshellarg($targetDir) . ' ' . escapeshellarg($sourcePath) . ' 2>/dev/null';
|
|
@exec($cmd);
|
|
|
|
return is_file($targetPath) ? basename($targetPath) : null;
|
|
}
|
|
|
|
private function schemaHasColumn(string $table, string $column): bool
|
|
{
|
|
try {
|
|
$fields = $this->db->getFieldNames($table);
|
|
return in_array($column, $fields, true);
|
|
} catch (\Throwable $e) {
|
|
log_message('error', "Schema check failed for {$table}.{$column}: " . $e->getMessage());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private function fullUploadPath(string $subdir, string $filename): string
|
|
{
|
|
return WRITEPATH . 'uploads/' . trim($subdir, '/') . '/' . $filename;
|
|
}
|
|
|
|
private function neighborPdfIfExists(?string $filename, string $subdir): ?string
|
|
{
|
|
if (empty($filename)) return null;
|
|
$path = $this->fullUploadPath($subdir, $filename);
|
|
$base = pathinfo($path, PATHINFO_FILENAME);
|
|
$dir = pathinfo($path, PATHINFO_DIRNAME);
|
|
$pdfPath = $dir . '/' . $base . '.pdf';
|
|
return is_file($pdfPath) ? basename($pdfPath) : null;
|
|
}
|
|
|
|
private function ensurePdfExists(string $finalFilename, ?string $originalExt): ?string
|
|
{
|
|
$pdfNeighbor = $this->neighborPdfIfExists($finalFilename, self::FINAL_UPLOAD_DIR);
|
|
if ($pdfNeighbor !== null) {
|
|
return $pdfNeighbor;
|
|
}
|
|
$ext = strtolower((string)$originalExt);
|
|
if ($ext === 'pdf') {
|
|
// final file itself is already pdf
|
|
$path = $this->fullUploadPath(self::FINAL_UPLOAD_DIR, $finalFilename);
|
|
return is_file($path) ? $finalFilename : null;
|
|
}
|
|
return $this->convertDocToPdf(
|
|
$this->fullUploadPath(self::FINAL_UPLOAD_DIR, $finalFilename),
|
|
self::FINAL_UPLOAD_DIR
|
|
);
|
|
}
|
|
|
|
private function copyDraftToFinal(string $draftFilename): ?string
|
|
{
|
|
$source = $this->fullUploadPath(self::TEACHER_UPLOAD_DIR, $draftFilename);
|
|
if (!is_file($source)) {
|
|
return null;
|
|
}
|
|
$destinationDir = WRITEPATH . 'uploads/' . trim(self::FINAL_UPLOAD_DIR, '/');
|
|
if (!is_dir($destinationDir)) {
|
|
mkdir($destinationDir, 0755, true);
|
|
}
|
|
$ext = pathinfo($draftFilename, PATHINFO_EXTENSION);
|
|
$destName = uniqid('final_', true) . '.' . $ext;
|
|
$destPath = $destinationDir . '/' . $destName;
|
|
if (!@copy($source, $destPath)) {
|
|
return null;
|
|
}
|
|
return $destName;
|
|
}
|
|
}
|