Files
geograsim/App/php/lib/Mailer.php
T
Adminator 9a61f55cb1 Atlas: Infrastruktur + Team-Konventionen + Sprachregel Lernarbeit
- 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>
2026-04-19 11:51:14 +02:00

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;
}
}