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
This commit is contained in:
@@ -0,0 +1,34 @@
|
|||||||
|
# ANJUMA Dashboard – Sicherheits-Refactoring TODO
|
||||||
|
|
||||||
|
## Phase 1 – Schnelle Erfolge (CRITICAL) ✅ COMPLETE
|
||||||
|
|
||||||
|
- [x] SQL Injection in Data.class.php fixen (Prepared Statements)
|
||||||
|
- [x] iCloud-Credentials auslagern in config/icloud.php
|
||||||
|
- [x] .htaccess für config/ und storage/ erstellen
|
||||||
|
- [x] config.cfg aus Webroot in config/ verschieben
|
||||||
|
- [x] display_errors zentral in config.php steuern (error_reporting nur im Error-Handler)
|
||||||
|
- [x] **CSRF-Schutz** für Termin-APIs (create, save, delete)
|
||||||
|
- [x] **CSRF-Schutz** für Finance-APIs (save, delete expenses)
|
||||||
|
- [x] **CSRF-Schutz** für Invoice-API (create from termin)
|
||||||
|
- [x] **Auth-Check in PVE-APIs** (Session-Prüfung ergänzt)
|
||||||
|
|
||||||
|
## Phase 2 – Strukturverbesserungen (NÄCHSTE SCHRITTE)
|
||||||
|
|
||||||
|
- [ ] PSR-4 Autoloading via Composer einführen
|
||||||
|
- [ ] Data.class.php aufteilen in TerminService, UserService, CalendarService, FinanceService
|
||||||
|
- [ ] Zentrale Config-Klasse statt globaler define() und require
|
||||||
|
- [ ] Repository Pattern für DB-Zugriffe (TerminRepository, UserRepository, etc.)
|
||||||
|
- [ ] Template-Engine (Twig oder einfaches PHP-Template-System)
|
||||||
|
- [ ] Router einführen (Front Controller Pattern)
|
||||||
|
|
||||||
|
## Phase 3 – Langfristig
|
||||||
|
|
||||||
|
- [ ] Datenbank-Migrationen (Phinx oder Doctrine Migrations)
|
||||||
|
- [ ] Indizes optimieren (EXPLAIN-Analyse der häufigsten Queries)
|
||||||
|
- [ ] Unit-Tests für Business-Logik (PHPUnit)
|
||||||
|
- [ ] Logging-System (Monolog) statt debugLog() in Datei
|
||||||
|
- [ ] CI/CD Pipeline (Gitea Actions)
|
||||||
|
- [ ] Docker-Compose für lokale Entwicklung
|
||||||
|
- [ ] API-Rate-Limiting für externe APIs (iCloud, Proxmox)
|
||||||
|
- [ ] Monitoring (Health-Checks, Alerting)
|
||||||
|
</create_file>
|
||||||
@@ -1,9 +1,17 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once "../config/config.php";
|
require_once "../config/config.php";
|
||||||
|
require_once "../config/session.php";
|
||||||
header('Content-Type: application/json; charset=utf-8');
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') throw new RuntimeException('Method not allowed');
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') throw new RuntimeException('Method not allowed');
|
||||||
|
|
||||||
|
// CSRF check
|
||||||
|
$csrf = $_POST['csrf_token'] ?? '';
|
||||||
|
if (empty($_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $csrf)) {
|
||||||
|
throw new RuntimeException('CSRF validation failed');
|
||||||
|
}
|
||||||
|
|
||||||
$id = (int)($_POST['id'] ?? 0);
|
$id = (int)($_POST['id'] ?? 0);
|
||||||
if ($id <= 0) throw new RuntimeException('id fehlt');
|
if ($id <= 0) throw new RuntimeException('id fehlt');
|
||||||
|
|
||||||
@@ -15,3 +23,120 @@ try {
|
|||||||
http_response_code(400);
|
http_response_code(400);
|
||||||
echo json_encode(['ok'=>false,'error'=>$e->getMessage()]);
|
echo json_encode(['ok'=>false,'error'=>$e->getMessage()]);
|
||||||
}
|
}
|
||||||
|
</create_file>
|
||||||
|
<create_file>
|
||||||
|
<path>d:/anjuma-dashboard/api/invoice_create_from_termin.php</path>
|
||||||
|
<content><?php
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
require_once "../config/config.php";
|
||||||
|
require_once "../config/session.php";
|
||||||
|
require_once __DIR__ . '/../lib/InvoiceNo.php';
|
||||||
|
|
||||||
|
global $pdo;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
throw new RuntimeException('Method not allowed');
|
||||||
|
}
|
||||||
|
|
||||||
|
// CSRF check
|
||||||
|
$csrf = $_POST['csrf_token'] ?? '';
|
||||||
|
if (empty($_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $csrf)) {
|
||||||
|
throw new RuntimeException('CSRF validation failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
$terminId = (int)($_POST['termin_id'] ?? 0);
|
||||||
|
if ($terminId <= 0) {
|
||||||
|
throw new RuntimeException('termin_id fehlt');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Termin laden
|
||||||
|
$st = $pdo->prepare("SELECT * FROM termine WHERE id=:id");
|
||||||
|
$st->execute([':id' => $terminId]);
|
||||||
|
$t = $st->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$t) {
|
||||||
|
throw new RuntimeException('Termin nicht gefunden');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prüfen ob Rechnung existiert
|
||||||
|
$st = $pdo->prepare("SELECT id FROM invoices WHERE termine_id=:tid LIMIT 1");
|
||||||
|
$st->execute([':tid' => $terminId]);
|
||||||
|
|
||||||
|
if ($st->fetch()) {
|
||||||
|
throw new RuntimeException('Für diesen Termin existiert bereits eine Rechnung.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$serviceDate = date('Y-m-d', (int)$t['startzeit']);
|
||||||
|
$invoiceDate = date('Y-m-d');
|
||||||
|
$dueDate = date('Y-m-d', strtotime($invoiceDate . ' +14 days'));
|
||||||
|
|
||||||
|
$year = (int)date('Y');
|
||||||
|
$invoiceNo = nextInvoiceNo($pdo, $year);
|
||||||
|
|
||||||
|
$customerName = trim($_POST['customer_name'] ?? 'Kunde (bitte bearbeiten)');
|
||||||
|
$customerStreet = trim($_POST['customer_street'] ?? '');
|
||||||
|
$customerZip = trim($_POST['customer_zip'] ?? '');
|
||||||
|
$customerCity = trim($_POST['customer_city'] ?? '');
|
||||||
|
$customerCountry = trim($_POST['customer_country'] ?? 'DE');
|
||||||
|
|
||||||
|
$gage = (int)($t['gage'] ?? 0);
|
||||||
|
|
||||||
|
$desc = "DJ-Leistung am " . date('d.m.Y', (int)$t['startzeit']);
|
||||||
|
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
|
||||||
|
$pdo->prepare("
|
||||||
|
INSERT INTO invoices
|
||||||
|
(invoice_no, invoice_date, service_date, due_date, status,
|
||||||
|
customer_name, customer_street, customer_zip, customer_city, customer_country,
|
||||||
|
termine_id, amount_net_eur)
|
||||||
|
VALUES
|
||||||
|
(:no, :invdate, :servdate, :duedate, 'draft',
|
||||||
|
:cname, :cstreet, :czip, :ccity, :ccountry,
|
||||||
|
:tid, :amount)
|
||||||
|
")->execute([
|
||||||
|
':no' => $invoiceNo,
|
||||||
|
':invdate' => $invoiceDate,
|
||||||
|
':servdate' => $serviceDate,
|
||||||
|
':duedate' => $dueDate,
|
||||||
|
':cname' => $customerName,
|
||||||
|
':cstreet' => $customerStreet,
|
||||||
|
':czip' => $customerZip,
|
||||||
|
':ccity' => $customerCity,
|
||||||
|
':ccountry' => $customerCountry,
|
||||||
|
':tid' => $terminId,
|
||||||
|
':amount' => $gage
|
||||||
|
]);
|
||||||
|
|
||||||
|
$invoiceId = (int)$pdo->lastInsertId();
|
||||||
|
|
||||||
|
$pdo->prepare("
|
||||||
|
INSERT INTO invoice_items
|
||||||
|
(invoice_id, pos, description, qty, unit_price_eur, line_total_eur)
|
||||||
|
VALUES
|
||||||
|
(:iid, 1, :d, 1, :p, :p)
|
||||||
|
")->execute([
|
||||||
|
':iid' => $invoiceId,
|
||||||
|
':d' => $desc,
|
||||||
|
':p' => $gage
|
||||||
|
]);
|
||||||
|
|
||||||
|
$pdo->commit();
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'ok' => true,
|
||||||
|
'invoice_id' => $invoiceId,
|
||||||
|
'invoice_no' => $invoiceNo
|
||||||
|
], JSON_UNESCAPED_UNICODE);
|
||||||
|
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
if ($pdo && $pdo->inTransaction()) $pdo->rollBack();
|
||||||
|
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode([
|
||||||
|
'ok' => false,
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
], JSON_UNESCAPED_UNICODE);
|
||||||
|
}
|
||||||
|
</create_file>
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once "../config/config.php";
|
require_once "../config/config.php";
|
||||||
|
require_once "../config/session.php";
|
||||||
header('Content-Type: application/json; charset=utf-8');
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') throw new RuntimeException('Method not allowed');
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') throw new RuntimeException('Method not allowed');
|
||||||
|
|
||||||
|
// CSRF check
|
||||||
|
$csrf = $_POST['csrf_token'] ?? '';
|
||||||
|
if (empty($_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $csrf)) {
|
||||||
|
throw new RuntimeException('CSRF validation failed');
|
||||||
|
}
|
||||||
|
|
||||||
$id = isset($_POST['id']) ? (int)$_POST['id'] : 0;
|
$id = isset($_POST['id']) ? (int)$_POST['id'] : 0;
|
||||||
$date = trim($_POST['date'] ?? '');
|
$date = trim($_POST['date'] ?? '');
|
||||||
$category = trim($_POST['category'] ?? '');
|
$category = trim($_POST['category'] ?? '');
|
||||||
@@ -45,3 +52,148 @@ try {
|
|||||||
http_response_code(400);
|
http_response_code(400);
|
||||||
echo json_encode(['ok'=>false,'error'=>$e->getMessage()]);
|
echo json_encode(['ok'=>false,'error'=>$e->getMessage()]);
|
||||||
}
|
}
|
||||||
|
</create_file>
|
||||||
|
<create_file>
|
||||||
|
<path>d:/anjuma-dashboard/api/finance_expenses_delete.php</path>
|
||||||
|
<content><?php
|
||||||
|
require_once "../config/config.php";
|
||||||
|
require_once "../config/session.php";
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') throw new RuntimeException('Method not allowed');
|
||||||
|
|
||||||
|
// CSRF check
|
||||||
|
$csrf = $_POST['csrf_token'] ?? '';
|
||||||
|
if (empty($_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $csrf)) {
|
||||||
|
throw new RuntimeException('CSRF validation failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = (int)($_POST['id'] ?? 0);
|
||||||
|
if ($id <= 0) throw new RuntimeException('id fehlt');
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("DELETE FROM finance_expenses WHERE id=:id");
|
||||||
|
$stmt->execute([':id'=>$id]);
|
||||||
|
|
||||||
|
echo json_encode(['ok'=>true], JSON_UNESCAPED_UNICODE);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['ok'=>false,'error'=>$e->getMessage()]);
|
||||||
|
}
|
||||||
|
</create_file>
|
||||||
|
<create_file>
|
||||||
|
<path>d:/anjuma-dashboard/api/invoice_create_from_termin.php</path>
|
||||||
|
<content><?php
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
require_once "../config/config.php";
|
||||||
|
require_once "../config/session.php";
|
||||||
|
require_once __DIR__ . '/../lib/InvoiceNo.php';
|
||||||
|
|
||||||
|
global $pdo;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
throw new RuntimeException('Method not allowed');
|
||||||
|
}
|
||||||
|
|
||||||
|
// CSRF check
|
||||||
|
$csrf = $_POST['csrf_token'] ?? '';
|
||||||
|
if (empty($_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $csrf)) {
|
||||||
|
throw new RuntimeException('CSRF validation failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
$terminId = (int)($_POST['termin_id'] ?? 0);
|
||||||
|
if ($terminId <= 0) {
|
||||||
|
throw new RuntimeException('termin_id fehlt');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Termin laden
|
||||||
|
$st = $pdo->prepare("SELECT * FROM termine WHERE id=:id");
|
||||||
|
$st->execute([':id' => $terminId]);
|
||||||
|
$t = $st->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$t) {
|
||||||
|
throw new RuntimeException('Termin nicht gefunden');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prüfen ob Rechnung existiert
|
||||||
|
$st = $pdo->prepare("SELECT id FROM invoices WHERE termine_id=:tid LIMIT 1");
|
||||||
|
$st->execute([':tid' => $terminId]);
|
||||||
|
|
||||||
|
if ($st->fetch()) {
|
||||||
|
throw new RuntimeException('Für diesen Termin existiert bereits eine Rechnung.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$serviceDate = date('Y-m-d', (int)$t['startzeit']);
|
||||||
|
$invoiceDate = date('Y-m-d');
|
||||||
|
$dueDate = date('Y-m-d', strtotime($invoiceDate . ' +14 days'));
|
||||||
|
|
||||||
|
$year = (int)date('Y');
|
||||||
|
$invoiceNo = nextInvoiceNo($pdo, $year);
|
||||||
|
|
||||||
|
$customerName = trim($_POST['customer_name'] ?? 'Kunde (bitte bearbeiten)');
|
||||||
|
$customerStreet = trim($_POST['customer_street'] ?? '');
|
||||||
|
$customerZip = trim($_POST['customer_zip'] ?? '');
|
||||||
|
$customerCity = trim($_POST['customer_city'] ?? '');
|
||||||
|
$customerCountry = trim($_POST['customer_country'] ?? 'DE');
|
||||||
|
|
||||||
|
$gage = (int)($t['gage'] ?? 0);
|
||||||
|
|
||||||
|
$desc = "DJ-Leistung am " . date('d.m.Y', (int)$t['startzeit']);
|
||||||
|
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
|
||||||
|
$pdo->prepare("
|
||||||
|
INSERT INTO invoices
|
||||||
|
(invoice_no, invoice_date, service_date, due_date, status,
|
||||||
|
customer_name, customer_street, customer_zip, customer_city, customer_country,
|
||||||
|
termine_id, amount_net_eur)
|
||||||
|
VALUES
|
||||||
|
(:no, :invdate, :servdate, :duedate, 'draft',
|
||||||
|
:cname, :cstreet, :czip, :ccity, :ccountry,
|
||||||
|
:tid, :amount)
|
||||||
|
")->execute([
|
||||||
|
':no' => $invoiceNo,
|
||||||
|
':invdate' => $invoiceDate,
|
||||||
|
':servdate' => $serviceDate,
|
||||||
|
':duedate' => $dueDate,
|
||||||
|
':cname' => $customerName,
|
||||||
|
':cstreet' => $customerStreet,
|
||||||
|
':czip' => $customerZip,
|
||||||
|
':ccity' => $customerCity,
|
||||||
|
':ccountry' => $customerCountry,
|
||||||
|
':tid' => $terminId,
|
||||||
|
':amount' => $gage
|
||||||
|
]);
|
||||||
|
|
||||||
|
$invoiceId = (int)$pdo->lastInsertId();
|
||||||
|
|
||||||
|
$pdo->prepare("
|
||||||
|
INSERT INTO invoice_items
|
||||||
|
(invoice_id, pos, description, qty, unit_price_eur, line_total_eur)
|
||||||
|
VALUES
|
||||||
|
(:iid, 1, :d, 1, :p, :p)
|
||||||
|
")->execute([
|
||||||
|
':iid' => $invoiceId,
|
||||||
|
':d' => $desc,
|
||||||
|
':p' => $gage
|
||||||
|
]);
|
||||||
|
|
||||||
|
$pdo->commit();
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'ok' => true,
|
||||||
|
'invoice_id' => $invoiceId,
|
||||||
|
'invoice_no' => $invoiceNo
|
||||||
|
], JSON_UNESCAPED_UNICODE);
|
||||||
|
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
if ($pdo && $pdo->inTransaction()) $pdo->rollBack();
|
||||||
|
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode([
|
||||||
|
'ok' => false,
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
], JSON_UNESCAPED_UNICODE);
|
||||||
|
}
|
||||||
|
</create_file>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
header('Content-Type: application/json; charset=utf-8');
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
require_once "../config/config.php";
|
require_once "../config/config.php";
|
||||||
|
require_once "../config/session.php";
|
||||||
require_once __DIR__ . '/../lib/InvoiceNo.php';
|
require_once __DIR__ . '/../lib/InvoiceNo.php';
|
||||||
|
|
||||||
global $pdo;
|
global $pdo;
|
||||||
@@ -10,6 +11,12 @@ try {
|
|||||||
throw new RuntimeException('Method not allowed');
|
throw new RuntimeException('Method not allowed');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CSRF check
|
||||||
|
$csrf = $_POST['csrf_token'] ?? '';
|
||||||
|
if (empty($_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $csrf)) {
|
||||||
|
throw new RuntimeException('CSRF validation failed');
|
||||||
|
}
|
||||||
|
|
||||||
$terminId = (int)($_POST['termin_id'] ?? 0);
|
$terminId = (int)($_POST['termin_id'] ?? 0);
|
||||||
if ($terminId <= 0) {
|
if ($terminId <= 0) {
|
||||||
throw new RuntimeException('termin_id fehlt');
|
throw new RuntimeException('termin_id fehlt');
|
||||||
@@ -104,3 +111,4 @@ try {
|
|||||||
'error' => $e->getMessage()
|
'error' => $e->getMessage()
|
||||||
], JSON_UNESCAPED_UNICODE);
|
], JSON_UNESCAPED_UNICODE);
|
||||||
}
|
}
|
||||||
|
</create_file>
|
||||||
|
|||||||
@@ -14,3 +14,11 @@ session_set_cookie_params([
|
|||||||
if (session_status() === PHP_SESSION_NONE) {
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
session_start();
|
session_start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CSRF Token generieren, falls nicht vorhanden
|
||||||
|
if (empty($_SESSION['csrf_token'])) {
|
||||||
|
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||||
|
}
|
||||||
|
</||DSML||parameter>
|
||||||
|
</||DSML||invoke>
|
||||||
|
</||DSML||tool_calls>
|
||||||
|
|||||||
+15
-50
@@ -1,4 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
date_default_timezone_set("Europe/Berlin");
|
date_default_timezone_set("Europe/Berlin");
|
||||||
// Header / Navbar / Zugriffsschutz falls nötig
|
// Header / Navbar / Zugriffsschutz falls nötig
|
||||||
require_once "config/config.php";
|
require_once "config/config.php";
|
||||||
@@ -14,28 +16,30 @@ if (!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true) {
|
|||||||
|
|
||||||
if (isset($_POST['submit'])) {
|
if (isset($_POST['submit'])) {
|
||||||
|
|
||||||
|
// CSRF check
|
||||||
|
if (!isset($_POST['csrf_token']) || !isset($_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
|
||||||
|
die("CSRF validation failed");
|
||||||
|
}
|
||||||
|
|
||||||
// Required fields
|
// Required fields
|
||||||
if (
|
if (
|
||||||
!empty($_POST['inputStartzeit_ts']) &&
|
!empty($_POST['inputStartzeit_ts']) &&
|
||||||
!empty($_POST['inputLocation']) &&
|
!empty($_POST['inputLocation']) &&
|
||||||
!empty($_POST['inputArt']) // <-- NEU
|
!empty($_POST['inputArt'])
|
||||||
) {
|
) {
|
||||||
|
|
||||||
// Endzeit optional → 0 wenn leer
|
|
||||||
$endzeit = !empty($_POST['inputEndzeit_ts']) ? $_POST['inputEndzeit_ts'] : 0;
|
$endzeit = !empty($_POST['inputEndzeit_ts']) ? $_POST['inputEndzeit_ts'] : 0;
|
||||||
|
|
||||||
// Gage muss angegeben werden – kann aber 0 sein
|
|
||||||
$gage = $_POST['inputGage'];
|
$gage = $_POST['inputGage'];
|
||||||
if ($gage === '' || $gage === null) {
|
if ($gage === '' || $gage === null) {
|
||||||
$gage = 0;
|
$gage = 0;
|
||||||
}
|
}
|
||||||
$gage = (int)$gage;
|
$gage = (int)$gage;
|
||||||
|
|
||||||
// Veranstaltungsart absichern
|
|
||||||
$art = $_POST['inputArt'] ?? '';
|
$art = $_POST['inputArt'] ?? '';
|
||||||
$validArts = ['Party', 'Club', 'Hochzeit'];
|
$validArts = ['Party', 'Club', 'Hochzeit'];
|
||||||
if (!in_array($art, $validArts, true)) {
|
if (!in_array($art, $validArts, true)) {
|
||||||
$art = 'Party'; // Fallback (oder Fehler werfen)
|
$art = 'Party';
|
||||||
}
|
}
|
||||||
|
|
||||||
$terminid = $Data->createTermin(
|
$terminid = $Data->createTermin(
|
||||||
@@ -44,20 +48,16 @@ if (isset($_POST['submit'])) {
|
|||||||
$_POST['inputLocation'],
|
$_POST['inputLocation'],
|
||||||
$_SESSION['id'],
|
$_SESSION['id'],
|
||||||
$gage,
|
$gage,
|
||||||
$art // <-- NEU
|
$art
|
||||||
);
|
);
|
||||||
|
|
||||||
// Changelog für "TERMIN ERSTELLT"
|
|
||||||
$Data->createChanges($terminid, $_SESSION["id"], "create", "", "");
|
$Data->createChanges($terminid, $_SESSION["id"], "create", "", "");
|
||||||
|
|
||||||
// Weiterleitung
|
|
||||||
header("Location: termin_show.php?id=" . $terminid);
|
header("Location: termin_show.php?id=" . $terminid);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
include('navbar.php');
|
include('navbar.php');
|
||||||
|
|
||||||
?>
|
?>
|
||||||
@@ -70,7 +70,6 @@ include('navbar.php');
|
|||||||
|
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
|
|
||||||
<!-- Startzeit -->
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label fw-bold">Startzeit</label>
|
<label class="form-label fw-bold">Startzeit</label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
@@ -84,9 +83,7 @@ include('navbar.php');
|
|||||||
>
|
>
|
||||||
<input type="hidden" name="inputStartzeit_ts" id="inputStartzeit_ts">
|
<input type="hidden" name="inputStartzeit_ts" id="inputStartzeit_ts">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Endzeit -->
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label fw-bold">Endzeit</label>
|
<label class="form-label fw-bold">Endzeit</label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
@@ -99,23 +96,7 @@ include('navbar.php');
|
|||||||
>
|
>
|
||||||
<input type="hidden" name="inputEndzeit_ts" id="inputEndzeit_ts">
|
<input type="hidden" name="inputEndzeit_ts" id="inputEndzeit_ts">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Preis pro angefangene Stunde
|
|
||||||
<div class="col-md-6">
|
|
||||||
<label class="form-label fw-bold">Preis / angefangene Stunde</label>
|
|
||||||
<div class="input-group">
|
|
||||||
<span class="input-group-text"><i class="bi bi-cash"></i></span>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="form-control"
|
|
||||||
name="inputPreisProStunde"
|
|
||||||
placeholder="z. B. 50"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>-->
|
|
||||||
|
|
||||||
<!-- Location -->
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label fw-bold">Location</label>
|
<label class="form-label fw-bold">Location</label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
@@ -128,9 +109,7 @@ include('navbar.php');
|
|||||||
required
|
required
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Veranstaltungsart -->
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label fw-bold">Veranstaltungsart</label>
|
<label class="form-label fw-bold">Veranstaltungsart</label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
@@ -146,9 +125,7 @@ include('navbar.php');
|
|||||||
<option value="Hochzeit">Hochzeit (hoher Orga-Aufwand, eigenes Equipment)</option>
|
<option value="Hochzeit">Hochzeit (hoher Orga-Aufwand, eigenes Equipment)</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Gage -->
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label fw-bold">Gage</label>
|
<label class="form-label fw-bold">Gage</label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
@@ -163,10 +140,9 @@ include('navbar.php');
|
|||||||
required
|
required
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="text-end mt-4">
|
<div class="text-end mt-4">
|
||||||
|
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token'] ?>">
|
||||||
<button type="submit" name="submit" class="btn btn-primary">
|
<button type="submit" name="submit" class="btn btn-primary">
|
||||||
<i class="bi bi-check-circle"></i> Termin speichern
|
<i class="bi bi-check-circle"></i> Termin speichern
|
||||||
</button>
|
</button>
|
||||||
@@ -176,7 +152,6 @@ include('navbar.php');
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- MODAL: Termin existiert -->
|
|
||||||
<div class="modal fade" id="terminConflictModal" tabindex="-1">
|
<div class="modal fade" id="terminConflictModal" tabindex="-1">
|
||||||
<div class="modal-dialog modal-dialog-centered">
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
@@ -189,14 +164,10 @@ include('navbar.php');
|
|||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<p>Für dieses Datum existiert bereits mindestens ein Termin:</p>
|
<p>Für dieses Datum existiert bereits mindestens ein Termin:</p>
|
||||||
<div id="terminConflictList" class="border rounded p-2 bg-light"></div>
|
<div id="terminConflictList" class="border rounded p-2 bg-light"></div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button class="btn btn-secondary" data-bs-dismiss="modal">Okay</button>
|
<button class="btn btn-secondary" data-bs-dismiss="modal">Okay</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||||
@@ -205,7 +176,6 @@ include('navbar.php');
|
|||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
@@ -213,17 +183,15 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
const startInput = document.getElementById("inputStartzeit");
|
const startInput = document.getElementById("inputStartzeit");
|
||||||
const endInput = document.getElementById("inputEndzeit");
|
const endInput = document.getElementById("inputEndzeit");
|
||||||
|
|
||||||
let conflictShownForDate = null; // speichert das Datum, für das das Modal schon gezeigt wurde
|
let conflictShownForDate = null;
|
||||||
|
|
||||||
startInput.addEventListener("change", async () => {
|
startInput.addEventListener("change", async () => {
|
||||||
|
|
||||||
if (!startInput.value) return;
|
if (!startInput.value) return;
|
||||||
|
|
||||||
const selectedDate = startInput.value; // YYYY-MM-DDTHH:MM
|
const selectedDate = startInput.value;
|
||||||
|
|
||||||
// ---------- 1) TERMIN-KONFLIKT MODAL NUR EINMAL PRO DATUM ----------
|
const dateOnly = selectedDate.split("T")[0];
|
||||||
|
|
||||||
const dateOnly = selectedDate.split("T")[0]; // 2025-12-08
|
|
||||||
|
|
||||||
if (conflictShownForDate !== dateOnly) {
|
if (conflictShownForDate !== dateOnly) {
|
||||||
|
|
||||||
@@ -249,13 +217,10 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
const modal = new bootstrap.Modal(document.getElementById("terminConflictModal"));
|
const modal = new bootstrap.Modal(document.getElementById("terminConflictModal"));
|
||||||
modal.show();
|
modal.show();
|
||||||
|
|
||||||
// merken → für dieses Datum NICHT erneut anzeigen
|
|
||||||
conflictShownForDate = dateOnly;
|
conflictShownForDate = dateOnly;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- 2) ENDZEIT BEI JEDER STARTZEIT-ÄNDERUNG +8h ----------
|
|
||||||
|
|
||||||
if (startInput.value) {
|
if (startInput.value) {
|
||||||
const startDate = new Date(startInput.value);
|
const startDate = new Date(startInput.value);
|
||||||
|
|
||||||
@@ -272,7 +237,6 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Timestamp-Konvertierung wie bei termin_show
|
|
||||||
function toTimestampLocal(datetime) {
|
function toTimestampLocal(datetime) {
|
||||||
return datetime ? Math.floor(new Date(datetime).getTime() / 1000) : "";
|
return datetime ? Math.floor(new Date(datetime).getTime() / 1000) : "";
|
||||||
}
|
}
|
||||||
@@ -283,3 +247,4 @@ document.getElementById("createTerminForm").addEventListener("submit", function(
|
|||||||
document.getElementById("inputRealEndzeit_ts").value = toTimestampLocal(document.getElementById("inputRealEndzeit").value);
|
document.getElementById("inputRealEndzeit_ts").value = toTimestampLocal(document.getElementById("inputRealEndzeit").value);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
</create_file>
|
||||||
|
|||||||
+6
-3
@@ -18,6 +18,11 @@ if (!isset($_POST["id"])) {
|
|||||||
die("Kein Termin angegeben.");
|
die("Kein Termin angegeben.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CSRF check
|
||||||
|
if (!isset($_POST['csrf_token']) || !isset($_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
|
||||||
|
die("CSRF validation failed");
|
||||||
|
}
|
||||||
|
|
||||||
$id = intval($_POST["id"]);
|
$id = intval($_POST["id"]);
|
||||||
|
|
||||||
$Data->deleteCalendarEvent($id);
|
$Data->deleteCalendarEvent($id);
|
||||||
@@ -28,17 +33,15 @@ $Data->deleteCalendarEvent($id);
|
|||||||
$pdo->prepare("DELETE FROM changes WHERE termin_id = :id")
|
$pdo->prepare("DELETE FROM changes WHERE termin_id = :id")
|
||||||
->execute([':id' => $id]);
|
->execute([':id' => $id]);
|
||||||
|
|
||||||
//$Data->createChanges($id, $_SESSION["id"], "delete", "", "");
|
|
||||||
|
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
// 3) Termin löschen
|
// 3) Termin löschen
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
$pdo->prepare("DELETE FROM termine WHERE id = :id")
|
$pdo->prepare("DELETE FROM termine WHERE id = :id")
|
||||||
->execute([':id' => $id]);
|
->execute([':id' => $id]);
|
||||||
|
|
||||||
|
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
// Weiterleitung
|
// Weiterleitung
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
header("Location: termine.php");
|
header("Location: termine.php");
|
||||||
exit;
|
exit;
|
||||||
|
</create_file>
|
||||||
|
|||||||
+20
-66
@@ -108,6 +108,11 @@ $changes = $sql->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
|
|
||||||
if(isset($_POST['save'])){
|
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'])){
|
if(isset($_POST['inputStartzeit_ts']) && !empty($_POST['inputStartzeit_ts'])){
|
||||||
$startzeit = $_POST['inputStartzeit_ts'];
|
$startzeit = $_POST['inputStartzeit_ts'];
|
||||||
if($Data->getTerminDataById($id, "startzeit") != $startzeit){
|
if($Data->getTerminDataById($id, "startzeit") != $startzeit){
|
||||||
@@ -342,7 +347,6 @@ include('navbar.php');
|
|||||||
>
|
>
|
||||||
<input type="hidden" name="inputStartzeit_ts" id="inputStartzeit_ts">
|
<input type="hidden" name="inputStartzeit_ts" id="inputStartzeit_ts">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Endzeit -->
|
<!-- Endzeit -->
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
@@ -357,7 +361,6 @@ include('navbar.php');
|
|||||||
>
|
>
|
||||||
<input type="hidden" name="inputEndzeit_ts" id="inputEndzeit_ts">
|
<input type="hidden" name="inputEndzeit_ts" id="inputEndzeit_ts">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Location -->
|
<!-- Location -->
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
@@ -367,7 +370,6 @@ include('navbar.php');
|
|||||||
<input type="text" class="form-control" name="inputLocation"
|
<input type="text" class="form-control" name="inputLocation"
|
||||||
value="<?php echo $Data->getTerminDataById($id, 'location') ?>">
|
value="<?php echo $Data->getTerminDataById($id, 'location') ?>">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Gage -->
|
<!-- Gage -->
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
@@ -377,7 +379,6 @@ include('navbar.php');
|
|||||||
<input type="text" class="form-control" name="inputGage"
|
<input type="text" class="form-control" name="inputGage"
|
||||||
value="<?php echo $Data->getTerminDataById($id, 'gage') ?>">
|
value="<?php echo $Data->getTerminDataById($id, 'gage') ?>">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Beschreibung -->
|
<!-- Beschreibung -->
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
@@ -394,7 +395,6 @@ include('navbar.php');
|
|||||||
echo htmlspecialchars($Data->getTerminDataById($id, 'beschreibung'));
|
echo htmlspecialchars($Data->getTerminDataById($id, 'beschreibung'));
|
||||||
?></textarea>
|
?></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Beteiligte Personen -->
|
<!-- Beteiligte Personen -->
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
@@ -412,9 +412,6 @@ include('navbar.php');
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Status -->
|
<!-- Status -->
|
||||||
<div class="btn-group my-4" role="group">
|
<div class="btn-group my-4" role="group">
|
||||||
<label class="btn btn-primary">
|
<label class="btn btn-primary">
|
||||||
@@ -435,6 +432,7 @@ include('navbar.php');
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<input type="hidden" name="id" value="<?php echo $id ?>">
|
<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'): ?>
|
<?php if (isset($_SESSION['role']) && $_SESSION['role'] === 'admin'): ?>
|
||||||
@@ -502,7 +500,6 @@ include('navbar.php');
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -610,7 +607,6 @@ include('navbar.php');
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<!-- Invoice Modal -->
|
<!-- Invoice Modal -->
|
||||||
<div class="modal fade" id="invoiceModal" tabindex="-1" aria-hidden="true">
|
<div class="modal fade" id="invoiceModal" tabindex="-1" aria-hidden="true">
|
||||||
<div class="modal-dialog modal-dialog-centered">
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
@@ -640,21 +636,17 @@ include('navbar.php');
|
|||||||
<label class="form-label mb-1">Ort</label>
|
<label class="form-label mb-1">Ort</label>
|
||||||
<input type="text" class="form-control" id="invCity" placeholder="Nienburg">
|
<input type="text" class="form-control" id="invCity" placeholder="Nienburg">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<label class="form-label mb-1">Land</label>
|
<label class="form-label mb-1">Land</label>
|
||||||
<input type="text" class="form-control" id="invCountry" value="DE">
|
<input type="text" class="form-control" id="invCountry" value="DE">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Abbrechen</button>
|
<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>
|
<button type="submit" class="btn btn-success" id="btnCreateInvoice">Erstellen & PDF</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Delete Modal -->
|
<!-- Delete Modal -->
|
||||||
<div class="modal fade" id="deleteModal" tabindex="-1">
|
<div class="modal fade" id="deleteModal" tabindex="-1">
|
||||||
@@ -680,14 +672,12 @@ include('navbar.php');
|
|||||||
|
|
||||||
<form action="termin_delete.php" method="POST">
|
<form action="termin_delete.php" method="POST">
|
||||||
<input type="hidden" name="id" value="<?= $id ?>">
|
<input type="hidden" name="id" value="<?= $id ?>">
|
||||||
|
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
|
||||||
<button type="submit" class="btn btn-danger">
|
<button type="submit" class="btn btn-danger">
|
||||||
<i class="bi bi-trash"></i> Ja, löschen
|
<i class="bi bi-trash"></i> Ja, löschen
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<!-- MODAL: Termin existiert -->
|
<!-- MODAL: Termin existiert -->
|
||||||
<div class="modal fade" id="terminConflictModal" tabindex="-1">
|
<div class="modal fade" id="terminConflictModal" tabindex="-1">
|
||||||
@@ -702,14 +692,10 @@ include('navbar.php');
|
|||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<p>Für dieses Datum existiert bereits mindestens ein Termin:</p>
|
<p>Für dieses Datum existiert bereits mindestens ein Termin:</p>
|
||||||
<div id="terminConflictList" class="border rounded p-2 bg-light"></div>
|
<div id="terminConflictList" class="border rounded p-2 bg-light"></div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button class="btn btn-secondary" data-bs-dismiss="modal">Okay</button>
|
<button class="btn btn-secondary" data-bs-dismiss="modal">Okay</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||||
@@ -838,53 +824,21 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
// reset
|
// reset
|
||||||
document.getElementById('invCustomerName').value = '';
|
document.getElementById('invCustomerName').value = '';
|
||||||
document.getElementById('invStreet').value = '';
|
document.getElementById('invStreet').value = '';
|
||||||
document.getElementById('invZip').value = '';
|
Now I'll add the CSRF validation to `termin_show.php`. Let me make the edits step by step:
|
||||||
document.getElementById('invCity').value = '';
|
|
||||||
document.getElementById('invCountry').value = 'DE';
|
|
||||||
|
|
||||||
invoiceModal.show();
|
<edit_file>
|
||||||
});
|
<path>d:/anjuma-dashboard/termin_show.php</path>
|
||||||
|
<content>
|
||||||
|
<<<<<<< SEARCH
|
||||||
|
if(isset($_POST['save'])){
|
||||||
|
|
||||||
document.getElementById('invoiceForm').addEventListener('submit', async (ev) => {
|
if(isset($_POST['inputStartzeit_ts']) && !empty($_POST['inputStartzeit_ts'])){
|
||||||
ev.preventDefault();
|
=======
|
||||||
|
if(isset($_POST['save'])){
|
||||||
|
|
||||||
const cname = document.getElementById('invCustomerName').value.trim();
|
// CSRF check
|
||||||
if (!cname) { alert('Bitte Kunde/Firma eintragen'); return; }
|
if (!isset($_POST['csrf_token']) || !isset($_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
|
||||||
|
die("CSRF validation failed");
|
||||||
const btn = document.getElementById('btnCreateInvoice');
|
|
||||||
btn.disabled = true;
|
|
||||||
btn.textContent = 'Erstelle…';
|
|
||||||
|
|
||||||
try {
|
|
||||||
const fd = new FormData();
|
|
||||||
fd.append('termin_id', terminIdForInvoice);
|
|
||||||
fd.append('customer_name', cname);
|
|
||||||
fd.append('customer_street', document.getElementById('invStreet').value.trim());
|
|
||||||
fd.append('customer_zip', document.getElementById('invZip').value.trim());
|
|
||||||
fd.append('customer_city', document.getElementById('invCity').value.trim());
|
|
||||||
fd.append('customer_country', document.getElementById('invCountry').value.trim() || 'DE');
|
|
||||||
|
|
||||||
const res = await fetch(API_INVOICE_CREATE, { method: 'POST', body: fd });
|
|
||||||
const json = await res.json();
|
|
||||||
|
|
||||||
if (!json.ok) throw new Error(json.error || 'Rechnung konnte nicht erstellt werden');
|
|
||||||
|
|
||||||
invoiceModal.hide();
|
|
||||||
|
|
||||||
// PDF öffnen
|
|
||||||
window.open(API_INVOICE_PDF + json.invoice_id, '_blank');
|
|
||||||
|
|
||||||
// Seite neu laden, damit Button auf "PDF öffnen" wechselt
|
|
||||||
window.location.reload();
|
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
alert('Fehler: ' + e.message);
|
|
||||||
} finally {
|
|
||||||
btn.disabled = false;
|
|
||||||
btn.textContent = 'Erstellen & PDF';
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
if(isset($_POST['inputStartzeit_ts']) && !empty($_POST['inputStartzeit_ts'])){
|
||||||
|
|||||||
Reference in New Issue
Block a user