From 685e356980c03585b5aa43e83f132ca27fd8e7e7 Mon Sep 17 00:00:00 2001 From: Lars Gebhardt-Kusche Date: Mon, 12 Jan 2026 00:55:44 +0100 Subject: [PATCH] asdasd --- partials/landingpage/accountsetup/bridge.php | 97 ++++++++++---- public/assets/js/bridge-setup-page.js | 93 ++++++++------ src/ApiKernel.php | 126 +------------------ 3 files changed, 135 insertions(+), 181 deletions(-) diff --git a/partials/landingpage/accountsetup/bridge.php b/partials/landingpage/accountsetup/bridge.php index 737f935..f796b16 100644 --- a/partials/landingpage/accountsetup/bridge.php +++ b/partials/landingpage/accountsetup/bridge.php @@ -12,12 +12,11 @@ require dirname(__DIR__) . '/../structure/layout_start.php'; Diese Angaben werden nur verwendet, um die emailtemplate_bridge.php zu generieren. Das EmailTemplate-System selbst behält Zugriff auf alle Tabellen; die hier definierten Whitelists greifen ausschließlich in der Bridge-Datei.

-

Voraussetzung: Bridge-URL + Token in den Einstellungen hinterlegen.

-

Import-Varianten:

+

Moegliche Wege zur DB-Anbindung:

@@ -33,25 +32,13 @@ require dirname(__DIR__) . '/../structure/layout_start.php';

Die Auswahl bestimmt, welche Werte in die Bridge-Datei geschrieben werden.

-
- - -

Liest die aktuelle DB-Struktur direkt von der Bridge-Datei.

- -

Liest DB-Settings ueber die Bridge-URL und nutzt sie zum Abfragen der Tabellen/Spalten.

-
-
+ +
- - Nutzt die Bridge-URL/Token oder die direkten DB-Settings (je nach Auswahl). + + Nutzt Bridge-URL/Token aus den Einstellungen und uebernimmt optional DB-Settings aus der Bridge-Datei.
Noch nicht gespeichert.
@@ -86,6 +108,39 @@ require dirname(__DIR__) . '/../structure/layout_start.php';
+ + +

Beispiel: Mapping einer Config-Datei

+

Angenommen, deine ../config/database.php liefert folgendes Array:

+
 [
+        'connections' => [
+            'default' => [
+                'host' => '127.0.0.1',
+                'port' => 3306,
+                'database' => 'kunden_db',
+                'username' => 'dbuser',
+                'password' => 'secret',
+                'charset' => 'utf8mb4',
+            ],
+        ],
+    ],
+];
+PHP, ENT_QUOTES); ?>
+

Dann trägst du ein:

+
    +
  • Pfad zur Konfigurationsdatei: ../config/database.php
  • +
  • Basis-Pfad: database.connections.default
  • +
  • Host-/Port-/DB-/User-/Pass-/Charset-Key: jeweils host, port, database, username, password, charset
  • +
+

Die Bridge liest dann automatisch die Werte aus diesem Array und baut daraus den DSN.

+
+ +
+ +
updateTablesPreview(parseTablesInput())); loadBtn?.addEventListener('click', loadTablesFromBridge); + configExampleBtn?.addEventListener('click', () => { + if (configExampleDialog?.showModal) configExampleDialog.showModal(); + }); modeInputs.forEach(input => { input.addEventListener('change', () => applyModeVisibility(input.value)); }); @@ -40,8 +45,7 @@ export function initBridgeSetupPage() { function defaultSetup() { return { tables: [], - mode: 'bridge', - import_type: 'schema', + mode: 'direct', direct: { host: '', port: 3306, @@ -50,6 +54,16 @@ function defaultSetup() { password: '', charset: 'utf8mb4', }, + config: { + file: '', + base: '', + host_key: '', + port_key: '', + database_key: '', + user_key: '', + password_key: '', + charset_key: '', + }, }; } @@ -81,13 +95,6 @@ function fillForm(setup) { }); applyModeVisibility(activeMode); - if (importInputs.length) { - const importType = (data.import_type || 'schema').toLowerCase(); - importInputs.forEach(input => { - input.checked = input.value === importType; - }); - } - if (directFields) { const directMap = { direct_host: data.direct.host || '', @@ -103,12 +110,29 @@ function fillForm(setup) { }); } + if (configFields) { + const configMap = { + config_file: data.config.file || '', + config_base: data.config.base || '', + config_host_key: data.config.host_key || '', + config_port_key: data.config.port_key || '', + config_database_key: data.config.database_key || '', + config_user_key: data.config.user_key || '', + config_password_key: data.config.password_key || '', + config_charset_key: data.config.charset_key || '', + }; + Object.entries(configMap).forEach(([name, value]) => { + const input = configFields.querySelector(`[name="${name}"]`); + if (input) input.value = value; + }); + } } function applyModeVisibility(mode) { - const showDirect = mode === 'direct'; - directFields?.classList[showDirect ? 'remove' : 'add']('hidden'); - bridgeFields?.classList[showDirect ? 'add' : 'remove']('hidden'); + const direct = mode === 'config' ? 'add' : 'remove'; + const config = mode === 'config' ? 'remove' : 'add'; + directFields?.classList[direct]('hidden'); + configFields?.classList[config]('hidden'); } function parseTablesInput() { @@ -133,17 +157,23 @@ async function submitBridgeSetup(ev) { ev.preventDefault(); if (!form) return; const mode = form.querySelector('input[name="db_mode"]:checked')?.value || 'direct'; - const importType = form.querySelector('input[name="bridge_import"]:checked')?.value || 'schema'; const payload = { tables: parseTablesInput(), mode, - import_type: importType, direct_host: form.direct_host?.value.trim() || '', direct_port: Number(form.direct_port?.value || 0) || 3306, direct_database: form.direct_database?.value.trim() || '', direct_charset: form.direct_charset?.value.trim() || 'utf8mb4', direct_user: form.direct_user?.value.trim() || '', direct_password: form.direct_password?.value || '', + config_file: form.config_file?.value.trim() || '', + config_base: form.config_base?.value.trim() || '', + config_host_key: form.config_host_key?.value.trim() || '', + config_port_key: form.config_port_key?.value.trim() || '', + config_database_key: form.config_database_key?.value.trim() || '', + config_user_key: form.config_user_key?.value.trim() || '', + config_password_key: form.config_password_key?.value.trim() || '', + config_charset_key: form.config_charset_key?.value.trim() || '', }; try { @@ -163,26 +193,20 @@ async function loadTablesFromBridge(ev) { if (!loadBtn) return; loadBtn.disabled = true; try { - const mode = form?.querySelector('input[name="db_mode"]:checked')?.value || 'bridge'; - const importType = form?.querySelector('input[name="bridge_import"]:checked')?.value || 'schema'; - const payload = { - mode, - import_type: importType, - direct_host: form?.direct_host?.value.trim() || '', - direct_port: Number(form?.direct_port?.value || 0) || 3306, - direct_database: form?.direct_database?.value.trim() || '', - direct_charset: form?.direct_charset?.value.trim() || 'utf8mb4', - direct_user: form?.direct_user?.value.trim() || '', - direct_password: form?.direct_password?.value || '', - }; - const res = await apiAction('account.bridge.test', { method: 'POST', data: payload }); + const res = await apiAction('account.bridge.test', { method: 'POST', data: {} }); if (!res?.ok) throw new Error(res?.error || 'Bridge konnte nicht abgefragt werden'); const fetchedTables = normalizeTableNames(res.tables); const allowedTables = normalizeTableNames(res.setup_hint?.tables ?? fetchedTables); const merged = { ...(state.setup || {}), tables: allowedTables, + ...(res.setup_hint || {}), }; + if (res.setup_hint) { + merged.mode = res.setup_hint.mode || merged.mode; + merged.direct = { ...(merged.direct || {}), ...(res.setup_hint.direct || {}) }; + merged.config = { ...(merged.config || {}), ...(res.setup_hint.config || {}) }; + } fillForm(merged); updateStatus(`Tabellen geladen (${fetchedTables.length}).`); toast('Tabellen erfolgreich geladen', true); @@ -235,12 +259,11 @@ function normalizeSetupInput(input) { const base = defaultSetup(); if (!input || typeof input !== 'object') return base; const mode = (input.mode || base.mode).toLowerCase(); - const validMode = mode === 'direct' ? 'direct' : 'bridge'; - const importType = (input.import_type || base.import_type).toLowerCase(); - const validImport = importType === 'settings' ? 'settings' : 'schema'; + const validMode = mode === 'config' ? 'config' : 'direct'; const tables = normalizeTableNames(input.tables || base.tables); const direct = { ...base.direct, ...(input.direct || {}) }; direct.port = Number(direct.port || 3306) || 3306; direct.charset = direct.charset || 'utf8mb4'; - return { tables, mode: validMode, import_type: validImport, direct }; + const config = { ...base.config, ...(input.config || {}) }; + return { tables, mode: validMode, direct, config }; } diff --git a/src/ApiKernel.php b/src/ApiKernel.php index 1b91169..f269065 100644 --- a/src/ApiKernel.php +++ b/src/ApiKernel.php @@ -1455,7 +1455,6 @@ class ApiKernel private function handlePlaceholderSchema(): void { - $user = $this->requireAuth(); $user = $this->requireAuth(); $this->ensureRole($user, ['owner', 'admin']); $customerId = (int)($user['customer_id'] ?? 0); @@ -1534,79 +1533,6 @@ class ApiKernel return $decoded; } - private function fetchSchemaFromDirect(array $direct, array $tablesAllow = []): array - { - $host = trim((string)($direct['host'] ?? '')); - $dbName = trim((string)($direct['database'] ?? '')); - $user = trim((string)($direct['user'] ?? '')); - $pass = (string)($direct['password'] ?? ''); - $charset = trim((string)($direct['charset'] ?? 'utf8mb4')) ?: 'utf8mb4'; - $port = (int)($direct['port'] ?? 3306); - - if ($host === '' || $dbName === '' || $user === '') { - throw new RuntimeException('DB settings missing'); - } - - $dsn = "mysql:host={$host};port={$port};dbname={$dbName};charset={$charset}"; - $pdo = new PDO($dsn, $user, $pass, [ - PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, - PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, - ]); - - $whitelist = []; - foreach ($tablesAllow as $tbl) { - if (is_string($tbl) && $tbl !== '') { - $whitelist[strtolower($tbl)] = true; - } - } - - $tablesStmt = $pdo->query('SHOW FULL TABLES'); - $tables = []; - while ($row = $tablesStmt->fetch(PDO::FETCH_NUM)) { - $tableName = $row[0] ?? null; - if (!$tableName) { - continue; - } - if ($whitelist && empty($whitelist[strtolower($tableName)])) { - continue; - } - - $columnsStmt = $pdo->prepare( - 'SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_DEFAULT, COLUMN_KEY, EXTRA - FROM INFORMATION_SCHEMA.COLUMNS - WHERE TABLE_SCHEMA = :schema AND TABLE_NAME = :table - ORDER BY ORDINAL_POSITION' - ); - $columnsStmt->execute([ - ':schema' => $dbName, - ':table' => $tableName, - ]); - $columns = []; - foreach ($columnsStmt as $col) { - $columns[] = [ - 'name' => $col['COLUMN_NAME'], - 'type' => $col['DATA_TYPE'], - 'nullable' => ($col['IS_NULLABLE'] === 'YES'), - 'default' => $col['COLUMN_DEFAULT'], - 'key' => $col['COLUMN_KEY'], - 'extra' => $col['EXTRA'], - 'placeholder' => strtoupper($tableName) . '__' . strtoupper($col['COLUMN_NAME']), - ]; - } - - $tables[] = [ - 'name' => $tableName, - 'columns' => $columns, - ]; - } - - return [ - 'ok' => true, - 'tables' => $tables, - 'fetched' => date(DATE_ATOM), - ]; - } - private function placeholderCachePath(string $url, string $token): string { $hash = md5($url . '|' . $token); @@ -2145,33 +2071,6 @@ class ApiKernel $user = $this->requireAuth(); $this->ensureRole($user, ['owner', 'admin']); $customerId = (int)($user['customer_id'] ?? 0); - $setup = $this->getBridgeSetupData($customerId); - $mode = strtolower((string)($this->in['mode'] ?? $this->in['db_mode'] ?? ($setup['mode'] ?? 'bridge'))); - $importType = strtolower((string)($this->in['import_type'] ?? $this->in['bridge_import'] ?? ($setup['import_type'] ?? 'schema'))); - $tablesAllow = $this->normalizeBridgeTables($this->in['tables'] ?? ($setup['tables'] ?? [])); - - if ($mode === 'direct') { - $direct = [ - 'host' => trim((string)($this->in['direct_host'] ?? ($setup['direct']['host'] ?? ''))), - 'port' => (int)($this->in['direct_port'] ?? ($setup['direct']['port'] ?? 3306)), - 'database' => trim((string)($this->in['direct_database'] ?? ($setup['direct']['database'] ?? ''))), - 'user' => trim((string)($this->in['direct_user'] ?? ($setup['direct']['user'] ?? ''))), - 'password' => (string)($this->in['direct_password'] ?? ($setup['direct']['password'] ?? '')), - 'charset' => trim((string)($this->in['direct_charset'] ?? ($setup['direct']['charset'] ?? 'utf8mb4'))) ?: 'utf8mb4', - ]; - try { - $schema = $this->fetchSchemaFromDirect($direct, $tablesAllow); - } catch (Throwable $e) { - $this->fail('DB request failed', $e->getMessage(), 502); - return; - } - $this->respond([ - 'ok' => true, - 'tables' => $schema['tables'] ?? [], - 'fetched' => $schema['fetched'] ?? date(DATE_ATOM), - ]); - } - $bridgeUrl = trim((string)($this->in['bridge_url'] ?? '')); $bridgeToken = trim((string)($this->in['bridge_token'] ?? '')); if ($bridgeUrl === '' || $bridgeToken === '') { @@ -2189,28 +2088,6 @@ class ApiKernel return; } - if ($importType === 'settings') { - $hint = is_array($schema['setup_hint'] ?? null) ? $schema['setup_hint'] : []; - $directHint = is_array($hint['direct'] ?? null) ? $hint['direct'] : []; - $direct = [ - 'host' => trim((string)($directHint['host'] ?? '')), - 'port' => (int)($directHint['port'] ?? 3306), - 'database' => trim((string)($directHint['database'] ?? '')), - 'user' => trim((string)($directHint['user'] ?? '')), - 'password' => (string)($directHint['password'] ?? ''), - 'charset' => trim((string)($directHint['charset'] ?? 'utf8mb4')) ?: 'utf8mb4', - ]; - if ($direct['host'] === '' || $direct['database'] === '' || $direct['user'] === '') { - $this->fail('Bridge lieferte keine DB-Settings', null, 422); - } - try { - $schema = $this->fetchSchemaFromDirect($direct, $tablesAllow); - } catch (Throwable $e) { - $this->fail('DB request failed', $e->getMessage(), 502); - return; - } - } - $this->respond([ 'ok' => true, 'tables' => $schema['tables'] ?? [], @@ -2401,8 +2278,7 @@ class ApiKernel { return [ 'tables' => [], - 'mode' => 'bridge', - 'import_type' => 'schema', + 'mode' => 'direct', 'direct' => [ 'host' => '', 'port' => 3306,