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:
+54
-3
@@ -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];
|
||||
}
|
||||
|
||||
+34
-19
@@ -180,12 +180,16 @@ function createAdressQry($page,$pagesize,$src,$combiner,$fields,$full=0,$typ=1)
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Suche in den Dokumenten eines Kunden. Liefert array("all" => sql,
|
||||
* "limited" => sql mit LIMIT, "params" => Platzhalterwerte für beide).
|
||||
* $combiner muss "AND" oder "OR" sein.
|
||||
*/
|
||||
function createKdnRechQry($page,$pagesize,$src,$combiner,$id,$typ,$sks=0)
|
||||
{
|
||||
if($typ == 1)
|
||||
$docnr = "rechnr";
|
||||
else
|
||||
$docnr = "rechnr";
|
||||
$docnr = "rechnr";
|
||||
$combiner = $combiner == "OR" ? "OR" : "AND";
|
||||
$params = array($id, $typ);
|
||||
if(!$sks)
|
||||
{
|
||||
$query = "SELECT rechnung.$docnr AS rechnr,
|
||||
@@ -199,11 +203,12 @@ function createKdnRechQry($page,$pagesize,$src,$combiner,$id,$typ,$sks=0)
|
||||
LEFT JOIN rechtyp ON rechnung.typ_id = rechtyp.id
|
||||
LEFT JOIN artikel_rechn ON rechnung.id = artikel_rechn.rech_id
|
||||
LEFT JOIN artikel ON artikel_rechn.art_id = artikel.id
|
||||
WHERE partner_id='".$id."' AND rechtyp.id = '".$typ."' AND rechnung.sks = 0 ";
|
||||
WHERE partner_id = ? AND rechtyp.id = ? AND rechnung.sks = 0 ";
|
||||
foreach($src AS $tag)
|
||||
{
|
||||
$tag .= "%";
|
||||
$query .= " $combiner (artikel.bezeichn LIKE '$tag' OR rechnung.rechnr LIKE '$tag' OR DATE_FORMAT(rechnung.datetime, '%d.%m.%Y') LIKE '$tag')";
|
||||
$query .= " $combiner (artikel.bezeichn LIKE ? OR rechnung.rechnr LIKE ? OR DATE_FORMAT(rechnung.datetime, '%d.%m.%Y') LIKE ?)";
|
||||
array_push($params, $tag, $tag, $tag);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -217,19 +222,21 @@ function createKdnRechQry($page,$pagesize,$src,$combiner,$id,$typ,$sks=0)
|
||||
FROM rechnung
|
||||
LEFT JOIN rechtyp ON rechnung.typ_id = rechtyp.id
|
||||
RIGHT JOIN artikelsks ON rechnung.rechnr = artikelsks.rechnr
|
||||
WHERE partner_id='".$id."' AND rechtyp.id = '".$typ."' AND rechnung.sks = 1 ";
|
||||
WHERE partner_id = ? AND rechtyp.id = ? AND rechnung.sks = 1 ";
|
||||
foreach($src AS $tag)
|
||||
{
|
||||
$tag .= "%";
|
||||
$query .= " $combiner (artikelsks.bezeichn1 LIKE '$tag' OR artikelsks.bezeichn2 LIKE '$tag' OR rechnung.rechnr LIKE '$tag' OR DATE_FORMAT(rechnung.datetime, '%d.%m.%Y') LIKE '$tag')";
|
||||
$query .= " $combiner (artikelsks.bezeichn1 LIKE ? OR artikelsks.bezeichn2 LIKE ? OR rechnung.rechnr LIKE ? OR DATE_FORMAT(rechnung.datetime, '%d.%m.%Y') LIKE ?)";
|
||||
array_push($params, $tag, $tag, $tag, $tag);
|
||||
}
|
||||
}
|
||||
|
||||
$query .= " GROUP BY rechnung.rechnr ORDER BY rechnung.datetime DESC ";
|
||||
|
||||
|
||||
$ret["all"] = $query;
|
||||
$query .= " LIMIT ".$page*$pagesize.", $pagesize";
|
||||
$query .= " LIMIT ".((int)$page*(int)$pagesize).", ".(int)$pagesize;
|
||||
$ret["limited"] = $query;
|
||||
$ret["params"] = $params;
|
||||
return $ret;
|
||||
}
|
||||
|
||||
@@ -283,9 +290,14 @@ function createKommQry($page,$pagesize,$src,$combiner,$id)
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Suche über alle Dokumente, Rückgabe wie createKdnRechQry().
|
||||
*/
|
||||
function createRechQry($page,$pagesize,$src,$combiner,$sks=0)
|
||||
{
|
||||
$docnr = "rechnr";
|
||||
$combiner = $combiner == "OR" ? "OR" : "AND";
|
||||
$params = array();
|
||||
if(!$sks)
|
||||
{
|
||||
$query = "SELECT CONCAT(ELT(rechnung.typ_id,'R','G','A','B','L','M'),MID(rechnung.$docnr,1,4),'-',MID(rechnung.$docnr,5,2),'-',MID(rechnung.$docnr,7,2),'.',MID(rechnung.$docnr,9,2)) AS drechnr,
|
||||
@@ -304,9 +316,10 @@ function createRechQry($page,$pagesize,$src,$combiner,$sks=0)
|
||||
foreach($src AS $tag)
|
||||
{
|
||||
$tag = "%".$tag."%";
|
||||
$query .= " $combiner (artikel.bezeichn LIKE '$tag' OR
|
||||
CONCAT(ELT(rechnung.typ_id,'R','G','A','B','L','M'),MID(rechnung.$docnr,1,4),'-',MID(rechnung.$docnr,5,2),'-',MID(rechnung.$docnr,7,2),'.',MID(rechnung.$docnr,9,2)) LIKE '$tag' OR
|
||||
DATE_FORMAT(rechnung.datetime, '%d.%m.%Y') LIKE '$tag')";
|
||||
$query .= " $combiner (artikel.bezeichn LIKE ? OR
|
||||
CONCAT(ELT(rechnung.typ_id,'R','G','A','B','L','M'),MID(rechnung.$docnr,1,4),'-',MID(rechnung.$docnr,5,2),'-',MID(rechnung.$docnr,7,2),'.',MID(rechnung.$docnr,9,2)) LIKE ? OR
|
||||
DATE_FORMAT(rechnung.datetime, '%d.%m.%Y') LIKE ?)";
|
||||
array_push($params, $tag, $tag, $tag);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -326,18 +339,20 @@ function createRechQry($page,$pagesize,$src,$combiner,$sks=0)
|
||||
foreach($src AS $tag)
|
||||
{
|
||||
$tag = "%".$tag."%";
|
||||
$query .= " $combiner (artikelsks.bezeichn1 LIKE '$tag' OR
|
||||
artikelsks.bezeichn2 LIKE '$tag' OR
|
||||
rechnung.rechnr LIKE '$tag' OR
|
||||
DATE_FORMAT(rechnung.datetime, '%d.%m.%Y') LIKE '$tag')";
|
||||
$query .= " $combiner (artikelsks.bezeichn1 LIKE ? OR
|
||||
artikelsks.bezeichn2 LIKE ? OR
|
||||
rechnung.rechnr LIKE ? OR
|
||||
DATE_FORMAT(rechnung.datetime, '%d.%m.%Y') LIKE ?)";
|
||||
array_push($params, $tag, $tag, $tag, $tag);
|
||||
}
|
||||
}
|
||||
|
||||
$query .= " GROUP BY rechnung.rechnr ORDER BY rechnung.datetime DESC ";
|
||||
|
||||
|
||||
$ret["all"] = $query;
|
||||
$query .= " LIMIT ".$page*$pagesize.", $pagesize";
|
||||
$query .= " LIMIT ".((int)$page*(int)$pagesize).", ".(int)$pagesize;
|
||||
$ret["limited"] = $query;
|
||||
$ret["params"] = $params;
|
||||
return $ret;
|
||||
}
|
||||
|
||||
|
||||
+465
-665
File diff suppressed because it is too large
Load Diff
+12
-10
@@ -1,18 +1,20 @@
|
||||
<?php
|
||||
|
||||
// LOG
|
||||
/*
|
||||
* Änderungsprotokoll. $qrystr und $undoqry sind reiner Text zur
|
||||
* Nachvollziehbarkeit (siehe db_sql_text()), sie werden nie ausgeführt.
|
||||
* $type ist ein Eintrag aus accesstype ("anlegen", "ändern", "löschen").
|
||||
*/
|
||||
function saveLog($dbconn,$qrystr,$undoqry,$type)
|
||||
{
|
||||
$qry=mysqli_query($dbconn,"SELECT id FROM accesstype WHERE typ = '$type'") or $err=error("Fehler beim Eintragen der Aktion in die LOG:<br />".mysqli_error($dbconn));
|
||||
$res=mysqli_fetch_array($qry);
|
||||
|
||||
$qry=mysqli_query($dbconn,"INSERT INTO log SET
|
||||
typ_id= '$res[0]',
|
||||
datetime=NOW(),
|
||||
qry='".mysql_escape_string($qrystr)."',
|
||||
undo_qry='".mysql_escape_string($undoqry)."',
|
||||
user_id='".mysql_escape_string($_SESSION["user"])."'")
|
||||
or $err=error("Fehler beim LOGGEN der Aktion:<br />".mysqli_error($dbconn));
|
||||
$err = 0;
|
||||
$typId = db_wert($dbconn, "SELECT id FROM accesstype WHERE typ = ?", array($type));
|
||||
if($typId === false)
|
||||
return error("Fehler beim Eintragen der Aktion in die LOG:<br />".mysqli_error($dbconn));
|
||||
db_abfrage($dbconn, "INSERT INTO log SET typ_id = ?, datetime = NOW(), qry = ?, undo_qry = ?, user_id = ?",
|
||||
array((int)$typId, $qrystr, $undoqry, $_SESSION["user"]))
|
||||
or $err = error("Fehler beim LOGGEN der Aktion:<br />".mysqli_error($dbconn));
|
||||
return $err;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user