getenv('EMAILTEMPLATE_BRIDGE_TOKEN') ?: 'kgIqdL9aNWsFWy6mhSRpnuLc1EbZ62sGCcJAwjjlqqznEGE13szhksWUan0cEdjE', 'db' => [ 'dsn' => getenv('EMAILTEMPLATE_BRIDGE_DSN') ?: 'mysql:host=127.0.0.1;dbname=example;charset=utf8mb4', 'user' => getenv('EMAILTEMPLATE_BRIDGE_DB_USER') ?: 'root', 'pass' => getenv('EMAILTEMPLATE_BRIDGE_DB_PASS') ?: '', 'options' => [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ], ], 'tables_allow' => [], // optional whitelist: ['customers', 'orders'] ]; $localOverride = __DIR__ . '/emailtemplate.bridge.conf.php'; if (is_file($localOverride)) { $override = include $localOverride; if (is_array($override)) { $bridgeConfig = array_replace_recursive($bridgeConfig, $override); } } function bridgeRespond($payload, int $status = 200): void { http_response_code($status); header('Content-Type: application/json; charset=utf-8'); header('Cache-Control: no-store, max-age=0'); echo json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); exit; } function bridgeRequireToken(array $config): void { $expected = (string)($config['token'] ?? ''); if ($expected === '') { bridgeRespond(['ok' => false, 'error' => 'Bridge token not configured'], 500); } $provided = null; if (!empty($_SERVER['HTTP_AUTHORIZATION']) && stripos($_SERVER['HTTP_AUTHORIZATION'], 'Bearer ') === 0) { $provided = trim(substr($_SERVER['HTTP_AUTHORIZATION'], 7)); } elseif (!empty($_SERVER['HTTP_X_EMAILTEMPLATE_TOKEN'])) { $provided = trim($_SERVER['HTTP_X_EMAILTEMPLATE_TOKEN']); } elseif (isset($_GET['token'])) { $provided = (string)$_GET['token']; } elseif (isset($_POST['token'])) { $provided = (string)$_POST['token']; } if (!$provided || !hash_equals($expected, $provided)) { bridgeRespond(['ok' => false, 'error' => 'Unauthorized'], 403); } } function bridgeDb(array $config): PDO { static $pdo = null; if ($pdo instanceof PDO) { return $pdo; } try { $pdo = new PDO( $config['db']['dsn'], $config['db']['user'], $config['db']['pass'], $config['db']['options'] ); } catch (Throwable $e) { bridgeRespond(['ok' => false, 'error' => 'DB connection failed', 'detail' => $e->getMessage()], 500); } return $pdo; } bridgeRequireToken($bridgeConfig); $action = strtolower((string)($_GET['action'] ?? $_POST['action'] ?? 'schema')); if ($action === 'ping') { bridgeRespond(['ok' => true, 'time' => date(DATE_ATOM)]); } if ($action !== 'schema') { bridgeRespond(['ok' => false, 'error' => 'Unknown action'], 404); } $pdo = bridgeDb($bridgeConfig); try { $dbName = ''; if (preg_match('/dbname=([^;]+)/i', $bridgeConfig['db']['dsn'], $m)) { $dbName = $m[1]; } $tablesStmt = $pdo->query('SHOW FULL TABLES'); $tables = []; $whitelist = []; if (!empty($bridgeConfig['tables_allow']) && is_array($bridgeConfig['tables_allow'])) { foreach ($bridgeConfig['tables_allow'] as $tbl) { if (is_string($tbl) && $tbl !== '') { $whitelist[strtolower($tbl)] = true; } } } while ($row = $tablesStmt->fetch(PDO::FETCH_NUM)) { $tableName = $row[0]; if ($tableName === null) { 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 ?: $pdo->query('SELECT DATABASE()')->fetchColumn(), ':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, ]; } bridgeRespond([ 'ok' => true, 'tables' => $tables, 'fetched' => date(DATE_ATOM), ]); } catch (Throwable $e) { bridgeRespond(['ok' => false, 'error' => 'Schema fetch failed', 'detail' => $e->getMessage()], 500); } // Bridge DB Setup: direkte Angaben aus dem EmailTemplate-Backend.