From 3024e411214b247c159427ef97e81a6d82fc23f9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 2 May 2026 15:41:20 +0000 Subject: [PATCH] fix: config editor array-to-string warning and billing description INSERT failure - config_servers.php: add gsp_normalize_config_value(), gsp_value_to_display_string(), gsp_value_to_editable_string() helpers; replace (string)$attrValue cast at line 124 with gsp_value_to_editable_string() so PHP arrays from SimpleXML attribute iteration never trigger "Array to string conversion" notices - adminserverlist.php sync_billing_services(): add description column (= service name) to INSERT so the query succeeds on databases where description is NOT NULL without a default; add pre-flight col_exists() schema guard that shows a friendly admin warning and aborts sync instead of crashing on completely missing columns Agent-Logs-Url: https://github.com/GameServerPanel/GSP/sessions/e8bfe531-e1ff-4257-b49c-f8376b84e772 Co-authored-by: iaretechnician <2749183+iaretechnician@users.noreply.github.com> --- modules/billing/adminserverlist.php | 29 +++++++++++-- modules/config_games/config_servers.php | 58 ++++++++++++++++++++++++- 2 files changed, 82 insertions(+), 5 deletions(-) diff --git a/modules/billing/adminserverlist.php b/modules/billing/adminserverlist.php index eb1a1f1e..f61cba53 100644 --- a/modules/billing/adminserverlist.php +++ b/modules/billing/adminserverlist.php @@ -57,6 +57,25 @@ function sync_billing_services(mysqli $db, string $prefix): array { $messages = []; + // Schema guard: verify billing_services has the expected columns before touching it. + // col_exists() is provided by bootstrap.php. + $requiredCols = ['home_cfg_id', 'mod_cfg_id', 'service_name', 'description', + 'remote_server_id', 'enabled', 'out_of_stock', + 'price_daily', 'price_monthly', 'price_year', + 'slot_min_qty', 'slot_max_qty', 'install_method']; + $tableName = $prefix . 'billing_services'; + foreach ($requiredCols as $col) { + if (!col_exists($db, $tableName, $col)) { + $messages[] = "⚠ Schema issue: column '{$col}' missing from {$tableName}. Run the billing module migration."; + } + } + // If critical columns are missing, skip the sync to avoid SQL errors. + foreach (['service_name', 'mod_cfg_id', 'enabled'] as $critical) { + if (!col_exists($db, $tableName, $critical)) { + return $messages; + } + } + // Load all games/mods from panel config tables $gameMods = []; $res = $db->query( @@ -97,12 +116,14 @@ function sync_billing_services(mysqli $db, string $prefix): array $homeCfgId = (int)$gm['home_cfg_id']; $db->query( "INSERT INTO `{$prefix}billing_services` - (home_cfg_id, mod_cfg_id, service_name, remote_server_id, - enabled, out_of_stock, price_daily, price_monthly, price_year, + (home_cfg_id, mod_cfg_id, service_name, description, + remote_server_id, enabled, out_of_stock, + price_daily, price_monthly, price_year, slot_min_qty, slot_max_qty, install_method) VALUES - ({$homeCfgId}, {$modCfgId}, '{$svcName}', '', - 0, 0, 0, 0, 0, + ({$homeCfgId}, {$modCfgId}, '{$svcName}', '{$svcName}', + '', 0, 0, + 0, 0, 0, 1, 100, 'steamcmd')" ); $messages[] = "Added new service: " . ($gm['mod_name'] ?: $gm['game_name']); diff --git a/modules/config_games/config_servers.php b/modules/config_games/config_servers.php index 2729c203..4a2ad9a2 100644 --- a/modules/config_games/config_servers.php +++ b/modules/config_games/config_servers.php @@ -24,6 +24,62 @@ require_once("server_config_parser.php"); +/** + * Safely convert any config value (string, NULL, or array from SimpleXML) to a + * plain PHP string without triggering "Array to string conversion" notices. + * + * - string / int / float → cast directly + * - NULL → empty string + * - SimpleXMLElement → (string) cast (its __toString returns node text) + * - simple flat array → comma-separated list of leaf values + * - nested/complex array → JSON (pretty-printed for readability in admin forms) + */ +function gsp_normalize_config_value($value): string +{ + if ($value === null) { + return ''; + } + if ($value instanceof SimpleXMLElement) { + return (string)$value; + } + if (!is_array($value)) { + return (string)$value; + } + // Flat array → comma-separated list of scalar/castable items + $isFlat = true; + foreach ($value as $item) { + if (is_array($item) || ($item instanceof SimpleXMLElement && count($item->children()) > 0)) { + $isFlat = false; + break; + } + } + if ($isFlat) { + return implode(',', array_map(function ($item) { + return $item instanceof SimpleXMLElement ? (string)$item : (string)$item; + }, $value)); + } + return json_encode($value, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); +} + +/** + * Return an HTML-safe string suitable for display (echo) in the editor. + * Always wraps the result in htmlspecialchars. + */ +function gsp_value_to_display_string($value): string +{ + return htmlspecialchars(gsp_normalize_config_value($value), ENT_QUOTES, 'UTF-8'); +} + +/** + * Return an HTML-safe string suitable for use as an HTML form field value. + * Identical to gsp_value_to_display_string — kept as a distinct function so + * callers can signal intent and future formatting rules can differ. + */ +function gsp_value_to_editable_string($value): string +{ + return htmlspecialchars(gsp_normalize_config_value($value), ENT_QUOTES, 'UTF-8'); +} + function config_games_normalize_path($path) { $clean = preg_replace('/[^A-Za-z0-9_\\[\\]\\/\\-]/', '', (string)$path); @@ -121,7 +177,7 @@ function config_games_render_node(SimpleXMLElement $node, array $ancestors, arra $html .= "
Attributes"; foreach ((array)$attributes as $attrName => $attrValue) { $attrSafe = htmlspecialchars($attrName, ENT_QUOTES, 'UTF-8'); - $valSafe = htmlspecialchars((string)$attrValue, ENT_QUOTES, 'UTF-8'); + $valSafe = gsp_value_to_editable_string($attrValue); $html .= "
{$attrSafe}
"; } $html .= "
";