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>
This commit is contained in:
copilot-swe-agent[bot] 2026-05-02 15:41:20 +00:00 committed by GitHub
parent 04dc98380b
commit 3024e41121
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 82 additions and 5 deletions

View file

@ -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']);

View file

@ -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 .= "<div class='xml-node__attributes'><strong>Attributes</strong>";
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 .= "<div class='attr-row'><span>{$attrSafe}</span><input type='text' name=\"nodes[{$safeNodeKey}][attributes][{$attrSafe}]\" value=\"{$valSafe}\" placeholder='Leave blank to remove'></div>";
}
$html .= "<div class='attr-row'><input type='text' name=\"nodes[{$safeNodeKey}][new_attribute][name]\" placeholder='New attribute name'><input type='text' name=\"nodes[{$safeNodeKey}][new_attribute][value]\" placeholder='New attribute value'></div>";