From 756a079b5c1181751ec43e16318de5a108417f0d Mon Sep 17 00:00:00 2001 From: Lars Gebhardt-Kusche Date: Sat, 6 Dec 2025 01:18:18 +0100 Subject: [PATCH] adasd --- inc/ApiKernel (Kopie).php | 636 -------------------------------- inc/OUTDATED auth_helpers.php | 138 ------- inc/OUTDATED bootstrap.php | 232 ------------ inc/OUTDATED config.example.php | 36 -- inc/api_kernel_log.txt | 259 ------------- 5 files changed, 1301 deletions(-) delete mode 100644 inc/ApiKernel (Kopie).php delete mode 100644 inc/OUTDATED auth_helpers.php delete mode 100644 inc/OUTDATED bootstrap.php delete mode 100644 inc/OUTDATED config.example.php delete mode 100644 inc/api_kernel_log.txt diff --git a/inc/ApiKernel (Kopie).php b/inc/ApiKernel (Kopie).php deleted file mode 100644 index 79538fd..0000000 --- a/inc/ApiKernel (Kopie).php +++ /dev/null @@ -1,636 +0,0 @@ -conf = $this->loadConfig(); - $this->cors(); - $this->setInput(); - $this->pdo = $this->getPdoTemplates(); - $this->resolveAction(); - $this->resolveTableMap(); - $this->authService = new AuthService($this->conf, $this->pdo); - } catch (Throwable $e) { - $this->fail('Initialization error', get_class($e) . ': ' . $e->getMessage(), 500); - } - } - - // --- Core Responder-Methoden (Unverändert) --- - - public function respond($data, int $code = 200): void - { - http_response_code($code); - echo is_string($data) ? $data : json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); - exit; - } - - public function fail(string $msg, $detail = null, int $code = 400): void - { - $this->respond(['ok' => false, 'error' => $msg, 'detail' => $detail], $code); - } - - // --- Private Initialisierungs- & Utility-Methoden (Unverändert) --- - - private function loadConfig(): array { /* ... Logik bleibt unverändert ... */ - $paths = [ - __DIR__ . '/config.php', - __DIR__ . '/../config.php', - __DIR__ . '/../../config.php', - ]; - - foreach ($paths as $p) { - if (is_file($p)) { - $conf = @include $p; - if (is_array($conf)) return $conf; - } - } - $this->fail('Invalid config.php', 'config.php not found or not returning array', 500); - } - private function cors(): void { /* ... Logik bleibt unverändert ... */ - $cors = $this->conf['cors'] ?? '*'; - if ($cors) { - header('Access-Control-Allow-Origin: ' . $cors); - header('Access-Control-Allow-Methods: GET, POST, OPTIONS'); - header('Access-Control-Allow-Headers: Content-Type, Authorization'); - header('Access-Control-Allow-Credentials: true'); - } - if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'OPTIONS') $this->respond(['ok' => true]); - - if (!empty($this->conf['auth']['cookie'])) { - $c = $this->conf['auth']['cookie']; - $params = session_get_cookie_params(); - $params['lifetime'] = $c['lifetime'] ?? $params['lifetime']; - $params['path'] = $c['path'] ?? $params['path']; - $params['domain'] = $c['domain'] ?? $params['domain']; - $params['secure'] = $c['secure'] ?? $params['secure']; - $params['httponly'] = $c['httponly'] ?? $params['httponly']; - if (isset($c['samesite'])) $params['samesite'] = $c['samesite']; - session_set_cookie_params($params); - } - } - private function setInput(): void { /* ... Logik bleibt unverändert ... */ - $data = []; - $ct = $_SERVER['CONTENT_TYPE'] ?? ''; - if (stripos($ct, 'application/json') !== false) { - $raw = file_get_contents('php://input'); - if ($raw !== false && $raw !== '') { - $js = json_decode($raw, true); - if (is_array($js)) $data = $js; - } - } - foreach ($_POST as $k => $v) $data[$k] = $v; - foreach ($_GET as $k => $v) if (!array_key_exists($k, $data)) $data[$k] = $v; - $this->in = $data; - } - private function getPdoTemplates(): PDO { /* ... Logik bleibt unverändert ... */ - if (!isset($this->conf['templates']) || !is_array($this->conf['templates'])) { - $this->fail('Missing templates DB config', null, 500); - } - $c = $this->conf['templates']; - $host = $c['db_host'] ?? 'localhost'; - $db = $c['db_name'] ?? ($c['database'] ?? ''); - $user = $c['db_user'] ?? ($c['username'] ?? ''); - $pass = $c['db_pass'] ?? ($c['password'] ?? ''); - $charset = $c['db_charset'] ?? 'utf8mb4'; - $port = $c['db_port'] ?? 3306; - $dsn = "mysql:host=$host;port=$port;dbname=$db;charset=$charset"; - $opt = [ - PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, - PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, - ]; - return new PDO($dsn, $user, $pass, $opt); - } - private function resolveAction(): void { /* ... Logik bleibt unverändert ... */ - $action = $this->val($this->in, 'action', ''); - $resource = $this->val($this->in, 'resource', null); - $allowedResources = ['templates', 'sections', 'blocks', 'snippets']; - if ($resource && in_array($resource, $allowedResources, true) && strpos((string)$action, '.') === false) { - $verb = strtolower((string)$action); - if (in_array($verb, ['list', 'get', 'create', 'update', 'delete'], true)) $action = $resource . '.' . $verb; - } - $this->action = $action; - } - private function resolveTableMap(): void { /* ... Logik bleibt unverändert ... */ - $tables = $this->conf['tables'] ?? []; - $this->tableMap = [ - 'templates' => $tables['templates'] ?? 'emailtemplate_templates', - 'sections' => $tables['sections'] ?? 'emailtemplate_sections', - 'blocks' => $tables['blocks'] ?? 'emailtemplate_blocks', - 'snippets' => $tables['snippets'] ?? 'emailtemplate_snippets', - ]; - } - - private function val(array $in, $keys, $default = null) { /* ... Logik bleibt unverändert ... */ - if (!is_array($keys)) $keys = [$keys]; - foreach ($keys as $k) if (array_key_exists($k, $in)) return $in[$k]; - return $default; - } - private function firstExisting(array $columns, array $candidates): ?string { /* ... Logik bleibt unverändert ... */ - foreach ($candidates as $c) if (in_array($c, $columns, true)) return $c; - return null; - } - private function tableColumns(string $table): array { /* ... Logik bleibt unverändert ... */ - $cols = []; - $stmt = $this->pdo->query("SHOW COLUMNS FROM `$table`"); - foreach ($stmt->fetchAll() as $r) $cols[] = $r['Field']; - return $cols; - } - private function primaryKey(string $table): ?string { /* ... Logik bleibt unverändert ... */ - $stmt = $this->pdo->prepare("SHOW KEYS FROM `$table` WHERE Key_name = 'PRIMARY'"); - $stmt->execute(); - $row = $stmt->fetch(); - return $row['Column_name'] ?? null; - } - private function requireAuth(): array { /* ... Logik bleibt unverändert ... */ - return $this->authService->requireAuth(); - } - private function pullId(array $src) { /* ... Logik bleibt unverändert ... */ - $aliases = ['id', 'item_id', 'template_id', 'tpl_id', 'section_id', 'sec_id', 'block_id', 'blk_id', 'snippet_id', 'snip_id']; - foreach ($aliases as $a) if (isset($src[$a]) && $src[$a] !== '') return $src[$a]; - return null; - } - private function tenantWhere(array $session): array { /* ... Logik bleibt unverändert ... */ - $multi = $this->conf['multi'] ?? []; - $tenantCol = $multi['tenant_col'] ?? null; - $mapSess = $multi['map_session_to'] ?? 'id'; - - if (!$tenantCol) return ['', []]; - if (!$session) return [' AND 1=0 ', []]; - $val = $session[$mapSess] ?? null; - if ($val === null || $val === '') return [' AND 1=0 ', []]; - return [" AND `$tenantCol` = :__tenant", [':__tenant' => $val]]; - } - private function tenantAssign(array $session, array $columns): array { /* ... Logik bleibt unverändert ... */ - $multi = $this->conf['multi'] ?? []; - $tenantCol = $multi['tenant_col'] ?? null; - $mapSess = $multi['map_session_to'] ?? 'id'; - - if (!$tenantCol || !in_array($tenantCol, $columns, true)) return []; - $val = $session[$mapSess] ?? null; - return ($val === null || $val === '') ? [] : [$tenantCol => $val]; - } - private function resolveIdCol(string $kind): array { /* ... Logik bleibt unverändert ... */ - $t = $this->tableMap[$kind]; - $cfg = $this->conf['columns'][$kind] ?? []; - $cols = $this->tableColumns($t); - $idCol = $cfg['id'] ?? ($this->firstExisting($cols, ['id']) ?: $this->primaryKey($t)); - if (!$idCol) $idCol = 'id'; - return [$idCol, $cols]; - } - private function parseHtmlToGjsComponents(string $html): array { /* ... Logik bleibt unverändert ... */ - if (trim($html) === '') return []; - - return [ - [ - 'type' => 'html', - 'content' => $html, - 'removable' => true, - 'draggable' => true, - 'droppable' => true, - 'copyable' => true, - 'selectable' => true, - 'editable' => false, - 'traits' => [], - ] - ]; - } - - // 💡 KORREKTUR: Bereinigungsmethode (von vorheriger Version übernommen) - private function cleanReferenceComponents(array $components): array { - foreach ($components as &$component) { - if (is_array($component) && isset($component['type'])) { - - if ($component['type'] === 'library-reference') { - if (isset($component['content'])) { - $component['content'] = ''; - } - if (isset($component['components'])) { - $component['components'] = []; - } - } - if (isset($component['components']) && is_array($component['components'])) { - $component['components'] = $this->cleanReferenceComponents($component['components']); - } - } - } - return $components; - } - - - // ================================================================= - // 🚀 NEUE CRUD HANDLER METHODEN (Logik aus run() extrahiert) - // ================================================================= - - /** - * Allgemeine Methode zur Handhabung von LIST-Anfragen. - */ - private function handleList(string $kind): void - { - $auth = $this->requireAuth(); - $t = $this->tableMap[$kind]; - [$idCol, $allCols] = $this->resolveIdCol($kind); - $cfg = $this->conf['columns'][$kind] ?? []; - $nameCol = $cfg['name'] ?? ($this->firstExisting($allCols, ['name']) ?: $idCol); - $descCol = $cfg['desc'] ?? $this->firstExisting($allCols, ['description', 'desc', 'descr']); - $catCol = $cfg['cat'] ?? $this->firstExisting($allCols, ['category', 'cat']); - $updCol = $cfg['upd'] ?? $this->firstExisting($allCols, ['updated_at', 'updated', 'updatedAt']); - $q = trim((string)$this->val($this->in, 'q', '')); - $limit = max(1, (int)$this->val($this->in, 'limit', 500)); - $offset = max(0, (int)$this->val($this->in, 'offset', 0)); - - $where = ' WHERE 1=1 '; - $params = []; - // Suchlogik (q) - if ($q !== '') { - $parts = ["`$nameCol` LIKE :q"]; - if ($descCol) $parts[] = "`$descCol` LIKE :q"; - if ($catCol) $parts[] = "`$catCol` LIKE :q"; - $where .= " AND (" . implode(' OR ', $parts) . ") "; - $params[':q'] = '%' . $q . '%'; - } - - // Filterlogik (parentFilters) - $parentFilters = [ - 'template_id' => $this->val($this->in, ['template_id', 'tpl_id'], null), - 'section_id' => $this->val($this->in, ['section_id', 'sec_id'], null), - 'block_id' => $this->val($this->in, ['block_id', 'blk_id'], null), - ]; - foreach ($parentFilters as $col => $v) { - if ($v === null || $v === '') continue; - if (in_array($col, $allCols, true)) { $where .= " AND `$col` = :$col "; $params[":$col"] = $v; } - } - - // Tenant-Filter - [$tw, $tp] = $this->tenantWhere($auth); - $where .= $tw; - foreach ($tp as $k => $v) $params[$k] = $v; - - $order = $updCol ? " ORDER BY `$updCol` DESC " : " ORDER BY `$nameCol` ASC "; - $sql = "SELECT * FROM `$t` $where $order LIMIT :off,:lim"; - $stmt = $this->pdo->prepare($sql); - - // Bind parameters - foreach ($params as $k => $v) $stmt->bindValue($k, $v, is_int($v) ? PDO::PARAM_INT : PDO::PARAM_STR); - $stmt->bindValue(':off', $offset, PDO::PARAM_INT); - $stmt->bindValue(':lim', $limit, PDO::PARAM_INT); - $stmt->execute(); - $rows = $stmt->fetchAll(); - - $out = []; - foreach ($rows as $r) { - $item = [ - 'id' => $r[$idCol] ?? null, - 'name' => $r[$nameCol] ?? null, - ]; - if ($descCol && isset($r[$descCol])) $item['desc'] = $r[$descCol]; - if ($catCol && isset($r[$catCol])) $item['category'] = $r[$catCol]; - if ($updCol && isset($r[$updCol])) $item['updated_at'] = $r[$updCol]; - - // Lade HTML und JSON aus den korrekten Spalten - $htmlCol = $this->firstExisting($allCols, ['html', 'body', 'markup', 'content']); - if ($htmlCol && isset($r[$htmlCol])) $item['html'] = (string)$r[$htmlCol]; - $jsonCol = $this->firstExisting($allCols, ['json_content']); - if ($jsonCol && isset($r[$jsonCol])) $item['content'] = $r[$jsonCol]; - - $out[] = $item; - } - $this->respond(['ok' => true, 'kind' => $kind, 'items' => $out, 'data' => $out, 'count' => count($out), 'offset' => $offset, 'limit' => $limit]); - } - - /** - * Allgemeine Methode zur Handhabung von GET-Anfragen. - */ - private function handleGet(string $kind): void - { - $auth = $this->requireAuth(); - $t = $this->tableMap[$kind]; - [$idCol, $allCols] = $this->resolveIdCol($kind); - $id = $this->pullId($this->in); - if ($id === null || $id === '') $this->fail('id required', null, 422); - - [$tw, $tp] = $this->tenantWhere($auth); - $sql = "SELECT * FROM `$t` WHERE `$idCol` = :id" . $tw . " LIMIT 1"; - $stmt = $this->pdo->prepare($sql); - $stmt->bindValue(':id', $id); - foreach ($tp as $k => $v) $stmt->bindValue($k, $v); - $stmt->execute(); - $row = $stmt->fetch(); - if (!$row) $this->fail('Not found', ['kind' => $kind, 'id' => $id], 404); - $rowOut = ['id' => $row[$idCol] ?? $id] + $row; - - // Lade HTML und JSON aus den korrekten Spalten - $htmlCol = $this->firstExisting($allCols, ['html', 'body', 'markup', 'content']); - $topHtml = ($htmlCol && isset($row[$htmlCol])) ? (string)$row[$htmlCol] : null; - $jsonCol = $this->firstExisting($allCols, ['json_content']); - $topContent = ($jsonCol && isset($row[$jsonCol])) ? $row[$jsonCol] : null; - - $gjsComponents = []; - - if ($topContent !== null) { - $decodedContent = json_decode($topContent, true); - if (is_array($decodedContent)) { - $gjsComponents = $decodedContent; - } - } - - if (empty($gjsComponents) && $topHtml !== null) { - $gjsComponents = $this->parseHtmlToGjsComponents($topHtml); - } - - $this->respond([ - 'ok' => true, - 'kind' => $kind, - 'id' => $rowOut['id'], - 'item' => $rowOut, - 'data' => $rowOut, - 'html' => $topHtml, - 'content' => $topContent, - 'gjs_components' => $gjsComponents - ]); - } - - /** - * Allgemeine Methode zur Handhabung von CREATE-Anfragen (inkl. JSON-Bereinigung). - */ - private function handleCreate(string $kind): void - { - $auth = $this->requireAuth(); - $t = $this->tableMap[$kind]; - [$idCol, $allCols] = $this->resolveIdCol($kind); - $cfg = $this->conf['columns'][$kind] ?? []; - $nameCol = $cfg['name'] ?? ($this->firstExisting($allCols, ['name']) ?: $idCol); - $descCol = $cfg['desc'] ?? $this->firstExisting($allCols, ['description', 'desc', 'descr']); - $catCol = $cfg['cat'] ?? $this->firstExisting($allCols, ['category', 'cat']); - $updCol = $cfg['upd'] ?? $this->firstExisting($allCols, ['updated_at', 'updated', 'updatedAt']); - - $name = trim((string)$this->val($this->in, ['name', 'title'], '')); - if ($name === '') $this->fail('name required', null, 422); - - $desc = (string)$this->val($this->in, ['description', 'desc'], null); - $cat = (string)$this->val($this->in, ['category', 'cat'], null); - $html = (string)$this->val($this->in, ['html', 'body', 'markup'], null); - $json = $this->val($this->in, ['content_json', 'json', 'content', 'structure_json'], null); - $settings = $this->val($this->in, ['settings_json', 'settings'], null); - $templateId = $this->val($this->in, ['template_id', 'tpl_id'], null); - $sectionId = $this->val($this->in, ['section_id', 'sec_id'], null); - $blockId = $this->val($this->in, ['block_id', 'blk_id'], null); - - $data = [$nameCol => $name]; - if ($desc !== null && $descCol) $data[$descCol] = $desc; - if ($cat !== null && $catCol) $data[$catCol] = $cat; - - $htmlDbCol = $this->firstExisting($allCols, ($kind === 'snippets' ? ['content'] : ['html', 'body', 'markup'])); - $jsonDbCol = $this->firstExisting($allCols, ['json_content']); - - // --- LOGIK mit ERWEITERTER PRÜFUNG START --- - - // 1. JSON-Content behandeln - if ($json !== null) { - if ($jsonDbCol) { - $components = is_string($json) ? json_decode($json, true) : $json; - if (is_array($components)) { - $components = $this->cleanReferenceComponents($components); // BEREINIGUNG - $data[$jsonDbCol] = json_encode($components, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); - } else { - $data[$jsonDbCol] = is_string($json) ? $json : ''; - } - } else { - // FALLBACK: Wenn JSON gesendet wurde, aber keine json_content Spalte existiert, FEHLER ausgeben - $this->fail( - 'JSON content provided but no `json_content` column found', - ['table' => $t, 'available_cols' => $allCols], - 422 - ); - } - } - - // 2. HTML-Content speichern - if ($htmlDbCol && $html !== null) { - $data[$htmlDbCol] = $html; - } - // --- LOGIK mit ERWEITERTER PRÜFUNG ENDE --- - - $c = $this->firstExisting($allCols, ['settings_json', 'settings']); - if ($c && $settings !== null) $data[$c] = is_string($settings) ? $settings : json_encode($settings, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); - - if ($templateId !== null && in_array('template_id', $allCols, true)) $data['template_id'] = $templateId; - if ($sectionId !== null && in_array('section_id', $allCols, true)) $data['section_id'] = $sectionId; - if ($blockId !== null && in_array('block_id', $allCols, true)) $data['block_id'] = $blockId; - - $data = $data + $this->tenantAssign($_SESSION['auth'] ?? [], $allCols); - - $now = date('Y-m-d H:i:s'); - $createdCol = $this->firstExisting($allCols, ['created_at', 'created', 'createdAt']); - if ($createdCol) $data[$createdCol] = $now; - if ($updCol) $data[$updCol] = $now; - - $fields = array_keys($data); - $place = array_map(fn($c) => ":$c", $fields); - $sql = "INSERT INTO `$t` (" . implode(',', array_map(fn($c) => "`$c`", $fields)) . ") VALUES (" . implode(',', $place) . ")"; - $stmt = $this->pdo->prepare($sql); - foreach ($data as $k => $v) $stmt->bindValue(":$k", $v); - $stmt->execute(); - $newId = $this->pdo->lastInsertId(); - - $out = ['id' => $newId, 'name' => $name]; - if ($desc !== null) $out['desc'] = $desc; - if ($cat !== null) $out['category'] = $cat; - $this->respond(['ok' => true, 'kind' => $kind, 'id' => $newId, 'item' => $out, 'data' => $out]); - } - - /** - * Allgemeine Methode zur Handhabung von UPDATE-Anfragen (inkl. JSON-Bereinigung). - */ - private function handleUpdate(string $kind): void - { - $auth = $this->requireAuth(); - $t = $this->tableMap[$kind]; - [$idCol, $allCols] = $this->resolveIdCol($kind); - $cfg = $this->conf['columns'][$kind] ?? []; - $nameCol = $cfg['name'] ?? ($this->firstExisting($allCols, ['name']) ?: $idCol); - $descCol = $cfg['desc'] ?? $this->firstExisting($allCols, ['description', 'desc', 'descr']); - $catCol = $cfg['cat'] ?? $this->firstExisting($allCols, ['category', 'cat']); - $updCol = $cfg['upd'] ?? $this->firstExisting($allCols, ['updated_at', 'updated', 'updatedAt']); - $id = $this->pullId($this->in); - if ($id === null || $id === '') $this->fail('id required', null, 422); - - $data = []; - $name = $this->val($this->in, ['name', 'title'], null); - $desc = $this->val($this->in, ['description', 'desc'], null); - $cat = $this->val($this->in, ['category', 'cat'], null); - $html = $this->val($this->in, ['html', 'body', 'markup'], null); - $json = $this->val($this->in, ['content_json', 'json', 'content', 'structure_json'], null); - $settings = $this->val($this->in, ['settings_json', 'settings'], null); - - if ($name !== null) $data[$nameCol] = (string)$name; - if ($desc !== null && $descCol) $data[$descCol] = (string)$desc; - if ($cat !== null && $catCol) $data[$catCol] = (string)$cat; - - $htmlDbCol = $this->firstExisting($allCols, ($kind === 'snippets' ? ['content'] : ['html', 'body', 'markup'])); - $jsonDbCol = $this->firstExisting($allCols, ['json_content']); - - // --- LOGIK mit ERWEITERTER PRÜFUNG START --- - - // 1. JSON-Content behandeln - if ($json !== null) { - if ($jsonDbCol) { - // Wenn JSON-Spalte existiert, JSON verarbeiten und speichern - $components = is_string($json) ? json_decode($json, true) : $json; - if (is_array($components)) { - $components = $this->cleanReferenceComponents($components); // BEREINIGUNG - $data[$jsonDbCol] = json_encode($components, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); - } else { - $data[$jsonDbCol] = is_string($json) ? $json : ''; - } - } else { - // FALLBACK: Wenn JSON gesendet wurde, aber keine json_content Spalte existiert, FEHLER ausgeben - $this->fail( - 'JSON content provided but no `json_content` column found', - ['table' => $t, 'available_cols' => $allCols], - 422 - ); - } - - // 2. Den zugehörigen HTML-Output speichern (wird vom Editor immer mitgesendet, wenn JSON da ist) - if ($html !== null && $htmlDbCol) { - $data[$htmlDbCol] = (string)$html; - } - } elseif ($html !== null && $htmlDbCol) { - // Wenn NUR HTML gesendet wird (für minimale Änderungen), speichern wir nur HTML. - $data[$htmlDbCol] = (string)$html; - } - // --- LOGIK mit ERWEITERTER PRÜFUNG ENDE --- - - $c = $this->firstExisting($allCols, ['settings_json', 'settings']); - if ($settings !== null && $c) $data[$c] = is_string($settings) ? $settings : json_encode($settings, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); - - $tpl = $this->val($this->in, ['template_id', 'tpl_id'], null); - if ($tpl !== null && in_array('template_id', $allCols, true)) $data['template_id'] = $tpl; - $sec = $this->val($this->in, ['section_id', 'sec_id'], null); - if ($sec !== null && in_array('section_id', $allCols, true)) $data['section_id'] = $sec; - $blk = $this->val($this->in, ['block_id', 'blk_id'], null); - if ($blk !== null && in_array('block_id', $allCols, true)) $data['block_id'] = $blk; - - if ($updCol) $data[$updCol] = date('Y-m-d H:i:s'); - if (!$data) $this->fail('nothing to update', null, 422); - - [$tw, $tp] = $this->tenantWhere($auth); - $set = []; - foreach (array_keys($data) as $c) $set[] = "`$c` = :$c"; - $sql = "UPDATE `$t` SET " . implode(',', $set) . " WHERE `$idCol` = :__id" . $tw . " LIMIT 1"; - $stmt = $this->pdo->prepare($sql); - - foreach ($data as $k => $v) $stmt->bindValue(":$k", $v); - $stmt->bindValue(':__id', $id); - foreach ($tp as $k => $v) $stmt->bindValue($k, $v); - $stmt->execute(); - $this->respond(['ok' => true, 'kind' => $kind, 'id' => $id, 'updated' => array_keys($data)]); - } - - /** - * Allgemeine Methode zur Handhabung von DELETE-Anfragen. - */ - private function handleDelete(string $kind): void - { - $auth = $this->requireAuth(); - $t = $this->tableMap[$kind]; - [$idCol, $allCols] = $this->resolveIdCol($kind); - $id = $this->pullId($this->in); - if ($id === null || $id === '') $this->fail('id required', null, 422); - - [$tw, $tp] = $this->tenantWhere($auth); - $sql = "DELETE FROM `$t` WHERE `$idCol` = :__id" . $tw . " LIMIT 1"; - $stmt = $this->pdo->prepare($sql); - $stmt->bindValue(':__id', $id); - foreach ($tp as $k => $v) $stmt->bindValue($k, $v); - $stmt->execute(); - $this->respond(['ok' => true, 'kind' => $kind, 'id' => $id, 'deleted' => true]); - } - - // ================================================================= - // 💡 Öffentliche run()-Methode (DEUTLICH VEREINFACHT) - // ================================================================= - - public function run(): void - { - header('Content-Type: application/json; charset=utf-8'); - - try { - // Extrahiere den Ressourcen-Typ und die Operation (z.B. 'templates' und 'list') - [$kind, $operation] = explode('.', $this->action, 2) + [1 => '']; - - switch ($this->action) { - case 'health': - $this->respond(['ok' => true, 'time' => date('c')]); - - /* ---------- AUTH ---------- */ - case 'auth.login': - $result = $this->authService->login($this->in); - $this->respond(['ok' => true] + $result); - break; - case 'auth.me': - if (empty($_SESSION['auth'])) $this->fail('Not authenticated', null, 401); - $this->respond(['ok' => true, 'user' => $_SESSION['auth']]); - break; - case 'auth.logout': - $this->authService->logout(); - $this->respond(['ok' => true]); - break; - - /* ---------- CRUD HANDLER ---------- */ - default: - if (in_array($kind, ['templates', 'sections', 'blocks', 'snippets'])) { - switch ($operation) { - case 'list': - $this->handleList($kind); - break; - case 'get': - $this->handleGet($kind); - break; - case 'create': - $this->handleCreate($kind); - break; - case 'update': - $this->handleUpdate($kind); - break; - case 'delete': - $this->handleDelete($kind); - break; - default: - $this->fail('Unknown operation for resource: ' . $this->action, null, 404); - break; - } - } else { - $this->fail('Unknown action', $this->action ?: 'missing', 404); - } - break; - } - } catch (Throwable $e) { - $this->fail('Server error', get_class($e) . ': ' . $e->getMessage(), 500); - } - } -} diff --git a/inc/OUTDATED auth_helpers.php b/inc/OUTDATED auth_helpers.php deleted file mode 100644 index 6ad3f69..0000000 --- a/inc/OUTDATED auth_helpers.php +++ /dev/null @@ -1,138 +0,0 @@ - 0, - 'path' => $cookiePath, - 'domain' => $cookieDomain, - 'secure' => $cookieSecure, - 'httponly' => $cookieHttpOnly, - 'samesite' => $cookieSameSite, - ]); - session_start(); - } -} - -/** ===== Auth Core ===== */ -function auth_login(PDO $pdoCustomers, array $cfg, string $email, string $password): array { - auth_start_session($cfg); - - $sql = "SELECT cu.id, cu.customer_id, cu.email, cu.password_hash, cu.role, - c.slug AS customer_slug, c.plan, c.status - FROM customer_users cu - JOIN customers c ON c.id = cu.customer_id - WHERE cu.email = :email AND cu.is_active = 1 - LIMIT 1"; - $st = $pdoCustomers->prepare($sql); - $st->execute([':email' => $email]); - $u = $st->fetch(PDO::FETCH_ASSOC); - - if (!$u || !password_verify($password, $u['password_hash'])) { - return ['ok' => false, 'error' => 'invalid_credentials']; - } - if (($u['status'] ?? 'active') !== 'active') { - return ['ok' => false, 'error' => 'customer_inactive']; - } - - // neue Session-ID, alte wird invalidiert - session_regenerate_id(true); - - $_SESSION['user'] = [ - 'id' => (int)$u['id'], - 'email' => $u['email'], - 'role' => $u['role'], - 'customer_id' => (int)$u['customer_id'], - 'customer_slug' => $u['customer_slug'], - 'plan' => $u['plan'], - ]; - return ['ok' => true, 'user' => $_SESSION['user']]; -} - -function auth_logout(array $cfg): void { - auth_start_session($cfg); - - // Sessiondaten löschen - $_SESSION = []; - - // Cookie-Parameter aus der aktiven Session - $params = session_get_cookie_params(); - $name = session_name(); - - // Kandidaten für Domain/Path, um "falsch" gesetzte Cookies sicher zu treffen - $host = $_SERVER['HTTP_HOST'] ?? ''; - $cfgDomain = $cfg['auth']['cookie_domain'] ?? ''; - $paths = array_values(array_unique([$params['path'] ?? '/', '/', ''])); - $domains = array_values(array_unique([ - $params['domain'] ?? '', - $cfgDomain, - $host, - ltrim($host, '.'), - (strpos($host, '.') !== false ? '.' . ltrim($host, '.') : $host), - ])); - - // Alle Varianten invalidieren (secure/httponly wie gesetzt) - foreach ($paths as $p) { - foreach ($domains as $d) { - if ($d === null) continue; - setcookie($name, '', time() - 3600, $p, $d, $params['secure'] ?? true, $params['httponly'] ?? true); - } - // zusätzlich: ohne Domain (trifft Host-spezifische Cookies) - setcookie($name, '', time() - 3600, $p, '', $params['secure'] ?? true, $params['httponly'] ?? true); - } - - // Session beenden - session_destroy(); - session_write_close(); - - // In Staging aggressiv: Browser bitten, Cookies zu löschen (nicht jeder Browser respektiert das sofort) - if (($cfg['env'] ?? 'prod') === 'staging') { - header('Clear-Site-Data: "cookies"', false); - } -} - -function auth_require(array $cfg): void { - auth_start_session($cfg); - if (empty($_SESSION['user'])) { - http_response_code(401); - header('Content-Type: application/json; charset=utf-8'); - echo json_encode(['ok' => false, 'error' => 'unauthorized']); - exit; - } -} - -function require_role(array $cfg, array $roles): void { - auth_start_session($cfg); - $r = $_SESSION['user']['role'] ?? null; - if (!$r || !in_array($r, $roles, true)) { - http_response_code(403); - header('Content-Type: application/json; charset=utf-8'); - echo json_encode(['ok' => false, 'error' => 'forbidden']); - exit; - } -} - -function current_user(array $cfg): ?array { - auth_start_session($cfg); - return $_SESSION['user'] ?? null; -} - -function current_customer_id(array $cfg): ?int { - $u = current_user($cfg); - return $u['customer_id'] ?? null; -} - diff --git a/inc/OUTDATED bootstrap.php b/inc/OUTDATED bootstrap.php deleted file mode 100644 index 6a6c05c..0000000 --- a/inc/OUTDATED bootstrap.php +++ /dev/null @@ -1,232 +0,0 @@ -false,'error'=>$msg,'detail'=>$detail], $code); -} - -// RESTLICHE HELFER (MÜSSEN KEINE GLOBALS VERWENDEN) -function load_config(): array { - $paths = [ - __DIR__ . '/config.php', - __DIR__ . '/../config.php', - __DIR__ . '/../../config.php', - ]; - - foreach ($paths as $p) { - if (is_file($p)) { - $conf = @include $p; - if (is_array($conf)) return $conf; - } - } - fail('Invalid config.php', 'config.php not found or not returning array', 500); -} - -function cors(array $conf): void { - $cors = $conf['cors'] ?? '*'; - if ($cors) { - header('Access-Control-Allow-Origin: ' . $cors); - header('Access-Control-Allow-Methods: GET, POST, OPTIONS'); - header('Access-Control-Allow-Headers: Content-Type, Authorization'); - header('Access-Control-Allow-Credentials: true'); - } - if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'OPTIONS') respond(['ok'=>true]); -} - -function get_input(): array { - $data = []; - $ct = $_SERVER['CONTENT_TYPE'] ?? ''; - if (stripos($ct, 'application/json') !== false) { - $raw = file_get_contents('php://input'); - if ($raw !== false && $raw !== '') { - $js = json_decode($raw, true); - if (is_array($js)) $data = $js; - } - } - foreach ($_POST as $k=>$v) $data[$k]=$v; - foreach ($_GET as $k=>$v) if (!array_key_exists($k,$data)) $data[$k]=$v; - return $data; -} - -function val(array $in, $keys, $default=null) { - if (!is_array($keys)) $keys = [$keys]; - foreach ($keys as $k) if (array_key_exists($k,$in)) return $in[$k]; - return $default; -} - -function first_existing(array $columns, array $candidates): ?string { - foreach ($candidates as $c) if (in_array($c, $columns, true)) return $c; - return null; -} - -function pdo_templates(array $conf): PDO { - if (!isset($conf['templates']) || !is_array($conf['templates'])) { - fail('Missing templates DB config', null, 500); - } - $c = $conf['templates']; - $host = $c['db_host'] ?? 'localhost'; - $db = $c['db_name'] ?? ($c['database'] ?? ''); - $user = $c['db_user'] ?? ($c['username'] ?? ''); - $pass = $c['db_pass'] ?? ($c['password'] ?? ''); - $charset = $c['db_charset'] ?? 'utf8mb4'; - $port = $c['db_port'] ?? 3306; - $dsn = "mysql:host=$host;port=$port;dbname=$db;charset=$charset"; - $opt = [ - PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, - PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, - ]; - return new PDO($dsn, $user, $pass, $opt); -} - -function verify_password(string $input, string $stored, array $authDbConf): bool { - if (preg_match('~^\$2[aby]\$~', $stored) || strpos($stored, '$argon2') === 0) return password_verify($input, $stored); - $legacy = strtolower($authDbConf['legacy'] ?? ''); - if ($legacy === 'md5') return hash_equals($stored, md5($input)); - if ($legacy === 'sha1') return hash_equals($stored, sha1($input)); - if (password_get_info($stored)['algo'] !== 0) return password_verify($input, $stored); - return hash_equals($stored, $input); -} - -function table_columns(PDO $pdo, string $table): array { - $cols = []; - $stmt = $pdo->query("SHOW COLUMNS FROM `$table`"); - foreach ($stmt->fetchAll() as $r) $cols[] = $r['Field']; - return $cols; -} - -function primary_key(PDO $pdo, string $table): ?string { - $stmt = $pdo->prepare("SHOW KEYS FROM `$table` WHERE Key_name = 'PRIMARY'"); - $stmt->execute(); - $row = $stmt->fetch(); - return $row['Column_name'] ?? null; -} - - -// --- Neue, reguläre Funktionen (ersetzen Closures) --- - -function requireAuth(): array { - // Muss auf globale $_SESSION zugreifen - if (empty($_SESSION['auth'])) fail('Not authenticated', null, 401); - return $_SESSION['auth']; -} - -function pullId(array $src) { - $aliases = ['id','item_id','template_id','tpl_id','section_id','sec_id','block_id','blk_id','snippet_id','snip_id']; - foreach ($aliases as $a) if (isset($src[$a]) && $src[$a] !== '') return $src[$a]; - return null; -} - -function tenantWhere(array $session): array { - // Muss auf globale $conf zugreifen, um $tenantCol und $mapSess zu erhalten - global $conf; - $multi = $conf['multi'] ?? []; - $tenantCol = $multi['tenant_col'] ?? null; - $mapSess = $multi['map_session_to'] ?? 'id'; - - if (!$tenantCol) return ['', []]; - if (!$session) return [' AND 1=0 ', []]; - $val = $session[$mapSess] ?? null; - if ($val===null || $val==='') return [' AND 1=0 ', []]; - return [" AND `$tenantCol` = :__tenant", [':__tenant'=>$val]]; -} - -function tenantAssign(array $session, array $columns): array { - // Muss auf globale $conf zugreifen - global $conf; - $multi = $conf['multi'] ?? []; - $tenantCol = $multi['tenant_col'] ?? null; - $mapSess = $multi['map_session_to'] ?? 'id'; - - if (!$tenantCol || !in_array($tenantCol, $columns, true)) return []; - $val = $session[$mapSess] ?? null; - return ($val===null || $val==='') ? [] : [$tenantCol => $val]; -} - -function resolveIdCol(string $kind): array { - // Muss auf globale $conf, $pdo, und $tableMap zugreifen - global $conf, $pdo, $tableMap; - - $t = $tableMap[$kind]; - $cfg = $conf['columns'][$kind] ?? []; - $cols = table_columns($pdo, $t); - $idCol = $cfg['id'] ?? (in_array('id', $cols, true) ? 'id' : primary_key($pdo, $t)); - if (!$idCol) $idCol = 'id'; - return [$idCol, $cols]; -} - -// --- Haupt-Setup-Logik (Setzt die globalen Variablen) --- - -try { - // Deklariere alle Variablen, die im Router von api.php benötigt werden, als global - global $conf, $pdo, $in, $action, $tableMap; - - // 1. Globale Konfiguration und CORS - $conf = load_config(); - cors($conf); - - // 2. Cookie-Parameter setzen - if (!empty($conf['auth']['cookie'])) { - $c = $conf['auth']['cookie']; - $params = session_get_cookie_params(); - $params['lifetime'] = $c['lifetime'] ?? $params['lifetime']; - $params['path'] = $c['path'] ?? $params['path']; - $params['domain'] = $c['domain'] ?? $params['domain']; - $params['secure'] = $c['secure'] ?? $params['secure']; - $params['httponly'] = $c['httponly'] ?? $params['httponly']; - if (isset($c['samesite'])) $params['samesite'] = $c['samesite']; - session_set_cookie_params($params); - } - - // 3. Input-Daten abrufen - $in = get_input(); - - // 4. Datenbankverbindung herstellen - $pdo = pdo_templates($conf); - - // 5. Action / Resource auflösen - $action = val($in, 'action', ''); - $resource = val($in, 'resource', null); - $allowedResources = ['templates','sections','blocks','snippets']; - if ($resource && in_array($resource, $allowedResources, true) && strpos((string)$action, '.') === false) { - $verb = strtolower((string)$action); - if (in_array($verb, ['list','get','create','update','delete'], true)) $action = $resource.'.'.$verb; - } - - // 6. Tabellenzuweisungen - $tables = $conf['tables'] ?? []; - $tableMap = [ - 'templates' => $tables['templates'] ?? 'emailtemplate_templates', - 'sections' => $tables['sections'] ?? 'emailtemplate_sections', - 'blocks' => $tables['blocks'] ?? 'emailtemplate_blocks', - 'snippets' => $tables['snippets'] ?? 'emailtemplate_snippets', - ]; - -} catch (Throwable $e) { - // Fehler während der Initialisierung abfangen - fail('Initialization error', get_class($e).': '.$e->getMessage(), 500); -} diff --git a/inc/OUTDATED config.example.php b/inc/OUTDATED config.example.php deleted file mode 100644 index a990df8..0000000 --- a/inc/OUTDATED config.example.php +++ /dev/null @@ -1,36 +0,0 @@ - [ - 'db_host' => '127.0.0.1', - 'db_port' => 3306, - 'db_name' => 'YOUR_DB_NAME', - 'db_user' => 'YOUR_DB_USER', - 'db_pass' => 'YOUR_DB_PASS', - 'db_charset' => 'utf8mb4', - 'prefix' => 'emailtemplate_', - ], - 'project' => [ - 'db_host' => '127.0.0.1', - 'db_port' => 3306, - 'db_name' => 'YOUR_PROJECT_DB', - 'db_user' => 'YOUR_PROJECT_USER', - 'db_pass' => 'YOUR_PROJECT_PASS', - 'db_charset' => 'utf8mb4', - ], - - // SMTP / Testversand - 'smtp' => [ - 'host' => getenv('SMTP_HOST') ?: '', - 'port' => getenv('SMTP_PORT') ?: 587, - 'user' => getenv('SMTP_USER') ?: '', - 'pass' => getenv('SMTP_PASS') ?: '', - 'secure' => getenv('SMTP_SECURE') ?: 'tls', - 'from_email' => getenv('SMTP_FROM_EMAIL') ?: 'no-reply@example.com', - 'from_name' => getenv('SMTP_FROM_NAME') ?: 'EmailTemplate', - ], - - // Export-API: statische API-Keys - 'export' => [ - 'api_keys' => explode(',', getenv('EXPORT_API_KEYS') ?: 'dev-key-123'), - ], -]; diff --git a/inc/api_kernel_log.txt b/inc/api_kernel_log.txt deleted file mode 100644 index 21abd13..0000000 --- a/inc/api_kernel_log.txt +++ /dev/null @@ -1,259 +0,0 @@ - - - -[2025-10-31 01:21:54] --- Get::blocks - Raw JSON from DB --- -Array -( - [topContent] => {"dataSources":[],"assets":[],"styles":[{"selectors":[],"selectorsAdd":"*","style":{"box-sizing":"border-box"}},{"selectors":[],"selectorsAdd":"body","style":{"margin-top":"0px","margin-right":"0px","margin-bottom":"0px","margin-left":"0px"}}],"pages":[{"frames":[{"component":{"type":"wrapper","stylable":["background","background-color","background-image","background-repeat","background-attachment","background-position","background-size"],"components":[{"type":"library-reference","content":"Alles Neu macht der Mai","lib-kind":"snippets","lib-id":1}],"head":{"type":"head"},"docEl":{"tagName":"html"}},"id":"P4uy9DBKbT5yTO4c"}],"type":"main","id":"dJ5hyxgFUxsCWgbi"}],"symbols":[]} -) - - -[2025-10-31 01:21:54] --- Get::blocks - Decoded JSON --- -Array -( - [decodedContent] => Array - ( - [dataSources] => Array - ( - ) - - [assets] => Array - ( - ) - - [styles] => Array - ( - [0] => Array - ( - [selectors] => Array - ( - ) - - [selectorsAdd] => * - [style] => Array - ( - [box-sizing] => border-box - ) - - ) - - [1] => Array - ( - [selectors] => Array - ( - ) - - [selectorsAdd] => body - [style] => Array - ( - [margin-top] => 0px - [margin-right] => 0px - [margin-bottom] => 0px - [margin-left] => 0px - ) - - ) - - ) - - [pages] => Array - ( - [0] => Array - ( - [frames] => Array - ( - [0] => Array - ( - [component] => Array - ( - [type] => wrapper - [stylable] => Array - ( - [0] => background - [1] => background-color - [2] => background-image - [3] => background-repeat - [4] => background-attachment - [5] => background-position - [6] => background-size - ) - - [components] => Array - ( - [0] => Array - ( - [type] => library-reference - [content] => Alles Neu macht der Mai - [lib-kind] => snippets - [lib-id] => 1 - ) - - ) - - [head] => Array - ( - [type] => head - ) - - [docEl] => Array - ( - [tagName] => html - ) - - ) - - [id] => P4uy9DBKbT5yTO4c - ) - - ) - - [type] => main - [id] => dJ5hyxgFUxsCWgbi - ) - - ) - - [symbols] => Array - ( - ) - - ) - -) - - -[2025-10-31 01:21:54] --- Get::blocks - Final Gjs Components --- -Array -( - [count] => 5 - [first_component] => N/A -) - - -[2025-10-31 01:21:54] --- Get::blocks - Raw JSON from DB --- -Array -( - [topContent] => {"dataSources":[],"assets":[],"styles":[{"selectors":[],"selectorsAdd":"*","style":{"box-sizing":"border-box"}},{"selectors":[],"selectorsAdd":"body","style":{"margin-top":"0px","margin-right":"0px","margin-bottom":"0px","margin-left":"0px"}}],"pages":[{"frames":[{"component":{"type":"wrapper","stylable":["background","background-color","background-image","background-repeat","background-attachment","background-position","background-size"],"components":[{"type":"library-reference","content":"Alles Neu macht der Mai","lib-kind":"snippets","lib-id":1}],"head":{"type":"head"},"docEl":{"tagName":"html"}},"id":"P4uy9DBKbT5yTO4c"}],"type":"main","id":"dJ5hyxgFUxsCWgbi"}],"symbols":[]} -) - - -[2025-10-31 01:21:54] --- Get::blocks - Decoded JSON --- -Array -( - [decodedContent] => Array - ( - [dataSources] => Array - ( - ) - - [assets] => Array - ( - ) - - [styles] => Array - ( - [0] => Array - ( - [selectors] => Array - ( - ) - - [selectorsAdd] => * - [style] => Array - ( - [box-sizing] => border-box - ) - - ) - - [1] => Array - ( - [selectors] => Array - ( - ) - - [selectorsAdd] => body - [style] => Array - ( - [margin-top] => 0px - [margin-right] => 0px - [margin-bottom] => 0px - [margin-left] => 0px - ) - - ) - - ) - - [pages] => Array - ( - [0] => Array - ( - [frames] => Array - ( - [0] => Array - ( - [component] => Array - ( - [type] => wrapper - [stylable] => Array - ( - [0] => background - [1] => background-color - [2] => background-image - [3] => background-repeat - [4] => background-attachment - [5] => background-position - [6] => background-size - ) - - [components] => Array - ( - [0] => Array - ( - [type] => library-reference - [content] => Alles Neu macht der Mai - [lib-kind] => snippets - [lib-id] => 1 - ) - - ) - - [head] => Array - ( - [type] => head - ) - - [docEl] => Array - ( - [tagName] => html - ) - - ) - - [id] => P4uy9DBKbT5yTO4c - ) - - ) - - [type] => main - [id] => dJ5hyxgFUxsCWgbi - ) - - ) - - [symbols] => Array - ( - ) - - ) - -) - - -[2025-10-31 01:21:54] --- Get::blocks - Final Gjs Components --- -Array -( - [count] => 5 - [first_component] => N/A -)