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:
2026-07-20 21:54:16 +02:00
parent c4cb12b26e
commit ef5d0e1822
23 changed files with 918 additions and 160 deletions
+20 -4
View File
@@ -278,7 +278,10 @@ function saas_fetch_tenant_settings(PDO $pdo, int $tenantId): ?array
ts.pdf_split_mode,
ts.pdf_row_height_px,
ts.pdf_watermark_text,
ts.pdf_footer_text
ts.pdf_footer_text,
ts.payment_reminder_enabled,
ts.payment_reminder_interval_days,
ts.pdf_watermark_logo
FROM tenants t
LEFT JOIN tenant_settings ts ON ts.tenant_id = t.id
WHERE t.id = ?
@@ -316,6 +319,9 @@ function saas_fetch_tenant_settings(PDO $pdo, int $tenantId): ?array
'pdf_row_height_px' => (int)$settings['pdf_row_height_px'],
'pdf_watermark_text' => (string)$settings['pdf_watermark_text'],
'pdf_footer_text' => (string)$settings['pdf_footer_text'],
'payment_reminder_enabled' => (int)$settings['payment_reminder_enabled'],
'payment_reminder_interval_days' => (int)$settings['payment_reminder_interval_days'],
'pdf_watermark_logo' => (string)$settings['pdf_watermark_logo'],
];
}
@@ -333,6 +339,7 @@ function saas_update_tenant_settings(PDO $pdo, int $tenantId, array $input): arr
$pdfRowHeightPx = filter_var($input['pdf_row_height_px'] ?? null, FILTER_VALIDATE_INT);
$pdfWatermarkText = trim((string)($input['pdf_watermark_text'] ?? ''));
$pdfFooterText = trim((string)($input['pdf_footer_text'] ?? ''));
$paymentReminderIntervalDays = filter_var($input['payment_reminder_interval_days'] ?? null, FILTER_VALIDATE_INT);
$errors = [];
if (strlen($tenantName) < 3 || strlen($tenantName) > 255) {
@@ -371,6 +378,9 @@ function saas_update_tenant_settings(PDO $pdo, int $tenantId, array $input): arr
if ($pdfRowHeightPx === false || $pdfRowHeightPx < 10 || $pdfRowHeightPx > 60) {
$errors[] = 'Die Zeilenhöhe im Ausdruck muss zwischen 10 und 60 Pixel liegen.';
}
if ($paymentReminderIntervalDays === false || $paymentReminderIntervalDays < 1 || $paymentReminderIntervalDays > 90) {
$errors[] = 'Das Erinnerungs-Intervall muss zwischen 1 und 90 Tagen liegen.';
}
if ($errors !== []) {
return ['ok' => false, 'errors' => $errors];
@@ -379,6 +389,7 @@ function saas_update_tenant_settings(PDO $pdo, int $tenantId, array $input): arr
$selfEntryEnabled = !empty($input['self_entry_enabled']) ? 1 : 0;
$paypalEnabled = !empty($input['paypal_enabled']) ? 1 : 0;
$pdfShowEmptyRows = !empty($input['pdf_show_empty_rows']) ? 1 : 0;
$paymentReminderEnabled = !empty($input['payment_reminder_enabled']) ? 1 : 0;
try {
$pdo->beginTransaction();
@@ -395,8 +406,9 @@ function saas_update_tenant_settings(PDO $pdo, int $tenantId, array $input): arr
(tenant_id, mark_price_cents, self_entry_enabled, paypal_enabled,
paypal_url_template, sheet_window_days, negative_warning_cents,
pdf_show_empty_rows, pdf_split_mode, pdf_row_height_px,
pdf_watermark_text, pdf_footer_text)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
pdf_watermark_text, pdf_footer_text,
payment_reminder_enabled, payment_reminder_interval_days)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
mark_price_cents = VALUES(mark_price_cents),
self_entry_enabled = VALUES(self_entry_enabled),
@@ -408,7 +420,9 @@ function saas_update_tenant_settings(PDO $pdo, int $tenantId, array $input): arr
pdf_split_mode = VALUES(pdf_split_mode),
pdf_row_height_px = VALUES(pdf_row_height_px),
pdf_watermark_text = VALUES(pdf_watermark_text),
pdf_footer_text = VALUES(pdf_footer_text)'
pdf_footer_text = VALUES(pdf_footer_text),
payment_reminder_enabled = VALUES(payment_reminder_enabled),
payment_reminder_interval_days = VALUES(payment_reminder_interval_days)'
);
$stmt->execute([
$tenantId,
@@ -423,6 +437,8 @@ function saas_update_tenant_settings(PDO $pdo, int $tenantId, array $input): arr
(int)$pdfRowHeightPx,
$pdfWatermarkText,
$pdfFooterText,
$paymentReminderEnabled,
(int)$paymentReminderIntervalDays,
]);
$pdo->commit();