where('user_id', $learner->getKey())->where('course_version_id', $version->getKey())->first(); if ($existing) { return $existing->id; } $profile = DB::table('organization_profiles')->where('organization_id', $learner->organization_id)->first(); $template = $templateId ? DB::table('certificate_templates')->where('organization_id', $learner->organization_id)->where('id', $templateId)->first() : DB::table('certificate_templates')->where('organization_id', $learner->organization_id)->where('is_default', true)->where('is_active', true)->first(); $id = (string) Str::ulid(); $code = Str::lower(Str::random(32)); $number = 'ML-'.now()->format('Y').'-'.strtoupper(substr($id, -8)); $url = rtrim((string) config('app.frontend_url'), '/').'/certificate/verify/'.$code; $snapshot = ['schemaVersion' => 1, 'learnerName' => $learner->name, 'courseTitle' => $version->title, 'organizationName' => $learner->organization?->name, 'signatory' => $profile?->certificate_signatory, 'primaryColor' => $profile?->primary_color ?? '#5b3fd3', 'accentColor' => $profile?->accent_color ?? '#09bdd1', 'verificationUrl' => $url, 'template' => $template ? json_decode($template->canvas, true) : null]; $qr = (new SvgWriter)->write(new QrCode(data: $url))->getString(); $html = $this->html($snapshot, $number, now()->toDateString(), $qr); $dompdf = new Dompdf(['isRemoteEnabled' => false]); $dompdf->loadHtml($html, 'UTF-8'); $dompdf->setPaper('A4', 'landscape'); $dompdf->render(); $disk = (string) config('exports.disk', 'local'); $path = 'certificates/'.$learner->organization_id.'/'.$id.'.pdf'; Storage::disk($disk)->put($path, $dompdf->output()); DB::transaction(function () use ($id, $learner, $version, $template, $code, $number, $expiresInMonths, $snapshot, $disk, $path): void { DB::table('certificates')->insert(['id' => $id, 'organization_id' => $learner->organization_id, 'user_id' => $learner->getKey(), 'course_version_id' => $version->getKey(), 'template_id' => $template?->id, 'serial' => (string) Str::uuid(), 'verification_code' => $code, 'certificate_number' => $number, 'issued_at' => now(), 'expires_at' => $expiresInMonths ? now()->addMonths($expiresInMonths) : null, 'snapshot' => json_encode($snapshot), 'disk' => $disk, 'path' => $path, 'created_at' => now(), 'updated_at' => now()]); DB::table('in_app_notifications')->insert(['id' => (string) Str::ulid(), 'organization_id' => $learner->organization_id, 'recipient_id' => $learner->getKey(), 'actor_id' => null, 'type' => 'certificate.issued', 'title' => 'گواهی‌نامه جدید صادر شد', 'body' => 'گواهی‌نامه دوره «'.$version->title.'» آماده دریافت است.', 'target_url' => '/learn/progress', 'entity_type' => 'certificate', 'entity_id' => $id, 'data' => json_encode(['certificateNumber' => $number]), 'created_at' => now(), 'updated_at' => now()]); }); return $id; } /** @param array $snapshot */ private function html(array $snapshot, string $number, string $issued, string $qr): string { $color = htmlspecialchars((string) $snapshot['primaryColor']); $safe = fn (mixed $value) => htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8'); $qrData = 'data:image/svg+xml;base64,'.base64_encode($qr); return '
MicroLearn · '.$safe($snapshot['organizationName'] ?? '').'

گواهی‌نامه پایان دوره

گواهی می‌شود

'.$safe($snapshot['learnerName']).'

دوره

'.$safe($snapshot['courseTitle']).'
شماره: '.$safe($number).'
تاریخ صدور: '.$safe($issued).'
امضاکننده: '.$safe($snapshot['signatory'] ?? 'مدیریت آموزش').'
'; } }