From bf44b618e4175a7104864dc02c0df7a93dc2f526 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 7 May 2026 00:22:45 +0000 Subject: [PATCH 1/2] feat: add Workshop behavior settings UI + fix billing period_start migration Agent-Logs-Url: https://github.com/GameServerPanel/GSP/sessions/ee35e671-8ff2-43fb-a365-f7a4f9263ca7 Co-authored-by: iaretechnician <2749183+iaretechnician@users.noreply.github.com> --- modules/billing/module.php | 27 +++- modules/steam_workshop/admin.php | 47 ++++++ modules/steam_workshop/includes/functions.php | 76 ++++++++++ modules/steam_workshop/module.php | 75 +++++++++- modules/steam_workshop/user.php | 137 ++++++++++++++++++ 5 files changed, 358 insertions(+), 4 deletions(-) diff --git a/modules/billing/module.php b/modules/billing/module.php index dcfd5a14..307f8654 100644 --- a/modules/billing/module.php +++ b/modules/billing/module.php @@ -24,8 +24,8 @@ // Module general information $module_title = "billing"; -$module_version = "3.5"; -$db_version = 6; +$module_version = "3.6"; +$db_version = 7; $module_required = FALSE; // Module description $module_description = "Billing storefront / provisioning integration. Public ordering runs as a standalone site; panel pages provide provisioning and admin order management."; @@ -399,4 +399,27 @@ $install_queries[6] = array( } ); +// ----------------------------------------------------------------------- +// db_version 7 — Ensure period_start and period_end columns exist in +// billing_invoices. These columns were defined in the baseline CREATE TABLE +// (db_version 1) but no migration was provided for existing installations +// that created the table before those columns were added, causing a fatal +// "Unknown column 'period_start'" error in add_to_cart.php. +// Each callable uses INFORMATION_SCHEMA so it is safe to re-run. +// ----------------------------------------------------------------------- +$install_queries[7] = array( + // billing_invoices: add period_start if missing + function($db) { + $r = $db->resultQuery("SELECT COUNT(*) AS cnt FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'OGP_DB_PREFIXbilling_invoices' AND COLUMN_NAME = 'period_start'"); + if ($r && isset($r[0]['cnt']) && (int)$r[0]['cnt'] > 0) return true; + return (bool)$db->query("ALTER TABLE `OGP_DB_PREFIXbilling_invoices` ADD `period_start` DATETIME NULL AFTER `players`"); + }, + // billing_invoices: add period_end if missing + function($db) { + $r = $db->resultQuery("SELECT COUNT(*) AS cnt FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'OGP_DB_PREFIXbilling_invoices' AND COLUMN_NAME = 'period_end'"); + if ($r && isset($r[0]['cnt']) && (int)$r[0]['cnt'] > 0) return true; + return (bool)$db->query("ALTER TABLE `OGP_DB_PREFIXbilling_invoices` ADD `period_end` DATETIME NULL AFTER `period_start`"); + }, +); + ?> \ No newline at end of file diff --git a/modules/steam_workshop/admin.php b/modules/steam_workshop/admin.php index 09977c00..4dfb9991 100644 --- a/modules/steam_workshop/admin.php +++ b/modules/steam_workshop/admin.php @@ -124,6 +124,17 @@ function sw_admin_save_profile($db) 'notes' => trim($_POST['notes'] ?? ''), ); + // Per-profile default behavior fields + $valid_update_modes = array('manual', 'on_restart', 'before_start', 'scheduled'); + $valid_restart_behaviors = array('none', 'if_empty', 'immediate', 'next_restart'); + $valid_hot_load = array('disabled', 'attempt'); + $posted_um = $_POST['default_update_mode'] ?? 'manual'; + $posted_rb = $_POST['default_restart_behavior'] ?? 'none'; + $posted_hl = $_POST['default_hot_load'] ?? 'disabled'; + $fields['default_update_mode'] = in_array($posted_um, $valid_update_modes, true) ? $posted_um : 'manual'; + $fields['default_restart_behavior'] = in_array($posted_rb, $valid_restart_behaviors, true) ? $posted_rb : 'none'; + $fields['default_hot_load'] = in_array($posted_hl, $valid_hot_load, true) ? $posted_hl : 'disabled'; + $setParts = array(); foreach ($fields as $col => $val) { $setParts[] = "`$col` = '" . $db->realEscapeSingle($val) . "'"; @@ -291,6 +302,42 @@ function sw_admin_edit_form(array $profile, array $detected = array(), $showDete +
+

Default Workshop Behavior for New Servers

+

+ These defaults are applied when a user enables Workshop on a server that has no saved behavior settings yet. + Users can always override them on their own server pages. + All defaults are intentionally set to the safest option (manual / no auto-restart / hot-load off). +

+
+ + + +
+
+

Cancel diff --git a/modules/steam_workshop/includes/functions.php b/modules/steam_workshop/includes/functions.php index 11b106d4..b1228d48 100644 --- a/modules/steam_workshop/includes/functions.php +++ b/modules/steam_workshop/includes/functions.php @@ -342,6 +342,82 @@ function sw_generate_launch_params(array $mods, array $profile) ); } +// ── Server behavior settings helpers ───────────────────────────────────── + +/** + * Return the workshop behavior settings row for a home, or an array of + * safe defaults when no row exists yet. + * + * @param OGPDatabase $db + * @param int $home_id + * @return array + */ +function sw_get_server_settings($db, $home_id) +{ + $home_id = (int)$home_id; + $rows = $db->resultQuery( + "SELECT * FROM " . sw_table('steam_workshop_server_settings') . " + WHERE `home_id` = $home_id LIMIT 1" + ); + if ($rows && isset($rows[0])) { + return $rows[0]; + } + // Safe defaults – manual only, no automatic restarts, hot-load off + return array( + 'home_id' => $home_id, + 'update_mode' => 'manual', + 'restart_behavior' => 'none', + 'hot_load' => 'disabled', + 'warning_minutes' => 10, + 'schedule_interval' => 'daily', + ); +} + +/** + * Upsert the workshop behavior settings for a server home. + * + * @param OGPDatabase $db + * @param int $home_id + * @param array $data keys: update_mode, restart_behavior, hot_load, + * warning_minutes, schedule_interval + * @return bool + */ +function sw_save_server_settings($db, $home_id, array $data) +{ + $home_id = (int)$home_id; + + $valid_update_modes = array('manual', 'on_restart', 'before_start', 'scheduled'); + $valid_restart_behaviors = array('none', 'if_empty', 'immediate', 'next_restart'); + $valid_hot_load = array('disabled', 'attempt'); + $valid_intervals = array('hourly', 'daily', 'weekly'); + + $update_mode = in_array($data['update_mode'] ?? '', $valid_update_modes, true) ? $data['update_mode'] : 'manual'; + $restart_behavior = in_array($data['restart_behavior'] ?? '', $valid_restart_behaviors, true) ? $data['restart_behavior'] : 'none'; + $hot_load = in_array($data['hot_load'] ?? '', $valid_hot_load, true) ? $data['hot_load'] : 'disabled'; + $warning_minutes = max(1, min(120, (int)($data['warning_minutes'] ?? 10))); + $schedule_interval = in_array($data['schedule_interval'] ?? '', $valid_intervals, true) ? $data['schedule_interval'] : 'daily'; + + $safe_um = $db->realEscapeSingle($update_mode); + $safe_rb = $db->realEscapeSingle($restart_behavior); + $safe_hl = $db->realEscapeSingle($hot_load); + $safe_si = $db->realEscapeSingle($schedule_interval); + + return (bool)$db->query( + "INSERT INTO " . sw_table('steam_workshop_server_settings') . " + (`home_id`, `update_mode`, `restart_behavior`, `hot_load`, + `warning_minutes`, `schedule_interval`, `created_at`, `updated_at`) + VALUES ($home_id, '$safe_um', '$safe_rb', '$safe_hl', + $warning_minutes, '$safe_si', NOW(), NOW()) + ON DUPLICATE KEY UPDATE + `update_mode` = '$safe_um', + `restart_behavior` = '$safe_rb', + `hot_load` = '$safe_hl', + `warning_minutes` = $warning_minutes, + `schedule_interval` = '$safe_si', + `updated_at` = NOW()" + ); +} + // ── Output helpers ──────────────────────────────────────────────────────── /** diff --git a/modules/steam_workshop/module.php b/modules/steam_workshop/module.php index f8a1d09c..ad566778 100644 --- a/modules/steam_workshop/module.php +++ b/modules/steam_workshop/module.php @@ -11,8 +11,8 @@ // ── Module metadata ────────────────────────────────────────────────────── $module_title = "Steam Workshop"; -$module_version = "3.0"; -$db_version = 3; +$module_version = "3.1"; +$db_version = 4; $module_required = FALSE; $module_menus = array( array('subpage' => 'admin', 'name' => 'Steam Workshop', 'group' => 'admin'), @@ -51,6 +51,9 @@ $_sw_create_new = array( `update_script_template` TEXT NULL, `copy_bikeys_enabled` TINYINT(1) NOT NULL DEFAULT 1, `notes` TEXT NULL, + `default_update_mode` ENUM('manual','on_restart','before_start','scheduled') NOT NULL DEFAULT 'manual', + `default_restart_behavior` ENUM('none','if_empty','immediate','next_restart') NOT NULL DEFAULT 'none', + `default_hot_load` ENUM('disabled','attempt') NOT NULL DEFAULT 'disabled', `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` DATETIME NULL, PRIMARY KEY (`id`), @@ -76,6 +79,21 @@ $_sw_create_new = array( PRIMARY KEY (`id`), UNIQUE KEY `uniq_home_workshop` (`home_id`, `workshop_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci", + + "CREATE TABLE IF NOT EXISTS `OGP_DB_PREFIXsteam_workshop_server_settings` ( + `home_id` INT NOT NULL, + `update_mode` ENUM('manual','on_restart','before_start','scheduled') + NOT NULL DEFAULT 'manual', + `restart_behavior` ENUM('none','if_empty','immediate','next_restart') + NOT NULL DEFAULT 'none', + `hot_load` ENUM('disabled','attempt') + NOT NULL DEFAULT 'disabled', + `warning_minutes` INT NOT NULL DEFAULT 10, + `schedule_interval` VARCHAR(32) NOT NULL DEFAULT 'daily', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` DATETIME NULL, + PRIMARY KEY (`home_id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci", ); // ── Install queries ────────────────────────────────────────────────────── @@ -84,6 +102,9 @@ $_sw_create_new = array( // Drops any legacy tables and creates the new schema. // $install_queries[3] – runs when upgrading from db_version 2 → 3. // Same content; idempotent because of IF [NOT] EXISTS. +// $install_queries[4] – runs when upgrading from db_version 3 → 4. +// Adds steam_workshop_server_settings table and default +// behavior columns on steam_workshop_game_profiles. // // Note: the module manager loops $install_queries[$i+1] for each step from // current db_version up to target. Keys 1 and 2 are intentionally absent; @@ -96,8 +117,58 @@ $install_queries[3] = array_merge($_sw_drop_old, $_sw_create_new); unset($_sw_drop_old, $_sw_create_new); +// ── db_version 4: per-server behavior settings + per-profile defaults ──── +// +// New table: steam_workshop_server_settings +// Stores update/restart/hot-load preferences per server home. +// Defaults are safe (manual-only, no auto-restart, hot-load disabled). +// +// Altered table: steam_workshop_game_profiles +// Adds default_update_mode, default_restart_behavior, default_hot_load so +// admins can configure defaults per game profile. +// +// All callables check INFORMATION_SCHEMA before ALTER so this is re-runnable. + +$install_queries[4] = array( + // Create per-server settings table + "CREATE TABLE IF NOT EXISTS `OGP_DB_PREFIXsteam_workshop_server_settings` ( + `home_id` INT NOT NULL, + `update_mode` ENUM('manual','on_restart','before_start','scheduled') + NOT NULL DEFAULT 'manual', + `restart_behavior` ENUM('none','if_empty','immediate','next_restart') + NOT NULL DEFAULT 'none', + `hot_load` ENUM('disabled','attempt') + NOT NULL DEFAULT 'disabled', + `warning_minutes` INT NOT NULL DEFAULT 10, + `schedule_interval` VARCHAR(32) NOT NULL DEFAULT 'daily', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` DATETIME NULL, + PRIMARY KEY (`home_id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci", + + // Add default_update_mode to game_profiles if missing + function($db) { + $r = $db->resultQuery("SELECT COUNT(*) AS cnt FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'OGP_DB_PREFIXsteam_workshop_game_profiles' AND COLUMN_NAME = 'default_update_mode'"); + if ($r && isset($r[0]['cnt']) && (int)$r[0]['cnt'] > 0) return true; + return (bool)$db->query("ALTER TABLE `OGP_DB_PREFIXsteam_workshop_game_profiles` ADD `default_update_mode` ENUM('manual','on_restart','before_start','scheduled') NOT NULL DEFAULT 'manual' AFTER `notes`"); + }, + // Add default_restart_behavior to game_profiles if missing + function($db) { + $r = $db->resultQuery("SELECT COUNT(*) AS cnt FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'OGP_DB_PREFIXsteam_workshop_game_profiles' AND COLUMN_NAME = 'default_restart_behavior'"); + if ($r && isset($r[0]['cnt']) && (int)$r[0]['cnt'] > 0) return true; + return (bool)$db->query("ALTER TABLE `OGP_DB_PREFIXsteam_workshop_game_profiles` ADD `default_restart_behavior` ENUM('none','if_empty','immediate','next_restart') NOT NULL DEFAULT 'none' AFTER `default_update_mode`"); + }, + // Add default_hot_load to game_profiles if missing + function($db) { + $r = $db->resultQuery("SELECT COUNT(*) AS cnt FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'OGP_DB_PREFIXsteam_workshop_game_profiles' AND COLUMN_NAME = 'default_hot_load'"); + if ($r && isset($r[0]['cnt']) && (int)$r[0]['cnt'] > 0) return true; + return (bool)$db->query("ALTER TABLE `OGP_DB_PREFIXsteam_workshop_game_profiles` ADD `default_hot_load` ENUM('disabled','attempt') NOT NULL DEFAULT 'disabled' AFTER `default_restart_behavior`"); + }, +); + // ── Uninstall queries ───────────────────────────────────────────────────── $uninstall_queries = array( + "DROP TABLE IF EXISTS `OGP_DB_PREFIXsteam_workshop_server_settings`", "DROP TABLE IF EXISTS `OGP_DB_PREFIXsteam_workshop_server_mods`", "DROP TABLE IF EXISTS `OGP_DB_PREFIXsteam_workshop_game_profiles`", ); diff --git a/modules/steam_workshop/user.php b/modules/steam_workshop/user.php index 9f31ddd3..6a4b2f1a 100644 --- a/modules/steam_workshop/user.php +++ b/modules/steam_workshop/user.php @@ -73,6 +73,9 @@ function exec_ogp_module() case 'queue_update': sw_user_queue_update($db, $home_id); break; + case 'save_settings': + sw_user_save_settings($db, $home_id); + break; } } @@ -293,6 +296,23 @@ function sw_user_queue_update($db, $home_id) sw_success('All enabled mods queued for update. Run the agent to process downloads.'); } +function sw_user_save_settings($db, $home_id) +{ + $ok = sw_save_server_settings($db, $home_id, array( + 'update_mode' => $_POST['update_mode'] ?? 'manual', + 'restart_behavior' => $_POST['restart_behavior'] ?? 'none', + 'hot_load' => $_POST['hot_load'] ?? 'disabled', + 'warning_minutes' => $_POST['warning_minutes'] ?? 10, + 'schedule_interval' => $_POST['schedule_interval'] ?? 'daily', + )); + + if ($ok) { + sw_success('Workshop behavior settings saved.'); + } else { + sw_error('Failed to save settings.'); + } +} + // ───────────────────────────────────────────────────────────────────────── // Render // ───────────────────────────────────────────────────────────────────────── @@ -300,6 +320,7 @@ function sw_user_queue_update($db, $home_id) function sw_user_render($db, $home_id, array $home, array $profile) { $mods = sw_get_server_mods($db, $home_id) ?: array(); + $settings = sw_get_server_settings($db, $home_id); // Generate launch params from enabled mods $enabled_mods = array_filter($mods, function ($m) { @@ -531,5 +552,121 @@ function sw_user_render($db, $home_id, array $home, array $profile) +


+ + +

Workshop Behavior Settings

+

+ Configure how Workshop mods are installed and updated for this server. + All options default to the safest setting (manual only, no automatic restarts). +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SettingValueHelp
Install / Update Mode + + + Manual only – mods are only updated when you click “Queue Update” above.
+ On next restart – queued updates are applied the next time the server restarts.
+ Before every start – the update check runs automatically each time the server starts.
+ Scheduled – the update check runs on the interval set below (requires cron / agent). +
Restart Behavior + + + Controls what happens when new mod updates are found.
+ Do not restart – updates are staged but the server keeps running (safe default).
+ If empty – the server is restarted only when there are zero players connected.
+ Immediate with warning – a countdown warning is broadcast, then the server restarts.
+ Next manual restart – updates are installed the next time you manually stop/start the server. +
Hot-Load + + + Disabled – no hot-loading; mod changes take effect only after a server restart (safe default).
+ Attempt – if the game supports live mod reloading (e.g. via RCON), try to hot-load instead of restarting. +
Warning Countdown + minutes + + Minutes of advance warning broadcast to players before an automatic restart.
+ Only used when Restart Behavior is set to Restart immediately after warning.
+ Default: 10 minutes. +
Scheduled Check Interval + + + How often the scheduled update check runs.
+ Only used when Install / Update Mode is set to Scheduled update check.
+ Requires the Workshop agent to be running via cron on the game server host. +
+ +

+ +

+
+ Date: Thu, 7 May 2026 00:23:39 +0000 Subject: [PATCH 2/2] fix: minor formatting issues from code review Agent-Logs-Url: https://github.com/GameServerPanel/GSP/sessions/ee35e671-8ff2-43fb-a365-f7a4f9263ca7 Co-authored-by: iaretechnician <2749183+iaretechnician@users.noreply.github.com> --- modules/steam_workshop/admin.php | 6 +++--- modules/steam_workshop/user.php | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/steam_workshop/admin.php b/modules/steam_workshop/admin.php index 4dfb9991..fd3ad9b9 100644 --- a/modules/steam_workshop/admin.php +++ b/modules/steam_workshop/admin.php @@ -131,9 +131,9 @@ function sw_admin_save_profile($db) $posted_um = $_POST['default_update_mode'] ?? 'manual'; $posted_rb = $_POST['default_restart_behavior'] ?? 'none'; $posted_hl = $_POST['default_hot_load'] ?? 'disabled'; - $fields['default_update_mode'] = in_array($posted_um, $valid_update_modes, true) ? $posted_um : 'manual'; - $fields['default_restart_behavior'] = in_array($posted_rb, $valid_restart_behaviors, true) ? $posted_rb : 'none'; - $fields['default_hot_load'] = in_array($posted_hl, $valid_hot_load, true) ? $posted_hl : 'disabled'; + $fields['default_update_mode'] = in_array($posted_um, $valid_update_modes, true) ? $posted_um : 'manual'; + $fields['default_restart_behavior'] = in_array($posted_rb, $valid_restart_behaviors, true) ? $posted_rb : 'none'; + $fields['default_hot_load'] = in_array($posted_hl, $valid_hot_load, true) ? $posted_hl : 'disabled'; $setParts = array(); foreach ($fields as $col => $val) { diff --git a/modules/steam_workshop/user.php b/modules/steam_workshop/user.php index 6a4b2f1a..5ceb90d0 100644 --- a/modules/steam_workshop/user.php +++ b/modules/steam_workshop/user.php @@ -585,7 +585,7 @@ function sw_user_render($db, $home_id, array $home, array $profile)