Files
Kundenverwaltung/CLAUDE.md
T
adminandClaude Opus 5 73e64b72ac Instanzname in die Config, Setup-Luecke geschlossen
Instanz:
- $instanz in der externen Config bestimmt INSTANZ, UPLOADS_PATH und
  SESSION_NAME. Der Verzeichnisname waehlt nur noch die Config-Datei aus
  und ist Fallback, solange $instanz fehlt.
- savePDF() schreibt nicht mehr fest nach DOCUMENT_ROOT."kunden", sondern
  in das Verzeichnis, das es auch aufraeumt. .gitkeep wird nicht mehr
  mitgeloescht.

Sicherheit:
- index.php?action=setup war ohne Login auch bei gueltiger Config
  erreichbar. rebuild-settings.php schrieb $_POST ungeprueft als PHP-Code
  in die Config (Codeausfuehrung, sofern die Datei schreibbar war).
- Setup nur noch ohne gueltige Config, firststart.php und
  rebuild-settings.php pruefen das zusaetzlich selbst.
- Config-Werte werden per var_export() maskiert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 08:44:33 +02:00

8.1 KiB
Raw Blame History

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Kundenverwaltung für ein Ingenieurbüro: Kunden, Termine, Projekte, Angebote, Lieferscheine, Rechnungen, Zeiterfassung. Gewachsene PHP-Anwendung (seit ~2011) mit Smarty-Templates und MariaDB/MySQL. Domänenbegriffe, Bezeichner und UI-Texte sind durchgehend deutsch — das bitte beibehalten.

Tooling

Es gibt kein Composer, npm, Build, Linter oder Testsuite. Abhängigkeiten liegen als Kopie in incs/ und werden dort direkt gepflegt. Ein Update einer dieser Bibliotheken ist immer ein manueller Eingriff, kein Paketmanager-Aufruf.

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). 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. Der wählt aber nur noch die Konfigurationsdatei aus. Der eigentliche Instanzname steht in der Config als $instanz und wird zur Konstante INSTANZ:

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.

Konsequenz für alles, was per CLI/Cron läuft: Das Arbeitsverzeichnis muss das Installationsverzeichnis sein, sonst wird die falsche (oder keine) Konfiguration geladen.

cd /home/intranet/kunden && php db-backup.php

constants.php kompensiert dabei nur die Aufrufe aus ajax/ und ajax/auto/, indem es ein bzw. drei Ebenen hochgeht.

2. Zugangsdaten liegen außerhalb des Repos

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.

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).

Request-Ablauf

index.php ist der einzige Einstieg für die Oberfläche:

  1. Konstanten, Session, Konfiguration, DB-Verbindung
  2. Schema-Migration (siehe unten)
  3. head.tpl
  4. bei gültiger Session: checkRightsNInclude() aus incs/funcs.php — ein switch über $_GET["action"], das Rechte prüft und den Pfad in sites/ zurückgibt, der dann inkludiert wird
  5. foot.tpl

Eine neue Seite anzulegen heißt: Datei in sites/ plus ein case in checkRightsNInclude(). Ohne den case ist die Seite nicht erreichbar.

Die Endpunkte in ajax/ gehen nicht über index.php. Sie bootstrappen selbst über incs/connectmysql.php (Konstanten, Session, Config, DB, Escape-Wrapper) und rendern eigene Fragmente nach ajax/templates_c/.

Rechtesystem

$_SESSION["rights"] ist ein String, keine Zahl. Die Zeichenposition benennt den Bereich, der Zeichenwert ist eine Bitmaske:

  • Position: RIGHTS_KUNDEN=0, RIGHTS_TERMINE=1, RIGHTS_ANGEBOTE=2, RIGHTS_LIEFER=3, RIGHTS_RECHNUNGEN=4, RIGHTS_SUPERADMIN=5
  • Wert: RIGHTS_LOOK=1, RIGHTS_CHANGE=2, RIGHTS_ADD=4

Geprüft wird immer nach diesem Muster:

if(substr($_SESSION["rights"], RIGHTS_KUNDEN, 1) & RIGHTS_LOOK)

Schema-Migrationen laufen bei jedem Seitenaufruf

Es gibt keine SQL-Migrationsdateien. Der Stand steht in update/thisversion/currentversion.txt (dreistellig, aktuell 010), die Zielversion ist der erste Eintrag in $versions in update/versions.php. index.php ruft bei jedem Request checknewversion() und manageupdate() aus incs/update.php; liegt die Zielversion höher, arbeitet update_tables() in update/database-update.php sich in einem switch von Version zu Version hoch und schreibt die neue Nummer zurück.

Eine Schemaänderung besteht also aus: neuer Eintrag vorn in update/versions.php und passender case in update_tables(). update/thisversion/ muss für den Webserver schreibbar sein.

Datenbankzugriff

Prozedurales mysqli. $dbconn wird zusätzlich in $_SESSION["dbconn"] abgelegt, damit incs/mysql_ecape_wrapper.php eine globale Funktion mysql_escape_string() als Shim über diese Verbindung bereitstellen kann — ein Überbleibsel der mysql_*-Ära, das in altem Code noch aufgerufen wird.

SQL wird durchgehend per String-Verkettung gebaut. Escaping ist Handarbeit über mysqli_real_escape_string($dbconn, …) und muss bei jeder Änderung mitgedacht werden.

Fallstricke

  • smarty/ enthält Smarty 4.5.6 — der schlichte Verzeichnisname sagt nichts über die Version.
  • PHPMailer liegt in zwei Versionen parallel und beide sind aktiv: index.php lädt incs/PHPMailer_v6 für den Rechnungsversand, sites/projectadd.php dagegen incs/PHPMailer_v5.1. Keine der beiden ist entfernbar, ohne den jeweiligen Aufrufer umzustellen.
  • incs/font/ gehört zu pChart, nicht zu TCPDF. Die TTFs dort werden von stats.php für die Diagramme geladen; TCPDF bringt seine Schriften in incs/tcpdf/fonts/ selbst mit.
  • index.php setzt error_reporting(E_ERROR | E_PARSE). Warnungen und Notices sind unterdrückt, und der Code verlässt sich darauf — nicht ohne Not hochdrehen, sonst überflutet es die Ausgabe.
  • templates_c/, ajax/templates_c/ und temp/ müssen für den Webserver schreibbar sein. Sie sind git-ignoriert und nur über .gitkeep im Repo vorhanden.
  • uploads_*/{notes,signs} enthalten personenbezogene Kundendaten (Rechnungen, Scans, Unterschriften) und sind bewusst git-ignoriert. Nie committen.
  • Zeilenenden werden über .gitattributes auf LF normalisiert.

Authentifizierung

Klassischer Login plus WebAuthn/FIDO2. Server-Seite: authServer.php mit incs/WebAuthn (Bibliothek von Lukas Buchs) und incs/authHelper.php; Client-Seite js/auth.js. Schlüsselverwaltung über addKey.php und sites/userkeys.php.

Historie

Am 2026-08-28 wurden tote Pfade entfernt: smarty3/ (ungenutztes Smarty 3.1.6), incs/tcpdf_old/, incs/fpdf.php samt FPDF-Schriftmetriken und incs/font/makefont/, sites/makepdf_OLD.php, incs/users_old.php und oldindex.php. Auf keines davon verwies noch Code.

Das Repository wurde am 2026-08-28 neu initialisiert. Die mitgelieferte Historie von 2014 war irreparabel beschädigt und wurde verworfen — der erste Commit ist der Ausgangsstand, nicht der Projektbeginn.