Rechnungsmodul auf Prepared Statements umgebaut

- incs/rechnung.php: alle Abfragen mit Platzhaltern. saveRechnung() setzte
  $_POST ungeprueft in SQL (Injection; ein Apostroph im Text brach das
  Speichern ab). Speichern, Kopieren und Loeschen laufen in Transaktionen,
  bei Fehlern bleibt nichts Halbes zurueck. Formularwerte ueber
  formularText/-Zahl/-Ganzzahl (strikter Modus).
- copyRech() trug die Rechnungsnummer ohne typ_id ein (Fehler im strikten
  Modus) und zaehlte typuebergreifend; jetzt wie beim Anlegen pro Typ
  (neueRechnungsnummer()). Artikel und Eigenschaften per INSERT ... SELECT.
- Adressblock-Formatierung aus makeadressblock() und
  makeadressblockUmschlag() in formatiereAdressblock() zusammengefuehrt.
- ajax/autosave.php und ajax/preview.php teilen sich saveTmpRechnung();
  ueberzaehlige Entwuerfe werden samt Artikeln entfernt (vorher FK-Fehler).
- sites/rechnungadd.php: Storno ohne SQL-Injection ueber $_GET["id"],
  Auswahllisten ueber db_zuordnung(), tote Kommissions-Abfrage entfernt.
- Rechnungssuche (createRechQry/createKdnRechQry) liefert SQL und Parameter
  getrennt; Suchbegriffe waren ungeprueft im SQL.
- saveLog() mit Platzhaltern, Protokolltext ueber db_sql_text().
- db.php: db_zuordnung(), db_sql_text(); Helfer liefern Strings wie
  mysqli_query(), damit alter Code sich gleich verhaelt.

Getestet: alter und neuer Code lesen dieselbe migrierte Datenbank, 150 von
155 Pruefungen (10.936 Werte) identisch, die uebrigen 5 sind der
Injection-Suchbegriff (alt: SQL-Fehler). Schreibtests fuer Entwurf,
Anlegen, Nummernkreise, Aendern, Rollback, Kopieren, Adresse, Storno und
Loeschen im strikten Modus.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-17 10:41:43 +02:00
co-authored by Claude Opus 5
parent a130ed56a3
commit d8fff4aeb6
11 changed files with 666 additions and 1243 deletions
+54 -3
View File
@@ -44,19 +44,36 @@ function db_verbinden($server, $user, $pw, $db)
/*
* Führt eine Abfrage mit ?-Platzhaltern aus. Gibt bei SELECT ein
* mysqli_result zurück, sonst true, bei Fehlern false.
*
* Achtung: Prepared Statements liefern Zahlen als int/float, mysqli_query()
* lieferte Strings. db_zeilen(), db_zeile(), db_wert() und db_zuordnung()
* wandeln deshalb in Strings um, damit sich alter Code (=== "0", "1" etc.)
* gleich verhält. Wer das Ergebnis von db_abfrage() selbst ausliest, bekommt
* die nativen Typen.
*/
function db_abfrage($dbconn, $sql, $params = array())
{
return mysqli_execute_query($dbconn, $sql, array_values($params));
}
/* Werte einer Zeile als Strings wie bei mysqli_query(), NULL bleibt NULL */
function db_als_text($zeile)
{
if(!is_array($zeile))
return $zeile;
foreach($zeile as $spalte => $wert)
if($wert !== null && !is_string($wert))
$zeile[$spalte] = (string)$wert;
return $zeile;
}
/* Alle Zeilen als Liste assoziativer Arrays, bei Fehlern false. */
function db_zeilen($dbconn, $sql, $params = array())
{
$res = db_abfrage($dbconn, $sql, $params);
if(!$res)
return false;
return mysqli_fetch_all($res, MYSQLI_ASSOC);
return array_map("db_als_text", mysqli_fetch_all($res, MYSQLI_ASSOC));
}
/* Erste Zeile als assoziatives Array, null ohne Treffer, bei Fehlern false. */
@@ -65,7 +82,41 @@ function db_zeile($dbconn, $sql, $params = array())
$res = db_abfrage($dbconn, $sql, $params);
if(!$res)
return false;
return mysqli_fetch_assoc($res);
return db_als_text(mysqli_fetch_assoc($res));
}
/*
* Zwei Spalten als Zuordnung erste => zweite, etwa für Auswahllisten
* (SELECT id, anrede FROM anrede). Bei Fehlern false.
*/
function db_zuordnung($dbconn, $sql, $params = array())
{
$res = db_abfrage($dbconn, $sql, $params);
if(!$res)
return false;
$zuordnung = array();
while($zeile = mysqli_fetch_row($res))
$zuordnung[$zeile[0]] = $zeile[1] === null ? null : (string)$zeile[1];
return $zuordnung;
}
/*
* Die Abfrage mit eingesetzten Werten als lesbarer Text - ausschließlich für
* das Änderungsprotokoll (Tabelle log). Nie ausführen.
*/
function db_sql_text($dbconn, $sql, $params = array())
{
$teile = explode("?", $sql);
$text = array_shift($teile);
foreach(array_values($params) as $i => $wert)
{
if($wert === null)
$text .= "NULL";
else
$text .= "'".mysqli_real_escape_string($dbconn, (string)$wert)."'";
$text .= isset($teile[$i]) ? $teile[$i] : "";
}
return $text;
}
/* Erste Spalte der ersten Zeile, null ohne Treffer, bei Fehlern false. */
@@ -75,5 +126,5 @@ function db_wert($dbconn, $sql, $params = array())
if(!$res)
return false;
$zeile = mysqli_fetch_row($res);
return $zeile === null ? null : $zeile[0];
return $zeile === null || $zeile[0] === null ? null : (string)$zeile[0];
}