diff --git a/CLAUDE.md b/CLAUDE.md
index d9253a3..c9eb02d 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -14,19 +14,22 @@ Syntaxprüfung der eigenen Dateien:
for f in $(git ls-files "*.php" | grep -vE "^(incs/(tcpdf|pChart|PHPMailer|WebAuthn|drawer|font)|smarty|spellcheck|js)/"); do php -l "$f"; done
```
-Zum Ausführen braucht es einen Webserver mit PHP, eine MariaDB/MySQL-Instanz und die externe Konfigurationsdatei (siehe unten). Ohne die Konfiguration springt `index.php` in den Setup-Modus (`update/firststart.php`).
+Zum Ausführen braucht es einen Webserver mit PHP, eine MariaDB/MySQL-Instanz und die externe Konfigurationsdatei (siehe unten). Ohne die Konfiguration springt `index.php` in den Setup-Modus (`update/firststart.php`). **Nur** dann: Das Setup läuft ohne Login, schreibt die Config und kann die Datenbank leeren. Deshalb wird `action=setup` bei gültiger Config verworfen, und `firststart.php` und `rebuild-settings.php` brechen selbst ab. Werte, die in die Config geschrieben werden, immer über `var_export()` maskieren – die Config ist ausführbarer PHP-Code.
## Die zwei zentralen Mechanismen
### 1. Das Verzeichnis bestimmt die Instanz
-`incs/constants.php` leitet aus `getcwd()` den Namen des Installationsverzeichnisses ab und baut daraus:
+`incs/constants.php` leitet aus `getcwd()` den Namen des Installationsverzeichnisses ab. Der wählt aber nur noch die Konfigurationsdatei aus. Der eigentliche Instanzname steht in der Config als `$instanz` und wird zur Konstante `INSTANZ`:
-| Konstante | Wert bei Installation in `…/kunden` |
-|---|---|
-| `CONFIG_FILE` | `kunden_settings.conf` |
-| `UPLOADS_PATH` | `uploads_kunden/` |
-| `SESSION_NAME` | `KUNDENSESS` |
+| Konstante | abgeleitet aus | Wert bei Installation in `…/kunden` |
+|---|---|---|
+| `CONFIG_FILE` | Verzeichnisname | `kunden_settings.conf` |
+| `INSTANZ` | `$instanz` in der Config | `kunden` |
+| `UPLOADS_PATH` | `INSTANZ` | `uploads_kunden/` |
+| `SESSION_NAME` | `INSTANZ` | `KUNDENSESS` |
+
+Fehlt `$instanz` in der Config (ältere Configs, Ersteinrichtung), fällt `INSTANZ` auf den Verzeichnisnamen zurück. Erlaubt sind nur Buchstaben und Ziffern, sonst bricht der Request ab. Die Config wird dafür schon in `constants.php` einmal eingelesen, also **vor** `session_start()`. Code in der Config darf deshalb nichts ausgeben und keine Verbindung voraussetzen.
Aus **einer** Codebasis laufen dadurch mehrere getrennte Instanzen — die `uploads_dev`, `uploads_ingwa`, `uploads_kunden`, `uploads_lsg` im Baum sind die Spuren davon. Jede Instanz hat eigene Config, eigene Session, eigene Uploads.
@@ -42,7 +45,7 @@ cd /home/intranet/kunden && php db-backup.php
`CONFIG_PATH` zeigt auf `/volume1/homes/wagner/kundenconfig/` (Linux) bzw. `c:/kundenverwaltung/config/` (Windows). Geschrieben wird die Datei von `update/rebuild-settings.php` beim Ersteinrichten.
-Die Datei deklariert `$mysql_user`, `$mysql_pw`, `$mysql_server`, `$mysql_db`, `$firma` und weitere ausdrücklich als `global` und weist sie erst danach zu. Nur deshalb funktioniert `readsettings()` in `incs/settings.php`: Das `include` passiert im Funktionsrumpf, ohne die `global`-Zeilen blieben die Variablen dort lokal. Wer die Config-Struktur ändert, muss diese Eigenheit erhalten.
+Neben den Zugangsdaten steht dort auch `$instanz` (siehe oben). Die Datei deklariert `$mysql_user`, `$mysql_pw`, `$mysql_server`, `$mysql_db`, `$firma` und weitere ausdrücklich als `global` und weist sie erst danach zu. Nur deshalb funktioniert `readsettings()` in `incs/settings.php`: Das `include` passiert im Funktionsrumpf, ohne die `global`-Zeilen blieben die Variablen dort lokal. Wer die Config-Struktur ändert, muss diese Eigenheit erhalten.
Zugangsdaten gehören **nie** in den Code — `db-backup.php` zeigt das Muster für Skripte außerhalb des Request-Zyklus (Config einlesen, Passwort über eine temporäre Optionsdatei an `mysqldump`, nicht per Kommandozeile).
diff --git a/incs/constants.php b/incs/constants.php
index 9191de3..41f92ca 100644
--- a/incs/constants.php
+++ b/incs/constants.php
@@ -12,8 +12,34 @@ if(DIRECTORY_SEPARATOR == "/")
else
define("CONFIG_PATH", "c:/kundenverwaltung/config/");
define("CONFIG_FILE", $dir."_settings.conf");
-define("UPLOADS_PATH", "uploads_".$dir."/");
-define("SESSION_NAME",strtoupper($dir)."SESS");
+
+/*
+ * Der Verzeichnisname wählt nur noch aus, welche Konfigurationsdatei
+ * geladen wird. Der Name der Instanz steht dort als $instanz und bestimmt
+ * Uploads-Verzeichnis und Session-Name. Fehlt $instanz (ältere Configs,
+ * Ersteinrichtung), gilt wie bisher der Verzeichnisname.
+ */
+function instanzAusKonfiguration($datei, $fallback)
+{
+ if(!is_file($datei))
+ return $fallback;
+ // Leerzeilen hinter dem schließenden PHP-Tag der Config würden sonst
+ // ausgegeben und den Session-Cookie verhindern, weil das hier vor
+ // session_start() läuft.
+ ob_start();
+ include($datei);
+ ob_end_clean();
+ if(!isset($instanz) || $instanz === "")
+ return $fallback;
+ // Landet in Dateipfaden und im Session-Namen (dort nur alphanumerisch zulässig).
+ if(!is_string($instanz) || !preg_match('/^[A-Za-z0-9]+$/', $instanz))
+ die("Ungültiger Instanzname in ".$datei.": nur Buchstaben und Ziffern erlaubt.");
+ return $instanz;
+}
+
+define("INSTANZ", instanzAusKonfiguration(CONFIG_PATH.CONFIG_FILE, $dir));
+define("UPLOADS_PATH", "uploads_".INSTANZ."/");
+define("SESSION_NAME",strtoupper(INSTANZ)."SESS");
define("RIGHTS_KUNDEN", 0);
define("RIGHTS_TERMINE", 1);
define("RIGHTS_ANGEBOTE", 2);
diff --git a/incs/makepdf.php b/incs/makepdf.php
index bcf2299..47793e0 100644
--- a/incs/makepdf.php
+++ b/incs/makepdf.php
@@ -1107,13 +1107,19 @@ function writeZahlziel($ktonr,$zahlziel,$gespreis,$wahrung,$rechdate,$kundeid)
function savePDF($pdf,$PDFpath)
{
- $PDFpathpart = dirname($PDFpath)."/";
- $PDFfilepart = basename($PDFpath,".pdf");
+ // TCPDF stellt beim Schreiben file:// voran und braucht deshalb einen
+ // absoluten Pfad. Aufgelöst wird relativ zum Arbeitsverzeichnis, also in
+ // der Installation, aus der der Aufruf kommt - früher stand hier fest
+ // DOCUMENT_ROOT."kunden".
+ $PDFpathpart = realpath(dirname($PDFpath));
+ if($PDFpathpart === false || !is_dir($PDFpathpart))
+ die("PDF-Verzeichnis nicht gefunden: ".dirname($PDFpath));
+ $PDFpathpart = str_replace("\\", "/", $PDFpathpart)."/";
$handle=opendir($PDFpathpart);
while ($file = readdir($handle))
{
- if (preg_match("=^\.{1,2}$=", $file))
- continue; # . und .. Dateien ignorieren
+ if (preg_match("=^\.=", $file))
+ continue; # ., .. und versteckte Dateien (.gitkeep) ignorieren
elseif(is_file($PDFpathpart.$file))
{ #Dateien prüfen, und löschen, wenn älter als $delFile - Minuten
$lastTime = ceil((time() - filemtime($PDFpathpart.$file)));
@@ -1121,11 +1127,9 @@ function savePDF($pdf,$PDFpath)
unlink($PDFpathpart.$file);
}
}
- }
- while(str_starts_with($PDFpath, ".")){
- $PDFpath = substr($PDFpath,1);
}
- $pdf->Output($_SERVER['DOCUMENT_ROOT']."kunden".$PDFpath,"F");
+ closedir($handle);
+ $pdf->Output($PDFpathpart.basename($PDFpath),"F");
}
function outputPDF($pdf)
diff --git a/index.php b/index.php
index 897b02b..d6c78fd 100644
--- a/index.php
+++ b/index.php
@@ -46,8 +46,13 @@ if($version == -1)
$settings=readsettings();
+// Die Ersteinrichtung schreibt die Config neu und kann die Datenbank leeren.
+// Sie läuft ohne Login und darf deshalb nur erreichbar sein, solange es
+// noch keine gültige Config gibt.
if($settings != "valid")
$action = "setup";
+else if($action == "setup")
+ $action = "";
if($settings == "valid" && $action != "setup")
{
diff --git a/update/firststart.php b/update/firststart.php
index 51e1fd8..a19d298 100644
--- a/update/firststart.php
+++ b/update/firststart.php
@@ -1,4 +1,8 @@
-'))
{
$err= error("Konfiguration konnte nicht geschrieben werden!");
@@ -73,7 +90,7 @@ else
$version = $status[1];
}
if(!$err)
- ok("Installation komplett!!
Bitte entfernen Sie nun die Schreibrechte für die Datei /kundenverwaltung/config/settings.conf (chmod 555)");
+ ok("Installation komplett!!
Bitte entfernen Sie nun die Schreibrechte für die Datei ".htmlspecialchars(CONFIG_PATH.CONFIG_FILE)." (chmod 444)");
}
else
$err = error("Konfigurationsdatei fehlerhaft!");