# M2 Technisches Fundament

Stand: 2026-07-13

M2 führt die technischen Grundbausteine für den SaaS-Umbau ein, ohne das
Legacy-Verhalten oder das bestehende Design zu verändern.

Status: abgeschlossen für den M2-Scope. Bewusst ausgelagerte Themen sind unten
als Grenzen und M3-/M6-Übergabe dokumentiert.

## Ziel

- Versionierte Datenbankmigrationen statt loser Schema-Ausführung.
- Zentrales Bootstrap für Umgebung, Session und CSRF-Helper.
- Bestehende Legacy-Seiten bleiben kompatibel.
- Layout-Trennung wird vorbereitet, aber noch nicht in bestehende Seiten
  hineingezogen.

## Umgesetzte Bausteine

### Bootstrap

- `app/bootstrap.php` definiert zentrale Helfer:
  - `app_env()`
  - `app_is_dev()`
  - `app_start_session()`
  - `app_csrf_token()`
  - `app_csrf_field()`
  - `app_verify_csrf()`
  - `app_require_csrf()`
- `config.php` lädt den Bootstrap und startet die Session mit sicheren
  Cookie-Optionen.
- Sessions werden standardmäßig unter `var/sessions` abgelegt, weil die
  lokale PHP-Umgebung keinen beschreibbaren Systempfad garantiert. `var/` ist
  von Git ignoriert. Der Pfad kann über `APP_SESSION_PATH` überschrieben
  werden.
- CSRF ist bewusst noch nicht global erzwungen. Die vorhandenen POST-Seiten
  werden später einzeln umgestellt, damit keine Formulare oder Spezialflows
  brechen.

### CSRF-Rollout

Erste Legacy-POST-Seiten sind opt-in abgesichert:

- `hinweise.php`: Hinweis anlegen und Hinweis löschen. Der bisherige
  GET-Löschlink wurde durch ein POST-Formular mit CSRF-Token ersetzt.
- `mitarbeiterverwalten.php`: Mitglied anlegen, Bearbeitungsformular öffnen,
  Mitglied speichern, aktivieren und deaktivieren.
- `namenanpassen.php`: Anzeigenamen aktualisieren.
- `index.php`: eigene Web-Striche eintragen.
- `stricheintragen.php`: Sammelerfassung von Strichen.
- `einzahlung.php`: Sammelerfassung von Einzahlungen.
- `letzteneintraege.php`: letzte Einzahlungen und Strich-Einträge löschen.
- `csvupload.php`: CSV-Zahlungsimport.

Noch offen:

- Spezialprozesse: `mailversenden.php`, `jahresauswertung.php`.

### CSV-Upload-Härtung

- `csvupload.php` nutzt jetzt CSRF.
- Uploads werden unter `var/uploads` gespeichert und nach der Verarbeitung
  gelöscht. Damit liegen importierte Dateien nicht mehr im Webroot.
- Dateiendung, Dateigröße und MIME-Typ werden vor der Verarbeitung geprüft.
- Hochgeladene Dateien bekommen serverseitig erzeugte Zufallsnamen.
- CSV-Auswertungswerte werden HTML-escaped ausgegeben.
- Die PayPal-Namenssuche nutzt `Name` und `paypalname` mit expliziter
  Parameterbindung.

Offen für M6:

- Importvorschau vor dem Schreiben.
- Import-Batch/Audit-Log.
- Saubere Fehlerberichte je CSV-Zeile.

### Migrationen

- `database/migrations/0001_legacy_mysql_baseline.sql` bildet die bisherige
  MySQL-Dev-Baseline als erste versionierte Migration ab.
- `scripts/dev-db.php` verwaltet `schema_migrations` und führt neue
  Migrationen idempotent aus.
- `scripts/migrate.php` ist der direkte Runner für Migrationen.
- `scripts/init-mysql-dev.php` nutzt ab jetzt ebenfalls die Migrationslogik.

### Layout

- `header.php` und `footer.php` bleiben vorerst Legacy-Wrapper.
- Die spätere Trennung in Public-Layout und App-Layout wird erst umgesetzt,
  wenn die neue Route-/View-Struktur steht.
- Die bestehende HTML5-UP-Struktur, Sidebar und Assets bleiben unverändert.

## Ausführung

Mit normaler PHP-CLI:

```bash
php scripts/migrate.php
php scripts/init-mysql-dev.php
php scripts/check-golden-master.php
php scripts/http-smoke.php
```

In der aktuellen lokalen Umgebung:

```bash
LD_LIBRARY_PATH="$PWD/.local/php/usr/lib/x86_64-linux-gnu:$PWD/.local/php/usr/lib/x86_64-linux-gnu/sasl2" \
  "$PWD/.local/php/usr/bin/php8.3" \
  -c "$PWD/.local/php-dev.ini" \
  scripts/migrate.php
```

Die Skripte erwarten die bekannten Dev-Umgebungsvariablen `DB_HOST`, `DB_NAME`,
`DB_USER` und `DB_PASS`. `scripts/init-mysql-dev.php` braucht zusätzlich
`DEV_AUTH_EMAIL`.

## Aktueller Prüfstatus

- Migration `0001_legacy_mysql_baseline.sql` erfolgreich angewendet.
- Zweiter Migrationslauf meldet: Datenbank ist aktuell.
- PHP-Syntax für Bootstrap, Migrationen, Init-Skript und Config ist sauber.
- Session-Start läuft in der lokalen Dev-Umgebung ohne PHP-Warnings über
  `var/sessions`.
- CSRF negative Tests: `hinweise.php`, `mitarbeiterverwalten.php` und
  `namenanpassen.php` liefern bei POST ohne Token HTTP 419.
- CSRF positive Tests: gültige Token funktionieren für Hinweis-Anlage,
  Mitglieder-Bearbeitungsformular und Namensanpassung. Der temporäre
  Testhinweis wurde wieder entfernt.
- CSRF negative Tests für Buchungsflows: `index.php`, `stricheintragen.php`
  und `einzahlung.php` liefern bei POST ohne Token HTTP 419.
- CSRF positive Tests für Buchungsflows: gültige Token funktionieren für
  eigene Web-Striche, Sammelstriche und Sammeleinzahlungen. Die temporären
  Testbuchungen wurden wieder entfernt.
- CSRF negative Tests für Korrektur-/Löschflows: `letzteneintraege.php`
  liefert bei POST ohne Token HTTP 419.
- CSRF positive Tests für Korrektur-/Löschflows: gültige Token funktionieren
  für das Löschen temporärer Einzahlungs- und Strich-Testeinträge.
- CSRF negative Test für CSV-Upload: POST ohne Token liefert HTTP 419.
- CSV-Upload positive Tests: gültiges Token verarbeitet eine CSV-Datei, erkennt
  eine PayPal-Alias-Dublette und hinterlässt keine Datei in `var/uploads`.
- CSV-Upload negative Tests: Nicht-CSV-Dateien werden abgewiesen.
- Golden Master weiterhin grün mit 104 Assertions.
- HTTP-Smoke weiterhin grün mit 23 sicheren Seiten inklusive Landingpage, Login,
  Registrierung, Passwort-Reset, E-Mail-Verifikation und geschützter
  Mandant-Einstellungen, geschützter Mandantenauswahl sowie Ledger-Preview.

## Bewusste Grenzen

- Keine Tenant-/User-/Rollen-Tabellen in M2. Diese gehören zu M3.
- Keine globale CSRF-Erzwingung für Spezialprozesse. `mailversenden.php` und
  `jahresauswertung.php` werden in M6 als Jobs mit Dry-Run, Audit und
  Versandlog neu betrachtet.
- Kein Layout-Umbau in M2. Die visuelle Struktur bleibt stabil; Public-/App-
  Layouts werden mit der M3-/M7-Struktur vorbereitet.
- PDF-/Mail-/Jahresprozesse bleiben als M6-Themen offen.

## Abschlusskriterien

- Migrationsrunner ist vorhanden und idempotent.
- Bootstrap, Session und CSRF-Helper sind zentral verfügbar.
- Die relevanten Legacy-Schreibseiten sind CSRF-geschützt.
- CSV-Uploads liegen nicht mehr im Webroot.
- Bestehende Legacy-Seiten bleiben per HTTP-Smoke erreichbar.
- Golden-Master-Fachwerte bleiben unverändert.

## Übergabe an M3

M3 startet mit additiven SaaS-Tabellen und einer Default-Tenant-Abbildung. Die
Legacy-Seiten sollen dabei weiterhin über die bestehenden Tabellen laufen, bis
der fachliche Kern in M4/M5 schrittweise auf das Zielmodell umgestellt wird.

M3 wurde inzwischen gestartet. Umgesetzte Details stehen in
`docs/m3-saas-basis-vorbereitung.md`. Der ursprüngliche Startpunkt war:

1. `tenants`, `tenant_settings`, `users`, `tenant_memberships` und
   `participants` als neue Migration anlegen.
2. Einen Default-Tenant für die bestehende Kaffeeliste definieren.
3. Bestehende `kl_Mitarbeiter` in `participants` spiegeln, inklusive
   `legacy_mitarbeiter_id`.
4. Admins aus `kl_Mitarbeiter.admin` als `tenant_memberships.role = 'admin'`
   beziehungsweise für den ersten Hauptnutzer als `owner` abbilden.
5. Erst danach Login/Registrierung und Public-/App-Routen anschließen.
