initial upload
This commit is contained in:
466
uploadDeltaTest5.php
Normal file
466
uploadDeltaTest5.php
Normal file
@ -0,0 +1,466 @@
|
||||
<?php
|
||||
// /var/www/html/uploadDeltaTest5.php
|
||||
// Vollständige, eigenständige Version – einfach kopieren & einfügen.
|
||||
|
||||
// -----------------------------
|
||||
// Logging & PHP-Settings
|
||||
// -----------------------------
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', '0');
|
||||
ini_set('log_errors', '1');
|
||||
$LOG_FILE = __DIR__ . '/uploadDelta.log';
|
||||
function log_msg($msg) {
|
||||
global $LOG_FILE;
|
||||
error_log(date('[Y-m-d H:i:s] ') . $msg . PHP_EOL, 3, $LOG_FILE);
|
||||
}
|
||||
header('Content-Type: application/json; charset=UTF-8');
|
||||
|
||||
// -----------------------------
|
||||
// Krypto-/Token-Helfer
|
||||
// -----------------------------
|
||||
function b64_or_ascii_to_32_bytes(string $s): string {
|
||||
// Versuche Base64, sonst ASCII; pad/truncate auf 32
|
||||
$b = base64_decode($s, true);
|
||||
if ($b !== false && $b !== '') {
|
||||
return str_pad(substr($b, 0, 32), 32, "\0");
|
||||
}
|
||||
return str_pad(substr($s, 0, 32), 32, "\0");
|
||||
}
|
||||
|
||||
function get_master_key_bytes(): string {
|
||||
// MASTER-KEY nur serverseitig (für gespeicherte DB)
|
||||
// ENV: QDB_MASTER_KEY (Base64 oder ASCII). Fallback ist alter Key.
|
||||
$env = getenv('QDB_MASTER_KEY');
|
||||
if ($env && $env !== '') return b64_or_ascii_to_32_bytes($env);
|
||||
return "12345678901234567890123456789012"; // Fallback (bitte in PROD durch ENV ersetzen!)
|
||||
}
|
||||
|
||||
function hkdf_session_key_from_token(string $tokenHex, string $info = 'qdb-aes', int $len = 32): string {
|
||||
// HKDF-SHA256: salt = 32x0x00, info = "qdb-aes"
|
||||
$ikm = @hex2bin(trim($tokenHex));
|
||||
if ($ikm === false) $ikm = $tokenHex; // falls bereits binär
|
||||
$hashLen = 32;
|
||||
$salt = str_repeat("\0", $hashLen);
|
||||
$prk = hash_hmac('sha256', $ikm, $salt, true);
|
||||
$okm = '';
|
||||
$t = '';
|
||||
$n = (int)ceil($len / $hashLen);
|
||||
for ($i = 1; $i <= $n; $i++) {
|
||||
$t = hash_hmac('sha256', $t . $info . chr($i), $prk, true);
|
||||
$okm .= $t;
|
||||
}
|
||||
return substr($okm, 0, $len);
|
||||
}
|
||||
|
||||
function aes256_cbc_encrypt_bytes(string $plain, string $key32): string {
|
||||
$key = str_pad(substr($key32, 0, 32), 32, "\0");
|
||||
$iv = random_bytes(16);
|
||||
$cipher = openssl_encrypt($plain, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv);
|
||||
if ($cipher === false) throw new Exception('openssl_encrypt failed');
|
||||
return $iv . $cipher;
|
||||
}
|
||||
|
||||
function aes256_cbc_decrypt_bytes(string $data, string $key32): string {
|
||||
if (strlen($data) < 16) throw new Exception('cipher too short');
|
||||
$key = str_pad(substr($key32, 0, 32), 32, "\0");
|
||||
$iv = substr($data, 0, 16);
|
||||
$ct = substr($data, 16);
|
||||
$plain = openssl_decrypt($ct, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv);
|
||||
if ($plain === false) throw new Exception('openssl_decrypt failed');
|
||||
return $plain;
|
||||
}
|
||||
|
||||
// Token-Validierung: bevorzugt tokens.jsonl mit exp, Fallback valid_tokens.txt
|
||||
function token_is_valid(string $token): bool {
|
||||
$now = time();
|
||||
$jsonl = __DIR__ . '/tokens.jsonl';
|
||||
if (file_exists($jsonl)) {
|
||||
$h = fopen($jsonl, 'r');
|
||||
if ($h) {
|
||||
while (($line = fgets($h)) !== false) {
|
||||
$j = json_decode($line, true);
|
||||
if (!is_array($j)) continue;
|
||||
if (($j['token'] ?? '') === $token) {
|
||||
$exp = $j['exp'] ?? ($now + 1); // falls kein exp, akzeptieren
|
||||
fclose($h);
|
||||
return $exp >= $now;
|
||||
}
|
||||
}
|
||||
fclose($h);
|
||||
}
|
||||
}
|
||||
$txt = __DIR__ . '/valid_tokens.txt';
|
||||
if (file_exists($txt)) {
|
||||
$arr = file($txt, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
if ($arr && in_array($token, $arr, true)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// -----------------------------
|
||||
// Hauptlogik
|
||||
// -----------------------------
|
||||
try {
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(405);
|
||||
echo json_encode(["error" => "Nur POST erlaubt"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Token aus multipart oder Authorization: Bearer
|
||||
$token = $_POST['token'] ?? null;
|
||||
if (!$token) {
|
||||
$auth = $_SERVER['HTTP_AUTHORIZATION'] ?? ($_SERVER['Authorization'] ?? '');
|
||||
if (stripos($auth, 'Bearer ') === 0) {
|
||||
$token = substr($auth, 7);
|
||||
}
|
||||
}
|
||||
if (!$token || !token_is_valid($token)) {
|
||||
http_response_code(403);
|
||||
echo json_encode(["error" => "Ungültiger Token"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Upload entgegennehmen (multipart 'file' oder raw body)
|
||||
$uploadedFilePath = null;
|
||||
if (!empty($_FILES['file']) && is_uploaded_file($_FILES['file']['tmp_name'])) {
|
||||
$uploadedFilePath = $_FILES['file']['tmp_name'];
|
||||
} else {
|
||||
$raw = file_get_contents('php://input');
|
||||
if ($raw !== false && strlen($raw) > 0) {
|
||||
$tmp = tempnam(sys_get_temp_dir(), 'upl_');
|
||||
file_put_contents($tmp, $raw);
|
||||
$uploadedFilePath = $tmp;
|
||||
}
|
||||
}
|
||||
if (!$uploadedFilePath) {
|
||||
http_response_code(400);
|
||||
echo json_encode(["error" => "Keine Datei gesendet"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Payload mit SESSION-Key (aus Token via HKDF) entschlüsseln
|
||||
$encData = file_get_contents($uploadedFilePath);
|
||||
if ($encData === false) {
|
||||
http_response_code(500);
|
||||
echo json_encode(["error" => "Fehler beim Lesen der hochgeladenen Datei"]);
|
||||
exit;
|
||||
}
|
||||
$sessionKey = hkdf_session_key_from_token($token);
|
||||
$jsonData = null;
|
||||
try {
|
||||
$jsonData = aes256_cbc_decrypt_bytes($encData, $sessionKey);
|
||||
} catch (Throwable $e) {
|
||||
// Fallback: akzeptiere Plain-JSON (Kompatibilität)
|
||||
$maybeJson = trim($encData);
|
||||
if ($maybeJson === '' || json_decode($maybeJson, true) === null) {
|
||||
log_msg("Entschlüsselung fehlgeschlagen und kein valides JSON. raw len=" . strlen($encData));
|
||||
http_response_code(400);
|
||||
echo json_encode(["error" => "Ungültige verschlüsselte Datei oder kein JSON"]);
|
||||
exit;
|
||||
}
|
||||
$jsonData = $maybeJson;
|
||||
}
|
||||
|
||||
$data = json_decode($jsonData, true);
|
||||
if (!is_array($data)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(["error" => "Ungültiges JSON"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Ziel: MASTER-verschlüsselte DB speichern
|
||||
$dbPath = __DIR__ . '/uploads/questionnaire_database';
|
||||
if (!is_dir(dirname($dbPath))) {
|
||||
if (!mkdir(dirname($dbPath), 0755, true) && !is_dir(dirname($dbPath))) {
|
||||
throw new Exception("Konnte Upload-Ordner nicht erstellen");
|
||||
}
|
||||
}
|
||||
|
||||
// Lock, damit keine parallelen Writes kollidieren
|
||||
$lockFile = __DIR__ . '/uploads/.qdb_lock';
|
||||
$lockFp = fopen($lockFile, 'c');
|
||||
if ($lockFp === false) throw new Exception("Konnte Lock-Datei nicht öffnen");
|
||||
if (!flock($lockFp, LOCK_EX)) { fclose($lockFp); throw new Exception("Konnte Lock nicht setzen"); }
|
||||
|
||||
$tmpDb = null;
|
||||
$tmpEncrypted = null;
|
||||
|
||||
try {
|
||||
$tmpDb = tempnam(sys_get_temp_dir(), 'qdb_');
|
||||
$masterKey = get_master_key_bytes();
|
||||
|
||||
// Bestehende MASTER-verschlüsselte DB entschlüsseln oder neue DB mit Schema anlegen
|
||||
if (file_exists($dbPath) && is_file($dbPath)) {
|
||||
$storedEnc = @file_get_contents($dbPath);
|
||||
if ($storedEnc === false) throw new Exception("Konnte gespeicherte DB nicht lesen");
|
||||
$decrypted = aes256_cbc_decrypt_bytes($storedEnc, $masterKey);
|
||||
if (file_put_contents($tmpDb, $decrypted) === false) throw new Exception("Konnte temporäre DB nicht schreiben");
|
||||
} else {
|
||||
$pdo = new PDO("sqlite:$tmpDb");
|
||||
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
$pdo->exec("PRAGMA foreign_keys = ON;");
|
||||
$pdo->exec("
|
||||
CREATE TABLE IF NOT EXISTS clients (
|
||||
clientCode TEXT NOT NULL DEFAULT 'undefined' PRIMARY KEY
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS questionnaires (
|
||||
id TEXT NOT NULL DEFAULT 'undefined' PRIMARY KEY
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS questions (
|
||||
questionId TEXT NOT NULL DEFAULT 'undefined' PRIMARY KEY,
|
||||
questionnaireId TEXT NOT NULL,
|
||||
question TEXT NOT NULL DEFAULT '',
|
||||
FOREIGN KEY(questionnaireId) REFERENCES questionnaires(id) ON DELETE CASCADE
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS answers (
|
||||
clientCode TEXT NOT NULL,
|
||||
questionId TEXT NOT NULL,
|
||||
answerValue TEXT NOT NULL DEFAULT '',
|
||||
PRIMARY KEY (clientCode, questionId),
|
||||
FOREIGN KEY(clientCode) REFERENCES clients(clientCode) ON DELETE CASCADE,
|
||||
FOREIGN KEY(questionId) REFERENCES questions(questionId) ON DELETE CASCADE
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS completed_questionnaires (
|
||||
clientCode TEXT NOT NULL,
|
||||
questionnaireId TEXT NOT NULL,
|
||||
timestamp INTEGER NOT NULL,
|
||||
isDone INTEGER NOT NULL,
|
||||
sumPoints INTEGER,
|
||||
PRIMARY KEY (clientCode, questionnaireId),
|
||||
FOREIGN KEY(clientCode) REFERENCES clients(clientCode) ON DELETE CASCADE,
|
||||
FOREIGN KEY(questionnaireId) REFERENCES questionnaires(id) ON DELETE CASCADE
|
||||
);
|
||||
");
|
||||
$pdo = null;
|
||||
}
|
||||
|
||||
// Einspielen der Daten
|
||||
$db = new PDO("sqlite:$tmpDb");
|
||||
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
$db->exec("PRAGMA foreign_keys = ON;");
|
||||
$db->beginTransaction();
|
||||
|
||||
// --- Sammeln, welche IDs/Paarungen betroffen sind ---
|
||||
$questionnaireIds = []; // set[string] questionnaireId => true
|
||||
$clientQuestionPairs = []; // set["client|qid"] => true
|
||||
|
||||
if (!empty($data['questionnaires'])) {
|
||||
foreach ($data['questionnaires'] as $q) {
|
||||
if (isset($q['id']) && $q['id'] !== null && $q['id'] !== '') {
|
||||
$questionnaireIds[$q['id']] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!empty($data['questions'])) {
|
||||
foreach ($data['questions'] as $q) {
|
||||
if (isset($q['questionnaireId']) && $q['questionnaireId'] !== null && $q['questionnaireId'] !== '') {
|
||||
$questionnaireIds[$q['questionnaireId']] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!empty($data['completed_questionnaires'])) {
|
||||
foreach ($data['completed_questionnaires'] as $c) {
|
||||
if (isset($c['questionnaireId']) && $c['questionnaireId'] !== null && $c['questionnaireId'] !== '') {
|
||||
$questionnaireIds[$c['questionnaireId']] = true;
|
||||
}
|
||||
if (isset($c['clientCode']) && isset($c['questionnaireId'])) {
|
||||
$client = ($c['clientCode'] === null || $c['clientCode'] === '') ? 'undefined' : $c['clientCode'];
|
||||
$qid = ($c['questionnaireId'] === null || $c['questionnaireId'] === '') ? 'undefined' : $c['questionnaireId'];
|
||||
$clientQuestionPairs["$client|$qid"] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!empty($data['answers'])) {
|
||||
foreach ($data['answers'] as $a) {
|
||||
if (isset($a['clientCode'])) {
|
||||
$client = ($a['clientCode'] === null || $a['clientCode'] === '') ? 'undefined' : $a['clientCode'];
|
||||
$qid = null;
|
||||
if (isset($a['questionId']) && is_string($a['questionId'])) {
|
||||
$parts = explode('-', $a['questionId'], 2);
|
||||
if (count($parts) >= 1) {
|
||||
$potential = $parts[0];
|
||||
if ($potential !== '') $qid = $potential;
|
||||
}
|
||||
}
|
||||
if ($qid !== null) {
|
||||
$questionnaireIds[$qid] = true;
|
||||
$clientQuestionPairs["$client|$qid"] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!empty($data['clients']) && !empty($data['questionnaires'])) {
|
||||
foreach ($data['clients'] as $c) {
|
||||
if (!isset($c['clientCode'])) continue;
|
||||
$client = ($c['clientCode'] === null || $c['clientCode'] === '') ? 'undefined' : $c['clientCode'];
|
||||
foreach ($data['questionnaires'] as $q) {
|
||||
if (!isset($q['id'])) continue;
|
||||
$qid = ($q['id'] === null || $q['id'] === '') ? 'undefined' : $q['id'];
|
||||
$clientQuestionPairs["$client|$qid"] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Löschungen (zuerst abhängige Tabellen) ---
|
||||
$delAnswersByPairStmt = $db->prepare("
|
||||
DELETE FROM answers
|
||||
WHERE clientCode = :clientCode
|
||||
AND questionId IN (SELECT questionId FROM questions WHERE questionnaireId = :questionnaireId)
|
||||
");
|
||||
$delCompletedByPairStmt = $db->prepare("
|
||||
DELETE FROM completed_questionnaires
|
||||
WHERE clientCode = :clientCode
|
||||
AND questionnaireId = :questionnaireId
|
||||
");
|
||||
foreach ($clientQuestionPairs as $pair => $_) {
|
||||
list($clientCode, $qid) = explode('|', $pair, 2);
|
||||
try {
|
||||
$delAnswersByPairStmt->execute([
|
||||
':clientCode' => $clientCode,
|
||||
':questionnaireId' => $qid
|
||||
]);
|
||||
$delCompletedByPairStmt->execute([
|
||||
':clientCode' => $clientCode,
|
||||
':questionnaireId' => $qid
|
||||
]);
|
||||
log_msg("Deleted answers & completed for client='$clientCode' questionnaire='$qid'");
|
||||
} catch (Exception $e) {
|
||||
log_msg("Delete error for pair $pair: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($questionnaireIds)) {
|
||||
$delQuestionsStmt = $db->prepare("DELETE FROM questions WHERE questionnaireId = :questionnaireId");
|
||||
foreach ($questionnaireIds as $qid => $_) {
|
||||
try {
|
||||
$delQuestionsStmt->execute([':questionnaireId' => $qid]);
|
||||
log_msg("Deleted questions for questionnaireId='$qid'");
|
||||
} catch (Exception $e) {
|
||||
log_msg("Delete questions error for $qid: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Inserts/Upserts ---
|
||||
if (!empty($data['clients'])) {
|
||||
$stmt = $db->prepare("INSERT OR IGNORE INTO clients (clientCode) VALUES (:clientCode)");
|
||||
foreach ($data['clients'] as $client) {
|
||||
if (!isset($client['clientCode'])) continue;
|
||||
$val = $client['clientCode'] === null || $client['clientCode'] === '' ? 'undefined' : $client['clientCode'];
|
||||
$stmt->execute([':clientCode' => $val]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($data['questionnaires'])) {
|
||||
$stmt = $db->prepare("INSERT OR IGNORE INTO questionnaires (id) VALUES (:id)");
|
||||
foreach ($data['questionnaires'] as $q) {
|
||||
if (!isset($q['id'])) continue;
|
||||
$val = $q['id'] === null || $q['id'] === '' ? 'undefined' : $q['id'];
|
||||
$stmt->execute([':id' => $val]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($data['questions'])) {
|
||||
$stmt = $db->prepare("
|
||||
INSERT OR IGNORE INTO questions (questionId, questionnaireId, question)
|
||||
VALUES (:questionId, :questionnaireId, :question)
|
||||
");
|
||||
foreach ($data['questions'] as $q) {
|
||||
if (!isset($q['questionId'])) continue;
|
||||
$questionId = $q['questionId'] === null || $q['questionId'] === '' ? 'undefined' : $q['questionId'];
|
||||
$questionnaireId = $q['questionnaireId'] === null || $q['questionnaireId'] === '' ? 'undefined' : $q['questionnaireId'];
|
||||
$questionText = $q['question'] ?? '';
|
||||
$stmt->execute([
|
||||
':questionId' => $questionId,
|
||||
':questionnaireId' => $questionnaireId,
|
||||
':question' => $questionText
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($data['answers'])) {
|
||||
$stmt = $db->prepare("
|
||||
INSERT OR IGNORE INTO answers (clientCode, questionId, answerValue)
|
||||
VALUES (:clientCode, :questionId, :answerValue)
|
||||
");
|
||||
foreach ($data['answers'] as $a) {
|
||||
if (!isset($a['clientCode']) || !isset($a['questionId'])) continue;
|
||||
$clientCode = $a['clientCode'] === null || $a['clientCode'] === '' ? 'undefined' : $a['clientCode'];
|
||||
$questionId = $a['questionId'] === null || $a['questionId'] === '' ? 'undefined' : $a['questionId'];
|
||||
$answerValue = array_key_exists('answerValue', $a) && $a['answerValue'] !== null ? $a['answerValue'] : '';
|
||||
$stmt->execute([
|
||||
':clientCode' => $clientCode,
|
||||
':questionId' => $questionId,
|
||||
':answerValue' => $answerValue
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($data['completed_questionnaires'])) {
|
||||
$stmt = $db->prepare("
|
||||
INSERT OR REPLACE INTO completed_questionnaires (clientCode, questionnaireId, timestamp, isDone, sumPoints)
|
||||
VALUES (:clientCode, :questionnaireId, :timestamp, :isDone, :sumPoints)
|
||||
");
|
||||
foreach ($data['completed_questionnaires'] as $c) {
|
||||
if (!isset($c['clientCode']) || !isset($c['questionnaireId'])) continue;
|
||||
$clientCode = $c['clientCode'] === null || $c['clientCode'] === '' ? 'undefined' : $c['clientCode'];
|
||||
$questionnaireId = $c['questionnaireId'] === null || $c['questionnaireId'] === '' ? 'undefined' : $c['questionnaireId'];
|
||||
$timestamp = isset($c['timestamp']) ? (int)$c['timestamp'] : time();
|
||||
$isDone = !empty($c['isDone']) ? 1 : 0;
|
||||
$sumPoints = array_key_exists('sumPoints', $c) ? ($c['sumPoints'] === null ? null : (int)$c['sumPoints']) : null;
|
||||
$stmt->execute([
|
||||
':clientCode' => $clientCode,
|
||||
':questionnaireId' => $questionnaireId,
|
||||
':timestamp' => $timestamp,
|
||||
':isDone' => $isDone,
|
||||
':sumPoints' => $sumPoints
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$db->commit();
|
||||
$db = null;
|
||||
|
||||
// MASTER-verschlüsselt speichern (atomar via temp + rename)
|
||||
$plainDb = file_get_contents($tmpDb);
|
||||
if ($plainDb === false) throw new Exception("Konnte tmp DB nicht lesen");
|
||||
$enc = aes256_cbc_encrypt_bytes($plainDb, $masterKey);
|
||||
|
||||
$tmpEncrypted = tempnam(dirname($dbPath), 'enc_qdb_');
|
||||
if (file_put_contents($tmpEncrypted, $enc) === false) throw new Exception("Konnte verschlüsselte DB nicht schreiben");
|
||||
if (!@rename($tmpEncrypted, $dbPath)) {
|
||||
if (!@copy($tmpEncrypted, $dbPath) || !@unlink($tmpEncrypted)) {
|
||||
throw new Exception("Konnte verschlüsselte DB nicht speichern (rename/copy fehlgeschlagen)");
|
||||
}
|
||||
}
|
||||
@chmod($dbPath, 0644);
|
||||
|
||||
// Cleanup
|
||||
@unlink($uploadedFilePath);
|
||||
@unlink($tmpDb);
|
||||
flock($lockFp, LOCK_UN);
|
||||
fclose($lockFp);
|
||||
|
||||
echo json_encode(["success" => true, "message" => "Delta erfolgreich eingespielt und DB gespeichert"]);
|
||||
exit;
|
||||
|
||||
} catch (Throwable $inner) {
|
||||
// Cleanup bei Fehler
|
||||
@flock($lockFp, LOCK_UN);
|
||||
@fclose($lockFp);
|
||||
@unlink($tmpDb ?? '');
|
||||
@unlink($tmpEncrypted ?? '');
|
||||
@unlink($uploadedFilePath ?? '');
|
||||
log_msg("Inner exception: " . $inner->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode(["error" => "Fehler beim Speichern", "message" => $inner->getMessage()]);
|
||||
exit;
|
||||
}
|
||||
|
||||
} catch (Throwable $e) {
|
||||
log_msg("Top-level exception: " . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode(["error" => "Fehler", "message" => $e->getMessage()]);
|
||||
exit;
|
||||
}
|
||||
Reference in New Issue
Block a user