9a61f55cb1
- Design-System (assets/css/design-system.css) mit 21 Komponenten, iPad-Responsive-Breakpoints, Touch-Ziele 36px, Music-Player, Glossar-Tooltips - Templates (sims/template.html + student/teacher-Dashboard) - Docs: module-interface.md (inkl. 4a Sprachregel, 4b Leichte Sprache, 4c iPad), content-architecture.md, crash-recovery.md, music-registry.md - Admin-Infrastruktur: admin-modules.html + api/admin-modules.php (Titel, Emoji, Bild, Status, Dauer, Alter pro Modul) - Inbox-System: _inbox/README.md + _status.md fuer Atlas + Briefings an Klima, Glossar, Lehrplan, Fluss - Zentrale SFX-Pipeline (scripts/generate-sounds.py) - DALL-E-Bilder: 8 Badges + 5 Glossar-Repraesentationsbilder (Querformat) - Logo + Inter-Font lokal - PHP-APIs: admin, glossar, levels, licenses, progress, waypoints, assignments, profile, tickets - Spielsprache entfernt (admin-modules, admin-levels, schueler) - Landing-Page-Bearbeitungen (Boote sichtbarer, Button-Hintergrund) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
213 lines
11 KiB
PHP
213 lines
11 KiB
PHP
<?php
|
|
/**
|
|
* GeoGraSim — E-Mail-Versand via PHPMailer
|
|
* Alle E-Mails im einheitlichen Design (Petrol #1f4e5a, Türkis #4a7c8a, Orange #e8833a)
|
|
*/
|
|
|
|
class Mailer {
|
|
|
|
private static function create(): \PHPMailer\PHPMailer\PHPMailer {
|
|
$mail = new \PHPMailer\PHPMailer\PHPMailer(true);
|
|
$mail->isSMTP();
|
|
$mail->Host = defined('SMTP_HOST') ? SMTP_HOST : 'smtp.world4you.com';
|
|
$mail->SMTPAuth = true;
|
|
$mail->Username = defined('SMTP_USER') ? SMTP_USER : 'support@geograsim.at';
|
|
$mail->Password = defined('SMTP_PASS') ? SMTP_PASS : 'geo8Zeichen!';
|
|
$mail->SMTPSecure = \PHPMailer\PHPMailer\PHPMailer::ENCRYPTION_STARTTLS;
|
|
$mail->Port = defined('SMTP_PORT') ? (int)SMTP_PORT : 587;
|
|
$mail->CharSet = 'UTF-8';
|
|
$mail->setFrom(
|
|
defined('SMTP_FROM') ? SMTP_FROM : 'support@geograsim.at',
|
|
defined('SMTP_FROM_NAME') ? SMTP_FROM_NAME : 'GeoGraSim'
|
|
);
|
|
return $mail;
|
|
}
|
|
|
|
public static function send(string $to, string $subject, string $htmlBody, string $textBody = ''): bool {
|
|
try {
|
|
$mail = self::create();
|
|
$mail->addAddress($to);
|
|
$mail->isHTML(true);
|
|
$mail->Subject = $subject;
|
|
$mail->Body = self::wrapHtml($htmlBody);
|
|
$mail->AltBody = $textBody ?: strip_tags(str_replace(['<br>','<br/>','</p>','</li>'], "\n", $htmlBody));
|
|
$mail->send();
|
|
return true;
|
|
} catch (\Exception $e) {
|
|
error_log('Mailer error: ' . $e->getMessage());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// ─── Willkommen (Lehrperson registriert sich) ───
|
|
|
|
public static function sendWelcomeTeacher(string $email, string $name): bool {
|
|
$n = htmlspecialchars($name);
|
|
$html = self::greeting($n)
|
|
. self::text('Willkommen bei <strong>GeoGraSim</strong> — interaktive Simulationen für den Geografie-Unterricht!')
|
|
. self::text('Ihr Konto wurde erfolgreich erstellt. Sie können jetzt:')
|
|
. self::checklist([
|
|
'Klassen anlegen und Schüler*innen-Zugänge generieren',
|
|
'Module freischalten und den Unterricht steuern',
|
|
'Ergebnisse und Fortschritte im Dashboard verfolgen',
|
|
])
|
|
. self::button('Zum Dashboard', 'https://geograsim.at/teacher.html')
|
|
. self::hint('Im <a href="https://geograsim.at/handbuch.html" style="color:#4a7c8a">Didaktischen Handbuch</a> finden Sie zu jedem Modul einen fertigen 45-Minuten-Unterrichtsentwurf.');
|
|
return self::send($email, 'Willkommen bei GeoGraSim', $html);
|
|
}
|
|
|
|
// ─── Zugangsdaten (Schüler*in wurde erstellt) ───
|
|
|
|
public static function sendStudentCredentials(string $email, string $studentName, string $username, string $password, string $classCode, string $className): bool {
|
|
$n = htmlspecialchars($studentName);
|
|
$html = self::greeting($n)
|
|
. self::text('Du hast einen Zugang zu <strong>GeoGraSim</strong> erhalten — interaktive Simulationen für den Geografie-Unterricht.')
|
|
. self::credentialBox($username, $password, $classCode, $className)
|
|
. self::button('Jetzt anmelden', 'https://geograsim.at/login.html')
|
|
. self::hint('Bewahre diese Zugangsdaten gut auf. Bei Problemen wende dich an deine Lehrperson.');
|
|
return self::send($email, 'Dein GeoGraSim-Zugang', $html);
|
|
}
|
|
|
|
// ─── Passwort-Reset ───
|
|
|
|
public static function sendPasswordReset(string $email, string $name, string $token): bool {
|
|
$link = 'https://geograsim.at/login.html?reset=' . urlencode($token);
|
|
$n = htmlspecialchars($name);
|
|
$html = self::greeting($n)
|
|
. self::text('Sie haben ein neues Passwort für Ihr GeoGraSim-Konto angefordert.')
|
|
. self::text('Klicken Sie auf den Button, um ein neues Passwort zu wählen:')
|
|
. self::button('Neues Passwort wählen', $link)
|
|
. self::hint('Dieser Link ist <strong>1 Stunde</strong> gültig und kann nur einmal verwendet werden.')
|
|
. self::hint('Falls Sie kein neues Passwort angefordert haben, können Sie diese E-Mail ignorieren. Ihr bestehendes Passwort bleibt unverändert.')
|
|
. self::hint('Bei Fragen wenden Sie sich an <a href="mailto:support@geograsim.at" style="color:#4a7c8a">support@geograsim.at</a>.');
|
|
return self::send($email, 'GeoGraSim — Passwort zurücksetzen', $html);
|
|
}
|
|
|
|
// ─── Admin 2FA PIN ───
|
|
|
|
public static function sendAdminPin(string $email, string $pin): bool {
|
|
$html = self::greeting('Admin')
|
|
. self::text('Ihr Anmelde-PIN für die GeoGraSim-Administration:')
|
|
. self::pinBox($pin)
|
|
. self::hint('Dieser PIN ist <strong>10 Minuten</strong> gültig.');
|
|
return self::send($email, 'GeoGraSim Admin — Anmelde-PIN', $html);
|
|
}
|
|
|
|
// ─── Batch-Zugangsdaten (Lehrperson bekommt Übersicht) ───
|
|
|
|
public static function sendBatchCredentials(string $teacherEmail, string $teacherName, string $className, array $students): bool {
|
|
$n = htmlspecialchars($teacherName);
|
|
$html = self::greeting($n)
|
|
. self::text('Die folgenden Zugangsdaten wurden für Klasse <strong>' . htmlspecialchars($className) . '</strong> erstellt:')
|
|
. self::studentTable($students, $className)
|
|
. self::button('Zum Dashboard', 'https://geograsim.at/teacher.html')
|
|
. self::hint('Sie können diese Zugangsdaten auch im Dashboard unter „Tickets drucken" als PDF herunterladen.');
|
|
return self::send($teacherEmail, 'GeoGraSim — Zugangsdaten für ' . $className, $html);
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════
|
|
// Bausteine
|
|
// ═══════════════════════════════════════════════════════
|
|
|
|
private static function greeting(string $name): string {
|
|
return '<p style="font-size:16px;color:#1f4e5a;margin:0 0 12px">Hallo ' . $name . ',</p>';
|
|
}
|
|
|
|
private static function text(string $t): string {
|
|
return '<p style="font-size:14px;color:#333;line-height:1.6;margin:0 0 12px">' . $t . '</p>';
|
|
}
|
|
|
|
private static function button(string $label, string $url): string {
|
|
return '<p style="text-align:center;margin:20px 0">'
|
|
. '<a href="' . $url . '" style="display:inline-block;padding:12px 28px;background:#4a7c8a;color:#fff;text-decoration:none;border-radius:8px;font-weight:700;font-size:14px">'
|
|
. $label . '</a></p>';
|
|
}
|
|
|
|
private static function hint(string $t): string {
|
|
return '<p style="font-size:12px;color:#8a8a8a;line-height:1.5;margin:16px 0 0">' . $t . '</p>';
|
|
}
|
|
|
|
private static function checklist(array $items): string {
|
|
$html = '<table cellpadding="0" cellspacing="0" border="0" style="margin:8px 0 12px">';
|
|
foreach ($items as $item) {
|
|
$html .= '<tr><td style="padding:3px 8px 3px 0;font-size:14px;color:#5a8a5e;vertical-align:top">✓</td>'
|
|
. '<td style="padding:3px 0;font-size:14px;color:#333">' . $item . '</td></tr>';
|
|
}
|
|
return $html . '</table>';
|
|
}
|
|
|
|
private static function credentialBox(string $username, string $password, string $classCode, string $className): string {
|
|
return '<div style="background:#f7f6f3;border:1.5px solid #dae8ec;border-radius:10px;padding:16px;margin:16px 0">'
|
|
. '<table cellpadding="0" cellspacing="0" border="0" style="width:100%">'
|
|
. self::credRow('Klassencode', $classCode, '#e8833a')
|
|
. self::credRow('Klasse', htmlspecialchars($className), '#333')
|
|
. self::credRow('Benutzername', $username, '#1f4e5a')
|
|
. self::credRow('Passwort', $password, '#1f4e5a')
|
|
. '</table>'
|
|
. '<p style="font-size:11px;color:#8a8a8a;margin:10px 0 0;text-align:center">geograsim.at</p>'
|
|
. '</div>';
|
|
}
|
|
|
|
private static function credRow(string $label, string $value, string $color): string {
|
|
return '<tr><td style="padding:4px 0;font-size:12px;color:#8a8a8a;width:110px">' . $label . '</td>'
|
|
. '<td style="padding:4px 0;font-size:15px;font-weight:700;font-family:\'Courier New\',monospace;color:' . $color . ';letter-spacing:.04em">' . htmlspecialchars($value) . '</td></tr>';
|
|
}
|
|
|
|
private static function pinBox(string $pin): string {
|
|
return '<div style="text-align:center;margin:20px 0">'
|
|
. '<div style="display:inline-block;padding:16px 32px;background:#1f4e5a;border-radius:12px;font-size:32px;font-weight:900;letter-spacing:.2em;color:#fff;font-family:\'Courier New\',monospace">'
|
|
. htmlspecialchars($pin)
|
|
. '</div></div>';
|
|
}
|
|
|
|
private static function studentTable(array $students, string $className): string {
|
|
$html = '<table cellpadding="0" cellspacing="0" border="0" style="width:100%;margin:12px 0;border-collapse:collapse">';
|
|
$html .= '<tr><th style="text-align:left;padding:6px 8px;font-size:11px;color:#8a8a8a;border-bottom:2px solid #dae8ec">Benutzer</th>'
|
|
. '<th style="text-align:left;padding:6px 8px;font-size:11px;color:#8a8a8a;border-bottom:2px solid #dae8ec">Passwort</th></tr>';
|
|
foreach ($students as $s) {
|
|
$html .= '<tr><td style="padding:4px 8px;font-size:13px;font-weight:600;border-bottom:1px solid #f0eeea">' . htmlspecialchars($s['username']) . '</td>'
|
|
. '<td style="padding:4px 8px;font-size:13px;font-family:\'Courier New\',monospace;color:#1f4e5a;border-bottom:1px solid #f0eeea">' . htmlspecialchars($s['password']) . '</td></tr>';
|
|
}
|
|
return $html . '</table>';
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════
|
|
// HTML-Wrapper (Briefpapier)
|
|
// ═══════════════════════════════════════════════════════
|
|
|
|
private static function wrapHtml(string $content): string {
|
|
return <<<HTML
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width"></head>
|
|
<body style="margin:0;padding:0;background:#f7f6f3;font-family:'Inter','Segoe UI',system-ui,sans-serif">
|
|
<div style="max-width:540px;margin:0 auto;padding:24px 16px">
|
|
|
|
<!-- Header -->
|
|
<div style="text-align:center;padding:20px 0 16px">
|
|
<img src="https://geograsim.at/assets/img/geograsim_t.svg" alt="GeoGraSim" style="height:38px">
|
|
</div>
|
|
|
|
<!-- Card -->
|
|
<div style="background:#fff;border-radius:12px;padding:28px 24px;box-shadow:0 2px 12px rgba(0,0,0,.06);border:1px solid rgba(0,0,0,.04)">
|
|
{$content}
|
|
</div>
|
|
|
|
<!-- Footer -->
|
|
<div style="text-align:center;padding:20px 0 8px">
|
|
<p style="font-size:11px;color:#aaa;margin:0">GeoGraSim — Interaktive Simulationen für den GW-Unterricht</p>
|
|
<p style="font-size:11px;margin:4px 0 0"><a href="https://geograsim.at" style="color:#4a7c8a;text-decoration:none">geograsim.at</a></p>
|
|
</div>
|
|
|
|
<!-- Landscape decoration -->
|
|
<div style="text-align:center;opacity:.08;margin-top:4px">
|
|
<span style="font-size:10px;letter-spacing:2px;color:#1f4e5a">🏔️ 🌲 🌲 🏙️ 🌊 ⛵ 🌊</span>
|
|
</div>
|
|
|
|
</div>
|
|
</body>
|
|
</html>
|
|
HTML;
|
|
}
|
|
}
|