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:
@@ -63,6 +63,19 @@ $settings = $hasAccess ? saas_fetch_tenant_settings($pdo, $tenantId) : null;
|
||||
$entryMessage = null;
|
||||
$entryError = null;
|
||||
|
||||
// Ergebnis einer vorangegangenen Aktion (Post-Redirect-Get): nach dem Buchen
|
||||
// bzw. Stornieren wird auf index.php zurueckgeleitet, damit ein Browser-Refresh
|
||||
// die Aktion nicht ein zweites Mal ausloest.
|
||||
if (isset($_SESSION['flash_dashboard'])) {
|
||||
$flash = $_SESSION['flash_dashboard'];
|
||||
unset($_SESSION['flash_dashboard']);
|
||||
if (($flash['type'] ?? '') === 'success') {
|
||||
$entryMessage = (string)$flash['text'];
|
||||
} else {
|
||||
$entryError = (string)$flash['text'];
|
||||
}
|
||||
}
|
||||
|
||||
function dashboard_money(int $cents): string
|
||||
{
|
||||
return saas_format_money_cents($cents) . ' €';
|
||||
@@ -203,17 +216,29 @@ function dashboard_render_consumption(array $entries): void
|
||||
}
|
||||
|
||||
if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||
$flash = ['type' => 'error', 'text' => 'Die Aktion konnte nicht ausgeführt werden.'];
|
||||
|
||||
if ($hasAccess && $participant !== null && $settings !== null) {
|
||||
$result = dashboard_record_self_entry($pdo, $participant, $settings, $_POST["anzahlStriche"] ?? null, $saasUser['user_id'] ?? null);
|
||||
if ($result['ok']) {
|
||||
$entryMessage = $result['message'] ?? null;
|
||||
$participant = ledger_fetch_participant_summary($pdo, $tenantId, (int)$participant['participant_id']);
|
||||
$aktion = (string)($_POST['aktion'] ?? 'strich_eintragen');
|
||||
|
||||
if ($aktion === 'strich_stornieren') {
|
||||
$result = ledger_void_own_self_entry($pdo, $tenantId, (int)$participant['participant_id']);
|
||||
$flash = $result['ok']
|
||||
? ['type' => 'success', 'text' => 'Dein zuletzt selbst eingetragener Strich wurde storniert.']
|
||||
: ['type' => 'error', 'text' => $result['error'] ?? 'Der Strich konnte nicht storniert werden.'];
|
||||
} else {
|
||||
$entryError = $result['error'] ?? 'Die Stricheintragung konnte nicht gespeichert werden.';
|
||||
$result = dashboard_record_self_entry($pdo, $participant, $settings, $_POST["anzahlStriche"] ?? null, $saasUser['user_id'] ?? null);
|
||||
$flash = $result['ok']
|
||||
? ['type' => 'success', 'text' => $result['message'] ?? 'Stricheintragung wurde erfolgreich eingetragen.']
|
||||
: ['type' => 'error', 'text' => $result['error'] ?? 'Die Stricheintragung konnte nicht gespeichert werden.'];
|
||||
}
|
||||
} else {
|
||||
$entryError = 'Die Stricheintragung konnte nicht gespeichert werden.';
|
||||
}
|
||||
|
||||
// Post-Redirect-Get: Ergebnis in die Session legen und per GET erneut
|
||||
// anzeigen, damit ein Refresh die Buchung/Stornierung nicht wiederholt.
|
||||
$_SESSION['flash_dashboard'] = $flash;
|
||||
header('Location: index.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$paymentEntries = $participant !== null
|
||||
@@ -274,6 +299,29 @@ if ($hasAccess && $participant !== null && $settings !== null) {
|
||||
Gesamtstriche im aktuellen Jahr: <?php echo number_format((int)$participant['year_marks'], 0, ',', '.'); ?><br>
|
||||
<p>Gesamteinzahlung im aktuellen Jahr: <?php echo saas_html(dashboard_money((int)$participant['year_payments_cents'])); ?></p>
|
||||
|
||||
<?php
|
||||
$aktuellesJahr = (int)date('Y');
|
||||
$monatsdaten = ledger_fetch_monthly_consumption($pdo, $tenantId, (int)$participant['participant_id'], $aktuellesJahr);
|
||||
$monatsnamen = [1 => 'Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'];
|
||||
$aktuellerMonat = (int)date('n');
|
||||
if ((int)$participant['year_marks'] > 0):
|
||||
?>
|
||||
<h2>Monatsübersicht <?php echo $aktuellesJahr; ?></h2>
|
||||
<table>
|
||||
<tr><th style="width:160px">Monat</th><th>Striche</th><th>Verbrauch</th></tr>
|
||||
<?php
|
||||
for ($m = 1; $m <= $aktuellerMonat; $m++):
|
||||
$md = $monatsdaten[$m];
|
||||
?>
|
||||
<tr>
|
||||
<td><?php echo saas_html($monatsnamen[$m]); ?></td>
|
||||
<td><?php echo number_format($md['marks'], 0, ',', '.'); ?></td>
|
||||
<td><?php echo saas_html(dashboard_money($md['cost_cents'])); ?></td>
|
||||
</tr>
|
||||
<?php endfor; ?>
|
||||
</table>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($selfEntryEnabled): ?>
|
||||
<h2>Eintrag in die Strichliste</h2>
|
||||
<b>Hier kannst du einen oder zwei Striche für dich in der Kaffeeliste eintragen. <br>Dafür benötigst du keinen Eintrag auf der Liste durchführen.</b><br>
|
||||
@@ -282,6 +330,7 @@ if ($hasAccess && $participant !== null && $settings !== null) {
|
||||
<li>
|
||||
<form method="post" action="index.php">
|
||||
<button type="submit">Einen Strich eintragen</button>
|
||||
<input type="hidden" name="aktion" value="strich_eintragen">
|
||||
<input type="hidden" name="anzahlStriche" value="1">
|
||||
<?php echo app_csrf_field(); ?>
|
||||
</form>
|
||||
@@ -289,11 +338,20 @@ if ($hasAccess && $participant !== null && $settings !== null) {
|
||||
<li>
|
||||
<form method="post" action="index.php">
|
||||
<button type="submit">Zwei Striche eintragen</button>
|
||||
<input type="hidden" name="aktion" value="strich_eintragen">
|
||||
<input type="hidden" name="anzahlStriche" value="2">
|
||||
<?php echo app_csrf_field(); ?>
|
||||
</form>
|
||||
</li>
|
||||
<li>
|
||||
<form method="post" action="index.php" onsubmit="return confirm('Deinen zuletzt selbst eingetragenen Strich stornieren?');">
|
||||
<button type="submit" class="alt">Letzten Strich stornieren</button>
|
||||
<input type="hidden" name="aktion" value="strich_stornieren">
|
||||
<?php echo app_csrf_field(); ?>
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
<small>Falsch getippt? Mit „Letzten Strich stornieren" nimmst du deinen jüngsten selbst eingetragenen Strich wieder zurück. Vom Kassenwart eingetragene Striche bleiben davon unberührt.</small>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($paypalEnabled): ?>
|
||||
|
||||
Reference in New Issue
Block a user