Termine auf Prepared Statements umgebaut

Kalender, Terminliste, Terminformular und Terminzettel laufen ueber
db_abfrage() mit Platzhaltern; das Anlegen einer mehrtaegigen Terminreihe
liegt in einer Transaktion. Die Logik steckt jetzt in incs/termin.php,
sites/termin*.php rufen sie nur noch auf.

Dabei behoben:
- SQL-Injektion ueber die Suchbegriffe der Terminliste, ueber die
  Mehrfachauswahl (Ankreuzfelder del<N>/erl<N> gingen roh in DELETE bzw.
  UPDATE), ueber die Wiedervorlage-Tage (INTERVAL $folgedays DAY) und ueber
  ?action=terminshow&id=... in maketerminarray(), maketerminadresse(),
  maketelnrn() und makegooglestring()
- gespeichertes XSS: Beschreibung, Notiz, Termintyp, Ort, Kundenname und
  Mitarbeiterfarbe im Kalender, in der Terminliste und im Formular
- "or $err=error(...)" stand innerhalb des Abfrage-Strings, dadurch wurden
  die Nebenzeilen einer Terminreihe beim Loeschen nie entfernt
- die Abfrage zum Folgetermin las eine Spalte typ_id, die es in termin nicht
  gibt; Typ und Wiedervorlage fehlten deshalb beim Bearbeiten
- ein Folgetermin wurde mit termtime='' und termdauer='' angelegt, ein
  Termin ohne Folgetermin mit followed_by='' - im strikten Modus Fehler
- ein Termin ohne Datum landete auf dem 01.01.1970 und gab dabei
  "NICHTTERMINIERT!!" mitten in die Seite aus
- die Ueberschneidungspruefung verglich ab dem zweiten Tag gegen das Ende
  des ersten Tages
- eine unbekannte Registerkarte der Terminliste ergab ein leeres WHERE und
  damit einen SQL-Fehler; jetzt gilt die Standardansicht
- terminshow.php pruefte den Typ mit = statt ==
- der Kalender haengt nicht mehr an der Locale de_DE (strftime)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-22 00:04:38 +02:00
co-authored by Claude Opus 5
parent 3dfbf1c9c7
commit 490c4f12a4
12 changed files with 986 additions and 950 deletions
+120 -158
View File
@@ -1,166 +1,132 @@
<?php
<?php
require_once("../incs/connectmysql.php");
require_once("../incs/funcs.php");
require_once("../incs/listfuncs.php");
require('../smarty/libs/Smarty.class.php');
$smarty = new Smarty;
$pagesize = 25;
if(!isset($err))
$err = "";
/*
* Datum und Dauer eines Termins für die Liste. Ab 24 Stunden zählt der
* Termin in Tagen, sonst in Stunden und Minuten.
*/
function terminZeitraum($termtime, $termdauer)
{
$usercolors = array("#f00","#0c0","#ff0","#90c","#0bb","#066","#09f","#f60","#00f","#f09");
$fulltag = utf8_encode($_GET["search"]);
$src = explode(" ",$fulltag);
if(isset($_GET["link"])){
$combiner = mysql_escape_string($_GET["link"]);
}else{
$combiner = "";
}
if($combiner != "AND" && $combiner != "OR")
$stunden = (int)strtok((string)$termdauer, ":");
$minuten = (int)substr((string)$termdauer, 3, 2);
if($termtime == "0000-00-00 00:00:00")
$zeitpunkt = "N/A";
elseif($stunden > 23)
{
$combiner = "AND";
$zeitpunkt = date("d.m.Y", strtotime($termtime));
if($stunden / 24 > 1)
$zeitpunkt .= " - ".date("d.m", strtotime("+".(($stunden / 24) - 1)." day", strtotime($termtime)));
}
$page = $_GET["page"];
$termtyp = $_GET["termtyp"];
else
$zeitpunkt = date("d.m.Y H:i ", strtotime($termtime))."Uhr";
$dauer = "";
if($stunden > 23)
$dauer = ($stunden / 24)." Tag".($stunden / 24 > 1 ? "e" : "");
elseif($stunden > 0)
$dauer = $stunden." Std";
if($stunden <= 23 && $stunden > 0 && $minuten > 0)
$dauer .= " - ";
if($stunden <= 23 && $minuten > 0)
$dauer .= $minuten." Min";
return array($zeitpunkt, $dauer);
}
if(!$err)
{
// kommt per encodeURIComponent bereits als UTF-8
$fulltag = isset($_GET["search"]) && is_string($_GET["search"]) ? $_GET["search"] : "";
$link = isset($_GET["link"]) && is_string($_GET["link"]) ? $_GET["link"] : "";
$page = isset($_GET["page"]) ? (int)$_GET["page"] : 0;
$termtyp = isset($_GET["termtyp"]) ? (int)$_GET["termtyp"] : 1;
$_SESSION["termtyp"] = $termtyp;
$src = explode(" ", $fulltag);
$combiner = $link == "OR" ? "OR" : "AND";
$listlines = array();
$colors = array();
$queries = createTermQry($page,$pagesize,$src,$combiner,$termtyp);
$qry = mysqli_query($dbconn,$queries["all"])
or die("Datenbank fehler: ".mysqli_error($dbconn));
$numres = mysqli_num_rows($qry);
$numres = zaehleTreffer($dbconn, $queries);
if($numres)
{
$qry = mysqli_query($dbconn,$queries["limited"])
or die("Datenbank fehler: ".mysqli_error($dbconn));
$qry = db_abfrage($dbconn,$queries["limited"],$queries["params"])
or die("Datenbank fehler: ".htmlspecialchars(mysqli_error($dbconn)));
$counter = 0;
while($res=mysqli_fetch_assoc($qry))
{
$counter = $counter+1;
$termdauer = "";
$termtime ="";
$telqry = mysqli_query($dbconn,"SELECT GROUP_CONCAT(nr SEPARATOR ', ') AS nr from tel WHERE partner_id = '".$res["partner_id"]."' AND RIGHT(nr,1) <> '-' GROUP BY partner_id")or $err=error("Fehler beim Listen der Termine <br />".mysqli_error($dbconn));
$telres=mysqli_fetch_assoc($telqry);
//Normale Termine anzeigen
if($res)
$counter++;
$id = (int)$res["id"];
$kopf = htmlspecialchars((string)$res["head"]);
$actions = "<span class='del' title='löschen' onClick='askdel(\"Möchten Sie den Termin ".$kopf."<br />wirklich <b>unwiederruflich</b> löschen?\",\"?action=terminadd&id=".$id."&del=1\")'></span>";
$actions .= "<a href='?action=terminadd&id=".$id."&adressid=".(int)$res["adresse_id"]."'><span class='edit' title='Termin ändern'></span></a>";
$actions .= "<a href='?action=kundenshow&id=".(int)$res["partner_id"]."'><span class='partner' title='Kunden anzeigen'></span></a>";
$actions .= "<a href='?action=terminshow&id=".$id."'><span class='print' title='Termin drucken'></span></a>";
if(!$res["done"])
$actions .= "<span class='check' title='Termin als erledigt markieren' onClick='askbranch(\"Möchten Sie einen Folgetermin vormerken?\",\"?action=terminadd&id=".$id."\",\"?action=terminadd&id=".$id."&done=yes\")'></span>";
else
$actions .= "<a href='?action=terminadd&id=".$id."&done=no'><span class='cross' title='Termin als nicht erledigt markieren'></span></a>";
list($termtime, $termdauer) = terminZeitraum($res["termtime"], $res["termdauer"]);
$adresse = "<b>".htmlspecialchars($res["plz"]." ".$res["ort"]);
if($res["ortsteil"])
$adresse .= "-".htmlspecialchars((string)$res["ortsteil"]);
$adresse .= "</b><br />".htmlspecialchars($res["str"]." ".$res["hnr"]);
// Nummern ohne Durchwahl-Bindestrich am Ende
$nummern = db_wert($dbconn, "SELECT GROUP_CONCAT(nr ORDER BY id SEPARATOR ', ') FROM tel
WHERE partner_id = ? AND RIGHT(nr,1) != '-'", array($res["partner_id"]));
if($nummern)
{
$actions = "<span class='del' title='löschen' onClick='askdel(\"Möchten Sie den Termiiin ".htmlspecialchars($res["head"],ENT_QUOTES)."<br />wirklich <b>unwiederruflich</b> löschen?\",\"?action=terminadd&id=".$res["id"]."&del=1\")'></span>";
$actions .= "<a href='?action=terminadd&id=".$res["id"]."&adressid=".$res["adresse_id"]."'><span class='edit' title='Termin ändern'></span></a>";
$actions .= "<a href='?action=kundenshow&id=".$res["partner_id"]."'><span class='partner' title='Kunden anzeigen'></span></a>";
$actions .= "<a href='?action=terminshow&id=".$res["id"]."'><span class='print' title='Termin drucken'></span></a>";
if(!$res["done"])
$actions .= "<span class='check' title='Termin als erledigt markieren' onClick='askbranch(\"Möchten Sie einen Folgetermin vormerken?\",\"?action=terminadd&id=".$res["id"]."\",\"?action=terminadd&id=".$res["id"]."&done=yes\")'></span>";
else
$actions .= "<a href='?action=terminadd&id=".$res["id"]."&done=no'><span class='cross' title='Termin als nicht erledigt markieren'></span></a>";
$split_termdauer = explode(":",$res["termdauer"]);
if($res["termtime"] == "0000-00-00 00:00:00")
$termtime = "N/A";
else if($split_termdauer[0] > 23)
{
if($split_termdauer[0]/24 > 1)
{
$termtime = date("d.m.Y - ",strtotime($res["termtime"]));
$termtime .= date("d.m",strtotime("+".(($split_termdauer[0]/24)-1)." day",strtotime($res["termtime"])));
}
else
{
$termtime = date("d.m.Y",strtotime($res["termtime"]));
}
}
else
$termtime = date("d.m.Y H:i ",strtotime($res["termtime"]))."Uhr";
if($split_termdauer[0] > 23)
{
if($split_termdauer[0]/24 > 1)
$termdauer = ($split_termdauer[0]/24)." Tage";
else
$termdauer = ($split_termdauer[0]/24)." Tag";
}
else if($split_termdauer[0] > 0)
$termdauer = ltrim(substr($res["termdauer"],0,2),"0")." Std";
if($split_termdauer[0] > 0 && $split_termdauer[1] > 0)
$termdauer .= " - ";
if($split_termdauer[1] > 0)
$termdauer .= ltrim(substr($res["termdauer"],3,2),"0")." Min";
$adresse = "<b>".$res["plz"]." ".$res["ort"];
if($res["ortsteil"])
$adresse .= "-".$res["ortsteil"];
$adresse .= "</b><br />".$res["str"]." ".$res["hnr"];
if(isset($telres)){
if($res["vname"])
$adresse .= "<br />Tel: ".$telres["nr"]."<br />(".$res["vname"]." ".$res["nname"].")";
else if($res["nname"])
$adresse .= "<br />Tel: ".$telres["nr"]."<br />(".$res["nname"].")";
else if($res["firma"])
$adresse .= "<br />Tel: ".$telres["nr"]."<br />(".$res["firma"].")";
else
$adresse .= "<br />Tel: ".$telres["nr"];
}
$namelist = $res["uservname"]." ".substr($res["usernname"],0,1);
if($res["user2"])
$namelist .= ", ".$res["uservname2"]." ".substr($res["usernname2"],0,1);
if($res["user3"])
$namelist .= ", ".$res["uservname3"]." ".substr($res["usernname3"],0,1);
if($res["user4"])
$namelist .= ", ".$res["uservname4"]." ".substr($res["usernname4"],0,1);
if($termtyp == 1){
$checkbox = "<input type='checkbox' value='".$res["id"]."' id='erl".$counter."' name='erl".$counter."'></input>";
$listlines[] = array("<a href='?action=terminshow&id=".$res["id"]."'><b>".$res["typ"]."</b></a><br />".$namelist,
"<a href='?action=terminshow&id=".$res["id"]."'>".$res["head"]."</a>",
$termtime,
$termdauer,
$adresse,
$actions,
$checkbox
);
}
else if($termtyp == 3){
$checkbox = "<input type='checkbox' value='".$res["id"]."' id='del".$counter."' name='del".$counter."'></input>";
$listlines[] = array("<a href='?action=terminshow&id=".$res["id"]."'><b>".$res["typ"]."</b></a><br />".$namelist,
"<a href='?action=terminshow&id=".$res["id"]."'>".$res["head"]."</a>",
$termtime,
$termdauer,
$adresse,
$actions,
$checkbox
);
}
else{
$listlines[] = array("<a href='?action=terminshow&id=".$res["id"]."'><b>".$res["typ"]."</b></a><br />".$namelist,
"<a href='?action=terminshow&id=".$res["id"]."'>".$res["head"]."</a>",
$termtime,
$termdauer,
$adresse,
$actions
);
}
if($res["user2"] || $res["user3"] || $res["user4"])
$colors[] = "#fff";
else if($res["color"])
$colors[] = $res["color"];
else
$colors[] = "#fff";
$adresse .= "<br />Tel: ".htmlspecialchars($nummern);
if($res["vname"] || $res["nname"])
$adresse .= "<br />(".htmlspecialchars(trim($res["vname"]." ".$res["nname"])).")";
elseif($res["firma"])
$adresse .= "<br />(".htmlspecialchars((string)$res["firma"]).")";
}
$namelist = htmlspecialchars($res["uservname"]." ".substr((string)$res["usernname"], 0, 1));
foreach(array(2, 3, 4) as $n)
if($res["user".$n])
$namelist .= ", ".htmlspecialchars($res["uservname".$n]." ".substr((string)$res["usernname".$n], 0, 1));
$zeile = array("<a href='?action=terminshow&id=".$id."'><b>".htmlspecialchars((string)$res["typ"])."</b></a><br />".$namelist,
"<a href='?action=terminshow&id=".$id."'>".$kopf."</a>",
$termtime,
$termdauer,
$adresse,
$actions);
// Registerkarte 1 hakt ab, Registerkarte 3 löscht - beides mehrfach
if($termtyp == 1 || $termtyp == 3)
{
$feld = $termtyp == 1 ? "erl" : "del";
$zeile[] = "<input type='checkbox' value='".$id."' id='".$feld.$counter."' name='".$feld.$counter."' />";
}
$listlines[] = $zeile;
if($res["user2"] || $res["user3"] || $res["user4"] || !$res["color"])
$colors[] = "#fff";
else
$colors[] = $res["color"];
}
if($termtyp == 1){
$headings = array("Typ","Beschreibung","Datum","Dauer","Ort","Aktionen","&nbsp;&nbsp;Erledigt&nbsp;");
}
else if($termtyp == 3){
$headings = array("Typ","Beschreibung","Datum","Dauer","Ort","Aktionen","&nbsp;&nbsp;Löschen&nbsp;");
}
else{
$headings = array("Typ","Beschreibung","Datum","Dauer","Ort","Aktionen");
}
$headings = array("Typ","Beschreibung","Datum","Dauer","Ort","Aktionen");
if($termtyp == 1)
$headings[] = "&nbsp;&nbsp;Erledigt&nbsp;";
elseif($termtyp == 3)
$headings[] = "&nbsp;&nbsp;Löschen&nbsp;";
$lastdisplay = ($page+1)*$pagesize;
if($numres < $lastdisplay)
$lastdisplay = $numres;
@@ -168,27 +134,23 @@ if(!isset($err))
ok("Die Suche nach ".htmlspecialchars($fulltag)." ergab ".$numres." Ergebnisse.<br /> Ergebnis ".($page*$pagesize+1)." bis ".$lastdisplay);
else
ok($numres." Termine sind insgesamt gespeichert.");
displayPages($pagesize,$page,$numres,"ajax_searchterm",",$termtyp");
if($termtyp == 1){
echo "<form action='?action=terminadd&id=multi&done=yes' method='post'><input type='hidden' name='pagesize' value='".$pagesize."'></input>";
}
else if($termtyp == 3){
echo "<form action='?action=terminadd&id=multi&del=1' method='post'><input type='hidden' name='pagesize' value='".$pagesize."'></input>";
}
displayList($headings,$listlines,Array(),$colors);
if($termtyp == 1 || $termtyp == 3){
echo "<input type='submit' value='Mehrfachänderung ausführen'></input></form><br />";
}
displayPages($pagesize,$page,$numres,"ajax_searchterm",",$termtyp");
displayPages($pagesize,$page,$numres,"ajax_searchterm",",".$termtyp);
if($termtyp == 1)
echo "<form action='?action=terminadd&id=multi&done=yes' method='post'><input type='hidden' name='pagesize' value='".$pagesize."' />";
elseif($termtyp == 3)
echo "<form action='?action=terminadd&id=multi&del=1' method='post'><input type='hidden' name='pagesize' value='".$pagesize."' />";
displayList($headings,$listlines,array(),$colors);
if($termtyp == 1 || $termtyp == 3)
echo "<input type='submit' value='Mehrfachänderung ausführen' /></form><br />";
displayPages($pagesize,$page,$numres,"ajax_searchterm",",".$termtyp);
}
else
else
{
if($fulltag)
ok("Die Suche nach ".htmlspecialchars($fulltag)." ergab keine Ergebnisse.");
else
else
ok("Keine Einträge.");
}
}
?>
?>