Sicherheits-/Korrektheits-Fixes und neue Funktionen
Bugfixes aus dem Code-Review: - XSS: unescaptes $_SERVER['PHP_SELF'] in csvupload.php und letzteneintraege.php durch feste Seitennamen ersetzt. - Stripe-Webhook: Event-Deduplizierung (neue Tabelle stripe_webhook_events) gegen doppelte Verarbeitung/Dolibarr-Rechnungen bei Retry-Zustellung. - Stripe-Webhook: Tarifwechsel aus dem Kundenportal wird lokal nachgezogen (plan_code aus dem Preis-lookup_key bei subscription.updated). - Post-Redirect-Get fuer jahresauswertung, mailversenden und die Selbst-Stricheintragung - verhindert Doppelbuchung/Doppelversand per Browser-Refresh. - Jahresbonus: Mails erst nach erfolgreichem Commit; Restcent-Ausgleich beim letzten Empfaenger, damit die Summe exakt stimmt. - Verschachtelte HTML-Dokumente in csvupload/einzahlung/stricheintragen entfernt (Layout kommt aus header.php). - Rate-Limit fuer den Versand von E-Mail-Verifizierungslinks. Neue Funktionen: - Mitglieder koennen ihren zuletzt selbst eingetragenen Strich wieder stornieren (nur eigene Web-Eintraege, Kassenwart-Eintraege bleiben). - Monatsuebersicht des eigenen Verbrauchs im Mitglieder-Dashboard. - Automatische Zahlungserinnerung: opt-in pro Mandant ab der Warnschwelle, mit Intervall; Cron-Skript scripts/send-payment-reminders.php. - CSV-Import fuer Mitgliederlisten inkl. herunterladbarer Vorlage (mitglieder-vorlage.php), Semikolon-/Komma- und BOM-Erkennung. - Logo als PDF-Wasserzeichen pro Mandant (Upload in den Mandant- Einstellungen, geschuetzt unter var/tenant_logos/, ersetzt den Text-Wasserzeichen im Ausdruck). Robusterer Teilnehmer-Lookup im Dashboard ueber user_id (Fallback E-Mail).
This commit is contained in:
+41
-23
@@ -5,20 +5,6 @@ require_once __DIR__ . "/app/ledger.php";
|
||||
require_once __DIR__ . "/app/saas-mail.php";
|
||||
require_once __DIR__ . "/app/audit.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);
|
||||
@@ -38,16 +24,24 @@ if (!$hasAccess && $saasUser === null && checkKaffeelisteAdmin($conn, $mailadres
|
||||
}
|
||||
}
|
||||
|
||||
if (!$hasAccess) {
|
||||
echo "<h2>Kein Zugriff</h2>";
|
||||
include "footer.php";
|
||||
exit;
|
||||
}
|
||||
|
||||
$ergebnisse = null;
|
||||
$dryRun = true;
|
||||
$liveFlash = false;
|
||||
|
||||
if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||
// Ergebnis eines vorangegangenen Live-Versands (Post-Redirect-Get): nach dem
|
||||
// echten Versand wird auf diese Seite zurueckgeleitet, damit ein Refresh nicht
|
||||
// erneut an alle Mitglieder mailt.
|
||||
if ($hasAccess && isset($_SESSION['flash_mailversand'])) {
|
||||
$flash = $_SESSION['flash_mailversand'];
|
||||
unset($_SESSION['flash_mailversand']);
|
||||
if ((int)($flash['tenant_id'] ?? 0) === $tenantId) {
|
||||
$ergebnisse = $flash['ergebnisse'] ?? null;
|
||||
$dryRun = false;
|
||||
$liveFlash = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($hasAccess && $_SERVER["REQUEST_METHOD"] === "POST") {
|
||||
$dryRun = !empty($_POST['dry_run']);
|
||||
$settings = saas_fetch_tenant_settings($pdo, $tenantId);
|
||||
$dashboardUrl = saas_app_url('index.php');
|
||||
@@ -89,10 +83,16 @@ if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||
|
||||
if (!$dryRun) {
|
||||
app_audit_log($pdo, $tenantId, $createdByUserId, 'mail_broadcast.sent', 'tenant', $tenantId, ['empfaenger' => count($ergebnisse)]);
|
||||
|
||||
// Post-Redirect-Get: Ergebnis in die Session legen und per GET erneut
|
||||
// anzeigen, damit ein Browser-Refresh nicht erneut versendet.
|
||||
$_SESSION['flash_mailversand'] = ['tenant_id' => $tenantId, 'ergebnisse' => $ergebnisse];
|
||||
header('Location: mailversenden.php');
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
$log = saas_fetch_outbound_email_log($pdo, $tenantId, 20);
|
||||
$log = $hasAccess ? saas_fetch_outbound_email_log($pdo, $tenantId, 20) : [];
|
||||
|
||||
function mailversenden_status_label(string $status): string
|
||||
{
|
||||
@@ -105,13 +105,29 @@ function mailversenden_status_label(string $status): string
|
||||
};
|
||||
}
|
||||
|
||||
include "header.php";
|
||||
include "headerline.php";
|
||||
include "nav.php";
|
||||
?>
|
||||
|
||||
|
||||
<!-- Banner -->
|
||||
<section id="banner">
|
||||
<div class="content">
|
||||
|
||||
<?php if (!$hasAccess): ?>
|
||||
<h2>Kein Zugriff</h2>
|
||||
<?php else: ?>
|
||||
|
||||
<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 ($liveFlash): ?>
|
||||
<div class="hint-box success"><p>Die Info-Mails wurden versendet.</p></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($ergebnisse !== null): ?>
|
||||
<h3>Ergebnis dieses Laufs (<?php echo $dryRun ? 'Dry-Run' : 'Live-Versand'; ?>)</h3>
|
||||
<table>
|
||||
@@ -126,7 +142,7 @@ Empfänger wird im Versandlog unten protokolliert.</p>
|
||||
</table>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>">
|
||||
<form method="post" action="mailversenden.php">
|
||||
<?php echo app_csrf_field(); ?>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="dry_run" id="dry_run" checked>
|
||||
@@ -152,6 +168,8 @@ Empfänger wird im Versandlog unten protokolliert.</p>
|
||||
<?php endforeach; ?>
|
||||
</table>
|
||||
|
||||
<?php endif; ?>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user