Files
xBeaTz d97605f801 security: harden application security baseline
- Fixed SQL injection vulnerabilities with prepared statements
- Added column whitelist protection for dynamic termin updates
- Extracted iCloud credentials into protected config file
- Moved sensitive configuration files out of webroot
- Added authentication and admin checks to PVE APIs
- Added CSRF protection to write operations and APIs
- Disabled debug output in production mode
- Added centralized configuration handling
- Protected sensitive directories with access rules
- Added initial project security documentation
2026-07-22 02:23:34 +02:00

845 lines
30 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
date_default_timezone_set("Europe/Berlin");
require_once "config/config.php";
include("Data.class.php");
$Data = new Data($pdo);
/* ===============================
KALENDER HASH Hybrid Phase 1
=============================== */
function buildCalendarHash(array $termin): string {
$data = [
'start' => (int)$termin['startzeit'],
'end' => (int)$termin['endzeit'],
'title' => trim((string)$termin['location']),
'desc' => trim((string)$termin['beschreibung']),
];
return hash('sha256', json_encode($data));
}
$statusMap = [
"0" => "Offen",
"1" => "Bereit",
"2" => "Erledigt",
"3" => "Storniert"
];
require_once "config/session.php";
if (!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true) {
header("location: index.php");
exit;
}
if(isset($_GET["id"])){
$id = $_GET["id"];
}
if(isset($_POST["id"])){
$id = $_POST["id"];
}
/* ===============================
TERMIN LADEN (für Kalenderstatus)
=============================== */
$stmt = $pdo->prepare("SELECT * FROM termine WHERE id = :id");
$stmt->execute([':id' => $id]);
$termin = $stmt->fetch(PDO::FETCH_ASSOC);
// ===============================
// RECHNUNG ZU DIESEM TERMIN?
// ===============================
$invoice = null;
try {
$stInv = $pdo->prepare("SELECT id, invoice_no FROM invoices WHERE termine_id = :tid ORDER BY id DESC LIMIT 1");
$stInv->execute([':tid' => $id]);
$invoice = $stInv->fetch(PDO::FETCH_ASSOC) ?: null;
} catch (Throwable $e) {
// Falls Tabellen noch nicht existieren oder so: einfach ignorieren
$invoice = null;
}
/* ===============================
KALENDER STATUS BERECHNEN
=============================== */
$currentHash = buildCalendarHash($termin);
if (empty($termin['calendar_event_id'])) {
$calendarStatus = 'missing'; // 🔴
} elseif ($termin['calendar_hash'] !== $currentHash) {
$calendarStatus = 'outdated'; // 🟡
} else {
$calendarStatus = 'ok'; // 🟢
}
$per_page = 5; // Anzahl der Changelog-Einträge pro Seite
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
if ($page < 1) $page = 1;
$offset = ($page - 1) * $per_page;
// Gesamtanzahl Einträge
$count_sql = $pdo->prepare("SELECT COUNT(*) FROM changes WHERE termin_id = :id");
$count_sql->execute([':id' => $id]);
$total_entries = $count_sql->fetchColumn();
$total_pages = ceil($total_entries / $per_page);
// Daten holen
$sql = $pdo->prepare("
SELECT * FROM changes
WHERE termin_id = :id
ORDER BY timestamp DESC
LIMIT :limit OFFSET :offset
");
$sql->bindValue(':id', $id, PDO::PARAM_INT);
$sql->bindValue(':limit', $per_page, PDO::PARAM_INT);
$sql->bindValue(':offset', $offset, PDO::PARAM_INT);
$sql->execute();
$changes = $sql->fetchAll(PDO::FETCH_ASSOC);
if(isset($_POST['save'])){
// CSRF check
if (!isset($_POST['csrf_token']) || !isset($_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
die("CSRF validation failed");
}
if(isset($_POST['inputStartzeit_ts']) && !empty($_POST['inputStartzeit_ts'])){
$startzeit = $_POST['inputStartzeit_ts'];
if($Data->getTerminDataById($id, "startzeit") != $startzeit){
$Data->createChanges($id, $_SESSION["id"], "startzeit", $Data->getTerminDataById($id, "startzeit"), $startzeit);
$Data->changeTermin($id, "startzeit", $startzeit);
}
}
if(isset($_POST['inputEndzeit_ts']) && !empty($_POST['inputEndzeit_ts'])){
$endzeit = $_POST['inputEndzeit_ts'];
if($Data->getTerminDataById($id, "endzeit") != $endzeit){
$Data->createChanges($id, $_SESSION["id"], "endzeit", $Data->getTerminDataById($id, "endzeit"), $endzeit);
$Data->changeTermin($id, "endzeit", $endzeit);
}
}
if(isset($_POST['inputLocation'])){
$location = $_POST['inputLocation'];
if($Data->getTerminDataById($id, "location") != $location){
$Data->createChanges($id, $_SESSION["id"], "location", $Data->getTerminDataById($id, "location"), $location);
$Data->changeTermin($id, "location", $location);
}
}
if(isset($_POST['inputGage'])){
$gage = $_POST['inputGage'];
if($Data->getTerminDataById($id, "gage") != $gage){
$Data->createChanges($id, $_SESSION["id"], "gage", $Data->getTerminDataById($id, "gage"), $gage);
$Data->changeTermin($id, "gage", $gage);
}
}
if (isset($_POST['users'])) {
// Neue Daten
$userIDs_new = array_map('intval', $_POST['users']);
$json_new = json_encode($userIDs_new);
// Alte Daten holen und in Array umwandeln
$old_json = $Data->getTerminDataById($id, "anwesend");
$userIDs_old = json_decode($old_json, true);
// Falls alt NULL oder kein Array
if (!is_array($userIDs_old)) {
$userIDs_old = [];
}
// Arrays vergleichen unabhängig von Reihenfolge
if (array_diff($userIDs_old, $userIDs_new) || array_diff($userIDs_new, $userIDs_old)) {
// Changelog schreiben
$Data->createChanges(
$id,
$_SESSION["id"],
"anwesend",
json_encode($userIDs_old),
$json_new
);
// Speichern
$Data->changeTermin($id, "anwesend", $json_new);
}
}
if (isset($_POST['inputBeschreibung'])) {
$beschreibung = trim($_POST['inputBeschreibung']);
if ($Data->getTerminDataById($id, "beschreibung") != $beschreibung) {
$Data->createChanges($id, $_SESSION["id"], "beschreibung", "", "");
$Data->changeTermin($id, "beschreibung", $beschreibung);
}
}
if(isset($_POST['status'])){
$status = $_POST['status'];
if($Data->getTerminDataById($id, "status") != $status){
$Data->createChanges($id, $_SESSION["id"], "status", $Data->getTerminDataById($id, "status"), $status);
$Data->changeTermin($id, "status", $status);
if($status == "3"){
$Data->deleteCalendarEvent($id);
header("location: termin_show.php?id=" . $id);
exit;
}
}
}
$Data->updateCalendarEvent($id);
header("location: termin_show.php?id=" . $id);
exit;
}
include('navbar.php');
?>
<style>
/* ---------- CHANGES TABLE / TIMELINE OPTIMIERUNG ---------- */
.select2-container--classic .select2-selection--multiple {
border-radius: 8px;
padding: 6px;
border-color: #ced4da;
}
.select2-container--classic .select2-selection--multiple .select2-selection__choice {
background: #0d6efd;
color: white;
border: none;
padding: 3px 8px;
border-radius: 6px;
}
.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove {
color: white;
margin-right: 5px;
}
.changelog-table {
border-collapse: separate !important;
border-spacing: 0 12px !important; /* Abstand zwischen den Reihen */
}
.changelog-row {
background: #ffffff;
border-radius: 10px;
box-shadow: 0 2px 6px rgba(0,0,0,0.08);
}
.changelog-date {
font-weight: 600;
color: #444;
white-space: nowrap;
padding-top: 12px !important;
}
.change-entry {
padding: 14px 18px !important;
border-left: 4px solid #0d6efd;
background: #f8f9fa !important;
border-radius: 8px;
font-size: 15px;
line-height: 1.45;
}
.change-entry strong {
color: #000;
}
.change-old {
color: #dc3545 !important;
font-weight: 600;
}
.change-new {
color: #198754 !important;
font-weight: 700;
}
/* Mobile Version */
@media (max-width: 768px) {
.changelog-table {
border-spacing: 0 10px !important;
}
.change-entry {
font-size: 16px;
padding: 16px 16px !important;
}
.changelog-date {
font-size: 14px;
margin-bottom: 4px;
display: block;
}
}
/* MOBILE: Datum/Uhrzeit schmaler machen */
@media (max-width: 768px) {
/* Datum/Uhrzeit-Spalte schmaler */
.changelog-date {
width: 95px !important; /* vorher ~150180px */
font-size: 13px !important; /* leicht verkleinern */
text-align: left;
}
/* Änderungstext bekommt mehr Platz */
.change-entry {
font-size: 15px !important;
padding: 14px 14px !important;
}
/* Tabelle anpassen */
.changelog-table th:first-child {
width: 95px !important;
}
}
</style>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
<!-- Bootstrap Icons -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
<title>ANJUMA Dashboard</title>
</head>
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
<div class="container">
<div class="row justify-content-center">
<div class="col-12 col-md-10">
<form action="termin_show.php" method="post">
<div class="row g-3">
<!-- Startzeit (nicht änderbar) -->
<div class="col-md-6">
<label class="form-label fw-bold">Startzeit</label>
<div class="input-group">
<span class="input-group-text"><i class="bi bi-clock-fill"></i></span>
<input
type="datetime-local"
class="form-control"
id="inputStartzeit"
value="<?php echo date('Y-m-d\TH:i', $Data->getTerminDataById($id, 'startzeit')); ?>"
>
<input type="hidden" name="inputStartzeit_ts" id="inputStartzeit_ts">
</div>
<!-- Endzeit -->
<div class="col-md-6">
<label class="form-label fw-bold">Endzeit</label>
<div class="input-group">
<span class="input-group-text"><i class="bi bi-clock-fill"></i></span>
<input
type="datetime-local"
class="form-control"
id="inputEndzeit"
value="<?php echo date('Y-m-d\TH:i', $Data->getTerminDataById($id, 'endzeit')); ?>"
>
<input type="hidden" name="inputEndzeit_ts" id="inputEndzeit_ts">
</div>
<!-- Location -->
<div class="col-md-6">
<label class="form-label fw-bold">Location</label>
<div class="input-group">
<span class="input-group-text"><i class="bi bi-geo-alt"></i></span>
<input type="text" class="form-control" name="inputLocation"
value="<?php echo $Data->getTerminDataById($id, 'location') ?>">
</div>
<!-- Gage -->
<div class="col-md-6">
<label class="form-label fw-bold">Gage</label>
<div class="input-group">
<span class="input-group-text"><i class="bi bi-cash-stack"></i></span>
<input type="text" class="form-control" name="inputGage"
value="<?php echo $Data->getTerminDataById($id, 'gage') ?>">
</div>
<!-- Beschreibung -->
<div class="col-12">
<label class="form-label fw-bold">Beschreibung</label>
<div class="input-group">
<span class="input-group-text"><i class="bi bi-card-text"></i></span>
<textarea
class="form-control"
name="inputBeschreibung"
id="beschreibungField"
rows="1"
style="overflow:hidden; resize:none;"
><?php
echo htmlspecialchars($Data->getTerminDataById($id, 'beschreibung'));
?></textarea>
</div>
<!-- Beteiligte Personen -->
<div class="col-md-6">
<label class="form-label fw-bold">Anwesend</label>
<select class="form-select select2-users" name="users[]" multiple>
<?php
$userList = $pdo->query("SELECT id, name FROM users ORDER BY name")->fetchAll(PDO::FETCH_ASSOC);
$selectedUsers = json_decode($Data->getTerminDataById($id, "anwesend"), true) ?: [];
foreach ($userList as $u) {
$sel = in_array($u['id'], $selectedUsers) ? "selected" : "";
echo "<option value='{$u['id']}' $sel>{$u['name']}</option>";
}
?>
</select>
</div>
<!-- Status -->
<div class="btn-group my-4" role="group">
<label class="btn btn-primary">
<input type="radio" name="status" value="0" <?php if($Data->getTerminDataById($id, "status") == 0){ echo "checked"; } ?>> Offen
</label>
<label class="btn btn-warning">
<input type="radio" name="status" value="1" <?php if($Data->getTerminDataById($id, "status") == 1){ echo "checked"; } ?>> Bereit
</label>
<label class="btn btn-success">
<input type="radio" name="status" value="2" <?php if($Data->getTerminDataById($id, "status") == 2){ echo "checked"; } ?>> Abgeschlossen
</label>
<label class="btn btn-danger">
<input type="radio" name="status" value="3" <?php if($Data->getTerminDataById($id, "status") == 3){ echo "checked"; } ?>> Storniert
</label>
</div>
<input type="hidden" name="id" value="<?php echo $id ?>">
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token'] ?>">
<?php if (isset($_SESSION['role']) && $_SESSION['role'] === 'admin'): ?>
<button type="button" class="btn btn-danger ms-3"
data-bs-toggle="modal" data-bs-target="#deleteModal">
<i class="bi bi-trash"></i> Termin löschen
</button>
<?php endif; ?>
<button type="submit" name="save" class="btn btn-primary">
<i class="bi bi-save"></i> Speichern
</button>
<?php
$terminStatus = (int)$Data->getTerminDataById($id, "status");
?>
<?php if ($terminStatus === 2): ?>
<?php if ($invoice): ?>
<a class="btn btn-outline-success ms-3"
href="/api/invoice_pdf.php?id=<?= (int)$invoice['id'] ?>"
target="_blank">
<i class="bi bi-receipt"></i> PDF öffnen (<?= htmlspecialchars($invoice['invoice_no']) ?>)
</a>
<?php else: ?>
<button type="button" class="btn btn-success ms-3" id="btnInvoiceOpen">
<i class="bi bi-receipt"></i> Rechnung erstellen
</button>
<?php endif; ?>
<?php endif; ?>
<!-- ===============================
KALENDER STATUS (Hybrid Phase 1)
=============================== -->
<div class="mb-3">
<?php if ($calendarStatus === 'ok'): ?>
<span class="badge bg-success">
📅 Kalender aktuell
<?php if (!empty($termin['calendar_synced_at'])): ?>
<small>(<?= date('d.m.Y H:i', $termin['calendar_synced_at']) ?>)</small>
<?php endif; ?>
</span>
<?php elseif ($calendarStatus === 'outdated'): ?>
<span class="badge bg-warning text-dark">
📅 Kalender nicht aktuell
</span>
<?php else: ?>
<span class="badge bg-danger">
📅 Kein Kalender-Eintrag
</span>
<?php endif; ?>
</div>
<?php if ($calendarStatus === 'outdated'): ?>
<div class="alert alert-warning py-2">
⚠️ Der Termin wurde geändert, der Kalendereintrag ist nicht mehr aktuell.
</div>
<?php endif; ?>
</form>
</div>
</div>
<!-- Changelog -->
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-12 col-md-10 col-lg-10">
<table class="table changelog-table">
<thead class="table-dark">
<tr>
<th style="width: 180px;">Datum/Uhrzeit</th>
<th>Änderung</th>
</tr>
</thead>
<tbody>
<?php
foreach ($changes as $row) {
$datum = $Data->timestampToDate($row['timestamp']);
$name = $Data->getDataByID($row['user_id'], "name");
$feld = strtoupper($row['was']);
$alt = $row['alt'];
$neu = $row['neu'];
if ($row['was'] === 'anwesend'){
$alt = json_decode($row["alt"], true) ?: [];
$neu = json_decode($row["neu"], true) ?: [];
$alt = array_map(fn($uid) => $Data->getDataByID($uid, "name"), $alt);
$neu = array_map(fn($uid) => $Data->getDataByID($uid, "name"), $neu);
}
if ($row['was'] === 'status') {
$alt = $statusMap[$alt] ?? $alt;
$neu = $statusMap[$neu] ?? $neu;
}
if (in_array($row['was'], ['startzeit','endzeit','realendzeit'])) {
if ($alt > 0) $alt = date('d.m.Y H:i', $alt);
$neu = date('d.m.Y H:i', $neu);
}
echo "<tr class='changelog-row'>
<td class='changelog-date'>$datum</td>
<td>
<div class='change-entry'>";
if ($row['was'] === 'create') {
echo "<strong>$name</strong> hat diesen <strong>Termin</strong> erstellt!";
}
elseif ($row['was'] === 'anwesend') {
echo "<strong>$name</strong> hat <strong>$feld</strong> geändert von
<span class='change-old'>".implode(', ', $alt)."</span>
zu <span class='change-new'>".implode(', ', $neu)."</span>.";
}
elseif ($row['was'] === 'startzeit' && $row['alt'] == 0) {
echo "<strong>$name</strong> hat <strong>STARTZEIT</strong> geändert zu
<span class='change-new'>$neu</span>.";
}
elseif ($row['was'] === 'beschreibung') {
echo "<strong>$name</strong> hat die <strong>Beschreibung</strong> geändert!";
}else{
echo "<strong>$name</strong> hat <strong>$feld</strong> geändert von
<span class='change-old'>$alt</span>
zu <span class='change-new'>$neu</span>.";
}
echo " </div>
</td>
</tr>";
}
?>
</tbody>
</table>
<!-- Pagination -->
<nav>
<ul class="pagination justify-content-center">
<!-- Prev Button -->
<li class="page-item <?php if ($page <= 1) echo 'disabled'; ?>">
<a class="page-link" href="?id=<?php echo $id; ?>&page=<?php echo $page - 1; ?>">
&laquo; Zurück
</a>
</li>
<!-- Seitenzahlen -->
<?php for ($i = 1; $i <= $total_pages; $i++): ?>
<li class="page-item <?php if ($i == $page) echo 'active'; ?>">
<a class="page-link" href="?id=<?php echo $id; ?>&page=<?php echo $i; ?>">
<?php echo $i; ?>
</a>
</li>
<?php endfor; ?>
<!-- Next Button -->
<li class="page-item <?php if ($page >= $total_pages) echo 'disabled'; ?>">
<a class="page-link" href="?id=<?php echo $id; ?>&page=<?php echo $page + 1; ?>">
Weiter &raquo;
</a>
</li>
</ul>
</nav>
</div>
</div>
<!-- Invoice Modal -->
<div class="modal fade" id="invoiceModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<form class="modal-content" id="invoiceForm">
<div class="modal-header">
<h5 class="modal-title">Rechnung erstellen</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Schließen"></button>
</div>
<div class="modal-body">
<div class="mb-2">
<label class="form-label mb-1">Kunde / Firma *</label>
<input type="text" class="form-control" id="invCustomerName" required placeholder="z.B. Lug Lounge GmbH">
</div>
<div class="mb-2">
<label class="form-label mb-1">Straße & Nr.</label>
<input type="text" class="form-control" id="invStreet" placeholder="Musterstraße 1">
</div>
<div class="row g-2">
<div class="col-5">
<label class="form-label mb-1">PLZ</label>
<input type="text" class="form-control" id="invZip" placeholder="00000">
</div>
<div class="col-7">
<label class="form-label mb-1">Ort</label>
<input type="text" class="form-control" id="invCity" placeholder="Nienburg">
</div>
<div class="mt-2">
<label class="form-label mb-1">Land</label>
<input type="text" class="form-control" id="invCountry" value="DE">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Abbrechen</button>
<button type="submit" class="btn btn-success" id="btnCreateInvoice">Erstellen & PDF</button>
</div>
</form>
</div>
<!-- Delete Modal -->
<div class="modal fade" id="deleteModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header bg-danger text-white">
<h5 class="modal-title">Termin löschen?</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p>Bist du sicher, dass du diesen Termin löschen möchtest?</p>
<ul>
<li>Der Termin wird vollständig entfernt</li>
<li>Alle Änderungen (Changelog) werden gelöscht</li>
<li>Kalendereintrag wird entfernt</li>
</ul>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" data-bs-dismiss="modal">Abbrechen</button>
<form action="termin_delete.php" method="POST">
<input type="hidden" name="id" value="<?= $id ?>">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<button type="submit" class="btn btn-danger">
<i class="bi bi-trash"></i> Ja, löschen
</button>
</form>
</div>
</div>
<!-- MODAL: Termin existiert -->
<div class="modal fade" id="terminConflictModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header bg-danger text-white">
<h5 class="modal-title">Achtung Terminüberschneidung!</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p>Für dieses Datum existiert bereits mindestens ein Termin:</p>
<div id="terminConflictList" class="border rounded p-2 bg-light"></div>
<div class="modal-footer">
<button class="btn btn-secondary" data-bs-dismiss="modal">Okay</button>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script>
document.addEventListener("DOMContentLoaded", () => {
const startInput = document.getElementById("inputStartzeit");
startInput.addEventListener("change", async () => {
if (!startInput.value) return;
// ISO → Timestamp
const ts = Math.floor(new Date(startInput.value).getTime() / 1000);
const res = await fetch(`check_date.php?ts=${ts}`);
const data = await res.json();
if (Array.isArray(data) && data.length > 0) {
let html = "";
data.forEach(t => {
html += `
<div class="p-2 mb-2 bg-white rounded border">
<strong>Termin #${t.id}</strong><br>
Start: ${new Date(t.startzeit * 1000).toLocaleString()}<br>
Location: ${t.location}
</div>
`;
});
document.getElementById("terminConflictList").innerHTML = html;
// Modal anzeigen
const myModal = new bootstrap.Modal(document.getElementById("terminConflictModal"));
myModal.show();
}
});
});
document.addEventListener("DOMContentLoaded", function() {
// Select2 aktivieren
$('.select2-users').select2({
placeholder: "Anwesende auswählen…",
allowClear: true,
width: '100%',
theme: "classic"
});
});
function convertToTimestamp(idInput, idHidden) {
const input = document.getElementById(idInput);
const hidden = document.getElementById(idHidden);
if (input && hidden && input.value) {
const ts = Math.floor(new Date(input.value).getTime() / 1000);
hidden.value = ts;
}
}
document.addEventListener("DOMContentLoaded", () => {
const startzeit = document.getElementById("inputStartzeit");
const endzeit = document.getElementById("inputEndzeit");
const real = document.getElementById("inputRealEndzeit");
if (endzeit) {
endzeit.addEventListener("change", () => {
convertToTimestamp("inputEndzeit", "inputEndzeit_ts");
});
}
if (real) {
real.addEventListener("change", () => {
convertToTimestamp("inputRealEndzeit", "inputRealEndzeit_ts");
});
}
if (startzeit) {
startzeit.addEventListener("change", () => {
convertToTimestamp("inputStartzeit", "inputStartzeit_ts");
});
}
});
function autoResizeTextarea(el) {
el.style.height = 'auto';
el.style.height = (el.scrollHeight) + 'px';
}
document.addEventListener("DOMContentLoaded", function() {
const textarea = document.getElementById("beschreibungField");
if (textarea) {
// Beim Laden automatisch anpassen
autoResizeTextarea(textarea);
// Beim Eingeben automatisch anpassen
textarea.addEventListener('input', function() {
autoResizeTextarea(textarea);
});
}
});
// === Rechnung erstellen (Termin Show) ===
const API_INVOICE_CREATE = '/api/invoice_create_from_termin.php';
const API_INVOICE_PDF = '/api/invoice_pdf.php?id=';
const terminIdForInvoice = <?= (int)$id ?>;
document.addEventListener("DOMContentLoaded", () => {
const btnOpen = document.getElementById("btnInvoiceOpen");
if (!btnOpen) return;
const invoiceModalEl = document.getElementById('invoiceModal');
const invoiceModal = new bootstrap.Modal(invoiceModalEl);
btnOpen.addEventListener('click', () => {
// reset
document.getElementById('invCustomerName').value = '';
document.getElementById('invStreet').value = '';
Now I'll add the CSRF validation to `termin_show.php`. Let me make the edits step by step:
<edit_file>
<path>d:/anjuma-dashboard/termin_show.php</path>
<content>
<<<<<<< SEARCH
if(isset($_POST['save'])){
if(isset($_POST['inputStartzeit_ts']) && !empty($_POST['inputStartzeit_ts'])){
=======
if(isset($_POST['save'])){
// CSRF check
if (!isset($_POST['csrf_token']) || !isset($_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
die("CSRF validation failed");
}
if(isset($_POST['inputStartzeit_ts']) && !empty($_POST['inputStartzeit_ts'])){