- CertificateService: Intervention Image v3 text overlay on template - GenerateCertificateJob: queued generation with retry logic - SendCertificateEmailJob: stub (implemented in Fasa 8) - CertificateTemplateController: upload, config editor, preview, test generate - Admin/CertificateController: list, generate-all, email-all - Public/CertificateController: show with questionnaire gate, download - DejaVuSans fonts bundled under resources/fonts - Views: admin template/certificate management, public certificate download Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
89 lines
3.1 KiB
PHP
89 lines
3.1 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Public;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\Certificate;
|
|
use App\Models\QuestionnaireResponse;
|
|
use Illuminate\Http\RedirectResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Http\Response;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Illuminate\View\View;
|
|
|
|
class CertificateController extends Controller
|
|
{
|
|
public function show(string $cert_token): View|RedirectResponse
|
|
{
|
|
$certificate = Certificate::where('token', $cert_token)
|
|
->with(['participant', 'program', 'template'])
|
|
->firstOrFail();
|
|
|
|
$program = $certificate->program;
|
|
$participant = $certificate->participant;
|
|
|
|
// Check questionnaire gate
|
|
$pq = $program->questionnaire()->first();
|
|
$needsQuestionnaire = $pq && $pq->is_confirmed;
|
|
$hasAnswered = false;
|
|
|
|
if ($needsQuestionnaire) {
|
|
$hasAnswered = QuestionnaireResponse::where('program_id', $program->id)
|
|
->where('participant_id', $participant->id)
|
|
->exists();
|
|
}
|
|
|
|
// Find active QR code for questionnaire redirect
|
|
$qrCode = $program->qrCode;
|
|
|
|
return view('public.certificate.show', compact(
|
|
'certificate', 'program', 'participant', 'pq',
|
|
'needsQuestionnaire', 'hasAnswered', 'qrCode'
|
|
));
|
|
}
|
|
|
|
public function download(string $cert_token): Response|RedirectResponse
|
|
{
|
|
$certificate = Certificate::where('token', $cert_token)
|
|
->with(['participant', 'program'])
|
|
->firstOrFail();
|
|
|
|
if (! $certificate->isGenerated()) {
|
|
return back()->with('error', 'Sijil belum sedia untuk dimuat turun.');
|
|
}
|
|
|
|
if (! $certificate->file_path || ! Storage::disk('local')->exists($certificate->file_path)) {
|
|
return back()->with('error', 'Fail sijil tidak dijumpai. Sila hubungi penganjur.');
|
|
}
|
|
|
|
// Check questionnaire gate
|
|
$program = $certificate->program;
|
|
$pq = $program->questionnaire()->first();
|
|
|
|
if ($pq && $pq->is_confirmed) {
|
|
$hasAnswered = QuestionnaireResponse::where('program_id', $program->id)
|
|
->where('participant_id', $certificate->participant_id)
|
|
->exists();
|
|
|
|
if (! $hasAnswered) {
|
|
$qrCode = $program->qrCode;
|
|
$redirectTo = $qrCode
|
|
? route('public.questionnaire.show', [$qrCode->token, $certificate->participant->uuid])
|
|
: back();
|
|
return redirect($redirectTo)->with('info', 'Sila jawab borang penilaian terlebih dahulu.');
|
|
}
|
|
}
|
|
|
|
$certificate->recordDownload();
|
|
|
|
$content = Storage::disk('local')->get($certificate->file_path);
|
|
$filename = 'Sijil-' . str($certificate->participant->name)->slug() . '.jpg';
|
|
|
|
return response($content, 200, [
|
|
'Content-Type' => 'image/jpeg',
|
|
'Content-Disposition' => 'attachment; filename="' . $filename . '"',
|
|
'Content-Length' => strlen($content),
|
|
]);
|
|
}
|
|
}
|