Files
adminandClaude Opus 5 490c4f12a4 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>
2026-09-22 00:04:38 +02:00

157 lines
6.1 KiB
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)
{
$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)
{
$zeitpunkt = date("d.m.Y", strtotime($termtime));
if($stunden / 24 > 1)
$zeitpunkt .= " - ".date("d.m", strtotime("+".(($stunden / 24) - 1)." day", strtotime($termtime)));
}
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);
$numres = zaehleTreffer($dbconn, $queries);
if($numres)
{
$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++;
$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)
{
$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"];
}
$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;
if($fulltag)
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."' />";
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
{
if($fulltag)
ok("Die Suche nach ".htmlspecialchars($fulltag)." ergab keine Ergebnisse.");
else
ok("Keine Einträge.");
}
}
?>