config(); $dbConfig = $appConfig->db ?? []; function bridgeResolveDsn(array $dbConfig): string { $dsn = (string)($dbConfig['dsn'] ?? ''); if ($dsn !== '') { return $dsn; } $driver = (string)($dbConfig['driver'] ?? ''); if ($driver === '') { return ''; } return match ($driver) { 'mysql' => (function () use ($dbConfig): string { $dbname = (string)($dbConfig['dbname'] ?? ''); if ($dbname === '') { return ''; } $charset = (string)($dbConfig['charset'] ?? 'utf8mb4'); if (!empty($dbConfig['unix_socket'])) { return sprintf( 'mysql:unix_socket=%s;dbname=%s;charset=%s', (string)$dbConfig['unix_socket'], $dbname, $charset ); } $host = (string)($dbConfig['host'] ?? 'localhost'); $port = (int)($dbConfig['port'] ?? 3306); return sprintf('mysql:host=%s;port=%d;dbname=%s;charset=%s', $host, $port, $dbname, $charset); })(), 'pgsql' => (function () use ($dbConfig): string { $dbname = (string)($dbConfig['dbname'] ?? ''); if ($dbname === '') { return ''; } $host = (string)($dbConfig['host'] ?? 'localhost'); $port = (int)($dbConfig['port'] ?? 5432); return sprintf('pgsql:host=%s;port=%d;dbname=%s', $host, $port, $dbname); })(), 'sqlite' => (function () use ($dbConfig): string { $path = (string)($dbConfig['path'] ?? ''); if ($path === '') { $path = ':memory:'; } return 'sqlite:' . $path; })(), default => '', }; } // 2) Bridge-spezifische Konfiguration auf Basis der App-Config $bridgeConfig = [ 'token' => getenv('EMAILTEMPLATE_BRIDGE_TOKEN') ?: 'kgIqdL9aNWsFWy6mhSRpnuLc1EbZ62sGCcJAwjjlqqznEGE13szhksWUan0cEdjE', 'db' => [ 'dsn' => getenv('EMAILTEMPLATE_BRIDGE_DSN') ?: bridgeResolveDsn($dbConfig), 'user' => getenv('EMAILTEMPLATE_BRIDGE_DB_USER') ?: ($dbConfig['user'] ?? ''), 'pass' => getenv('EMAILTEMPLATE_BRIDGE_DB_PASS') ?: ($dbConfig['password'] ?? ''), 'options' => $dbConfig['options'] ?? [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ], ], // Optional: Tabellen-Whitelist 'tables_allow' => ['event', 'mail_accounts', 'users', 'verification_codes'], // z.B. ['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; } if (empty($config['db']['dsn'])) { bridgeRespond(['ok' => false, 'error' => 'DB DSN not configured'], 500); } 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); }