diff --git a/.gitignore b/.gitignore index 882b235..e539a92 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ vendor/ -data/mariadb/ -uploads/ +# Persistente Nutzdaten (DB, Belege, Logo) – nie ins Repo, siehe data/README.md. +# data/README.md selbst ist per Negation unten wieder eingeschlossen. +data/* +!data/README.md backups/ .env *.log diff --git a/Changes.md b/Changes.md index 5bd7a48..1ff70eb 100644 --- a/Changes.md +++ b/Changes.md @@ -2,6 +2,34 @@ --- +## 2026-07-31 – Firmenlogo persistent in `data/logo`, Nutzdaten gebündelt unter `data/` + +- Fix #116 hatte die Beleg-Uploads aus dem Web-Root nach `data/uploads` verschoben – inklusive des Unterordners `logo/`. Angepasst wurden damals aber nur Beleg-, Bank- und Invoice-Code; `Setting`, `SettingsController` und `PdfInvoiceService` zeigten weiterhin auf `public/static/uploads/logo/`. Dadurch war das hochgeladene Firmenlogo ab diesem Zeitpunkt nicht mehr auffindbar (Datei vorhanden, Pfad falsch) und ein Neu-Hochladen legte es erneut im Web-Root ab, wo es bei jedem Update verloren ging. +- **Neue Ablagestruktur:** Alle persistenten Nutzdaten liegen jetzt gebündelt und nach Art getrennt unter `data/` – `data/mariadb` (Datenbank), `data/belege` (Belegdateien), `data/logo` (Firmenlogo). Jedes Verzeichnis ist ein eigenes Volume; `data/mariadb` wird nicht mehr in den `web`-Container gemountet. +- **`data/README.md`** (neu): Beschreibt für den Admin, was wo liegt, warum die Dateien außerhalb des Web-Roots stehen und wie eine vollständige Datensicherung aussieht. +- **`src/Config.php`:** `upload_dir` zeigt auf `data/belege`, neuer Schlüssel `logo_dir` auf `data/logo`. +- **`src/Models/Setting.php`:** Neue Methode `logoAbsolutePath()` löst den DB-Wert gegen `logo_dir` auf (toleriert Altpfade mit `static/uploads/`-Präfix); `deleteLogo()` nutzt sie statt des Web-Root-Pfads. `setLogo()` speichert nur noch den Dateinamen. +- **`src/Controllers/SettingsController.php`:** `uploadLogo()` schreibt nach `data/logo` und räumt beim Formatwechsel (png ↔ jpg) die Altdatei ab; neue Methode `serveLogo()` liefert das Logo authentifiziert über `/settings/logo/file` aus (Pfad- und MIME-Prüfung). +- **`src/Services/PdfInvoiceService.php`, `src/Views/invoices/show.php`, `src/Views/settings/form.php`:** Beziehen das Logo über den neuen Endpunkt bzw. den absoluten Dateipfad statt über einen Web-Root-Pfad. +- **`migrations/089_pfade_ohne_webroot_praefix.sql`** (neu): Entfernt die historischen `static/uploads/`-Präfixe aus `belege.storage_path` und `company_settings.logo_path`; idempotent. +- **`scripts/migrate-datenverzeichnis.sh`** (neu): Verschiebt vorhandene Daten aus `./uploads` nach `./data/belege` bzw. `./data/logo`. Der Umzug läuft in einem kurzlebigen Container als root, da die Dateien `www-data` gehören – kein sudo nötig, idempotent. +- **`docker-compose.yml`, `Dockerfile`, `docker-entrypoint.sh`, `.gitignore`:** Volumes, Verzeichnisanlage und Rechte auf die neue Struktur umgestellt; `.gitignore` schließt `data/*` aus (Ausnahme: `data/README.md`), sodass `git clean` die Nutzdaten nicht mehr treffen kann. +- **Kein Breaking Change bei bestehenden Installationen:** Der Entrypoint übernimmt vorhandene Dateien beim Container-Start automatisch nach `data/belege` bzw. `data/logo` – sowohl aus der gemeinsamen `uploads`-Ablage (dafür bleibt `./uploads` übergangsweise als `data/legacy-uploads` gemountet) als auch ein Logo aus dem alten Web-Root. Ein reines `scripts/update.sh` (git pull + Rebuild + Start) genügt damit; ohne diese Automatik hätte Migration 089 die DB-Pfade umgeschrieben, während die Dateien am alten Ort liegen geblieben wären. +- **Fix: Datei-Backups liefen seit Fix #116 leer.** `scripts/backup.sh` sicherte weiterhin `public/static/uploads/` – seit dem Umzug aus dem Web-Root ein leeres Verzeichnis. Die Archive enthielten dadurch keine einzige Belegdatei mehr (117 Bytes statt zweistelliger MB-Beträge), ohne dass es auffiel; gesichert wurde nur noch die Datenbank. Das Skript sichert jetzt `data/belege` und `data/logo` und bricht nicht mehr stillschweigend ab, sondern warnt, wenn das Archiv keine Datei enthält. +- **`scripts/restore.sh`:** Stellt Belege und Logo an den neuen Orten wieder her und erkennt zusätzlich das alte `uploads/`-Archivformat, sodass ältere Datei-Backups einspielbar bleiben. +- **`scripts/update.sh`:** Neuer Schritt 2 aktualisiert `scripts/` per `git pull` und startet sich in der neuen Fassung neu, **bevor** das Backup läuft. Bislang sicherte ein Update sich noch mit den Skripten des Vorgängerstands ab – dadurch entstand das leere Datei-Backup. Der Ausgangs-Commit wird über `STEUERKRAM_BEFORE_COMMIT` an den Neustart weitergereicht, damit die Rebuild-Erkennung die Änderungen weiterhin sieht. Der frühere, engere Selbst-Neustart (nur bei Änderung an `update.sh` selbst, und erst nach dem Backup) entfällt. +- **`scripts/backup.sh`:** Sichert defensiv jeden Ort, an dem Nutzdateien liegen können (`data/belege`, `data/logo`, `data/legacy-uploads`, `data/uploads` sowie das alte Web-Root-Verzeichnis), sofern er nicht leer ist. Damit ist das Backup auch im Übergangszustand eines laufenden Updates vollständig, in dem die Skripte schon neu sind, die Dateien aber erst beim nächsten Container-Start umziehen. +- **`scripts/restore.sh`:** Entpackt in ein Staging-Verzeichnis und normalisiert anschließend alle je verwendeten Ablagen (`belege/`, `logo/`, `legacy-uploads/`, `uploads/`) auf die aktuelle Struktur. +- **`tests/71_DatenverzeichnisPfadeTest.php`** (neu): Hält Backup-, Restore-, Compose-, Entrypoint- und Update-Pfade mit `Config::upload_dir`/`logo_dir` zusammen und prüft, dass die Skript-Vorabaktualisierung vor dem Backup steht – genau dieser Drift hatte das leere Backup verursacht. + +## 2026-07-31 – Fix: Logo-Upload defekt (falsches Ziel-Formular + Warnings in Setting::update()) + +- Der „Hochladen"-Button in den Einstellungen postete an `/settings` statt an `/settings/logo`: Das Logo-Formular lag als verschachteltes `
` bereits `#settings-form` schloss. Folge: Der Upload lief nie, stattdessen wurde `Setting::update()` mit unvollständigen POST-Daten aufgerufen. Aus demselben Grund war auch der Logo-Löschen-Button (`form="delete-logo-form"`) wirkungslos. +- `Setting::update()` las `mail_subject_template`/`mail_body_template` weiterhin per `?:` aus `$data`, obwohl beide Felder seit „E-Mail-Betreff und -Text pro Kunde" (2026-05-30) nicht mehr im Einstellungsformular stehen. Das erzeugte „Undefined array key"-Warnings; deren Ausgabe verhinderte anschließend den `Location`-Header in `View::redirect()` („headers already sent"). +- **`src/Views/settings/form.php`:** `#logo-upload-form` und `#delete-logo-form` stehen jetzt als eigenständige, leere Formulare außerhalb von `#settings-form`; Datei-Input und Buttons sind per `form="…"`-Attribut daran gebunden. +- **`src/Models/Setting.php`:** Alle optionalen Felder werden konsistent über `($data['x'] ?? '') ?: default` gelesen, sodass fehlende POST-Keys keine Warnings mehr auslösen. +- **`tests/70_SettingsLogoUploadTest.php`:** Neuer Test prüft, dass `Setting::update()` ohne optionale Keys warnungsfrei durchläuft, dass `setLogo()`/`deleteLogo()` den Pfad korrekt schreiben und dass in `#settings-form` kein verschachteltes `