Files
kaffeekasse-saas/mailversenden.php
T
clemens d5195e7f9f M6: Mailversand als nachvollziehbaren Versandjob mit Dry-Run/Log
mailversenden.php hatte keine Zugriffskontrolle, versendete bei jedem
GET-Request sofort echte Mails und nutzte PHPMailer, dessen Quelldateien
im Repo gar nicht vorhanden waren (nur composer.json/Lizenz) - der Aufruf
waere also ohnehin mit Fatal Error abgebrochen. Zusaetzlich waren SMTP-
Host, Absender, PayPal-Link und FAQ-URL fest auf einen Alt-Kunden (AOK)
codiert.

- Ersetzt PHPMailer durch die bestehende saas_send_mail()-Abstraktion aus
  M3 (Transport log/mail je nach APP_MAIL_TRANSPORT) statt eine fehlende
  Abhaengigkeit nachzuvendoren.
- Neue Tabelle outbound_emails protokolliert jeden Versandversuch:
  Mandant, Mitglied, Vorlage, Betreff, Status, Fehler - das Versandlog.
- Formular hat eine standardmaessig aktive Dry-Run-Checkbox; im Dry-Run
  wird nur geloggt, saas_send_mail() nicht aufgerufen.
- Mailtext ist jetzt tenant-generisch (Saldo, optionaler PayPal-Link nur
  wenn der Mandant PayPal aktiviert hat, eigener Dashboard-Link) statt
  hartcodierter Alt-Kunden-Inhalte.
- Zugriffskontrolle ergaenzt (owner/admin/treasurer + Legacy-Fallback).
- http-smoke.php: mailversenden.php ist jetzt reguel</EOF>
2026-07-15 16:13:38 +02:00

154 lines
4.6 KiB
PHP

<?php
include "functions.php";
require_once __DIR__ . "/app/ledger.php";
require_once __DIR__ . "/app/saas-mail.php";
app_require_csrf();
include "header.php";
include "headerline.php";
include "nav.php";
?>
<!-- Banner -->
<section id="banner">
<div class="content">
<?php
$pdo = app_db_pdo();
$saasUser = saas_current_user($pdo);
$tenantId = 0;
$hasAccess = false;
if ($saasUser !== null && saas_user_has_role(['owner', 'admin', 'treasurer'], $saasUser)) {
$tenantId = (int)$saasUser['tenant_id'];
$hasAccess = true;
}
if (!$hasAccess && $saasUser === null && checkKaffeelisteAdmin($conn, $mailadress)) {
$tenant = ledger_fetch_default_tenant($pdo);
if ($tenant !== null) {
$tenantId = (int)$tenant['id'];
$hasAccess = true;
}
}
if (!$hasAccess) {
echo "<h2>Kein Zugriff</h2>";
include "footer.php";
exit;
}
$ergebnisse = null;
$dryRun = true;
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$dryRun = !empty($_POST['dry_run']);
$settings = saas_fetch_tenant_settings($pdo, $tenantId);
$dashboardUrl = saas_app_url('index.php');
$createdByUserId = $saasUser['user_id'] ?? null;
$teilnehmer = ledger_fetch_participant_summaries($pdo, $tenantId, ['active_only' => true]);
$ergebnisse = [];
foreach ($teilnehmer as $person) {
$email = trim((string)($person['email'] ?? ''));
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$ergebnisse[] = ['name' => $person['display_name'], 'email' => $email, 'status' => 'ohne_email'];
continue;
}
$subject = 'Kaffeeliste - Dein aktueller Stand';
$body = saas_render_balance_mail_body(
$person['display_name'],
(int)$person['balance_cents'],
$settings,
$dashboardUrl
);
if ($dryRun) {
saas_log_outbound_email($pdo, $tenantId, $person['participant_id'], 'balance_notice', $subject, 'dry_run', null, $createdByUserId);
$ergebnisse[] = ['name' => $person['display_name'], 'email' => $email, 'status' => 'dry_run'];
continue;
}
$sendResult = saas_send_mail($email, $subject, $body);
if ($sendResult['ok']) {
saas_log_outbound_email($pdo, $tenantId, $person['participant_id'], 'balance_notice', $subject, 'sent', null, $createdByUserId);
$ergebnisse[] = ['name' => $person['display_name'], 'email' => $email, 'status' => 'sent'];
} else {
saas_log_outbound_email($pdo, $tenantId, $person['participant_id'], 'balance_notice', $subject, 'failed', $sendResult['error'] ?? 'unbekannter Fehler', $createdByUserId);
$ergebnisse[] = ['name' => $person['display_name'], 'email' => $email, 'status' => 'failed'];
}
}
}
$log = saas_fetch_outbound_email_log($pdo, $tenantId, 20);
function mailversenden_status_label(string $status): string
{
return match ($status) {
'sent' => 'Versendet',
'failed' => 'Fehlgeschlagen',
'dry_run' => 'Dry-Run (nicht versendet)',
'ohne_email' => 'Keine E-Mail-Adresse',
default => $status,
};
}
?>
<h2>Info-Mail versenden</h2>
<p>Verschickt an alle aktiven Mitglieder eine E-Mail mit dem aktuellen
Kaffeelisten-Stand. Im Dry-Run wird nichts wirklich versendet, aber jeder
Empfänger wird im Versandlog unten protokolliert.</p>
<?php if ($ergebnisse !== null): ?>
<h3>Ergebnis dieses Laufs (<?php echo $dryRun ? 'Dry-Run' : 'Live-Versand'; ?>)</h3>
<table>
<tr><th>Name</th><th>E-Mail</th><th>Status</th></tr>
<?php foreach ($ergebnisse as $eintrag): ?>
<tr>
<td><?php echo saas_html($eintrag['name']); ?></td>
<td><?php echo saas_html($eintrag['email']); ?></td>
<td><?php echo saas_html(mailversenden_status_label($eintrag['status'])); ?></td>
</tr>
<?php endforeach; ?>
</table>
<?php endif; ?>
<form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>">
<?php echo app_csrf_field(); ?>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="dry_run" id="dry_run" checked>
<label class="form-check-label" for="dry_run">Dry-Run (nichts wirklich versenden, nur protokollieren)</label>
</div>
<button type="submit">Info-Mail versenden</button>
</form>
<h3>Versandlog (letzte 20)</h3>
<table>
<tr><th>Datum</th><th>Mitglied</th><th>Betreff</th><th>Status</th><th>Fehler</th></tr>
<?php if ($log === []): ?>
<tr><td colspan="5">Noch kein Versand protokolliert.</td></tr>
<?php endif; ?>
<?php foreach ($log as $eintrag): ?>
<tr>
<td><?php echo saas_html($eintrag['created_at']); ?></td>
<td><?php echo saas_html($eintrag['display_name'] ?? '—'); ?></td>
<td><?php echo saas_html($eintrag['subject']); ?></td>
<td><?php echo saas_html(mailversenden_status_label($eintrag['status'])); ?></td>
<td><?php echo saas_html($eintrag['error'] ?? ''); ?></td>
</tr>
<?php endforeach; ?>
</table>
</div>
</section>
<?php include "footer.php"; ?>