Simplify Steam Workshop UX and wire panel docs links

Agent-Logs-Url: https://github.com/GameServerPanel/GSP/sessions/1575c81b-f8a7-433a-8f3b-e068c0992c18

Co-authored-by: iaretechnician <2749183+iaretechnician@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-05-08 12:55:27 +00:00 committed by GitHub
parent 5fc301e632
commit 01ad93a11a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 566 additions and 528 deletions

View file

@ -1,5 +1,9 @@
# Changelog # Changelog
## 2026-05-08
- **Steam Workshop reliability + UI simplification:** Removed customer CLI/update scripting instructions from Workshop user flow, reduced per-server behavior options to supported modes only, switched remaining Steam Workshop SQL references to prefix helpers (no `OGP_DB_PREFIX` strings), hardened queued-update agent processing (`queued → updating → installed/failed`) with clearer error persistence, and refreshed monitor/support documentation links to open game-specific docs (fallback to docs index) in a new tab.
- **Billing docs routing refresh:** Updated docs browser links/icons to root-relative storefront paths (`/docs.php`, `/docs/...`) and removed stale hardcoded panel host guidance from getting-started documentation.
## 2026-05-07 ## 2026-05-07
- **README + storefront mobile/cart pricing fixes:** Rewrote the root README for GSP positioning, hardened storefront mobile responsiveness (login, order, cart, shared header guardrails), fixed add-to-cart price persistence for low-decimal paid items, aligned cart/free-checkout math to `total_due` values, and refreshed the canonical storefront footer timestamp. - **README + storefront mobile/cart pricing fixes:** Rewrote the root README for GSP positioning, hardened storefront mobile responsiveness (login, order, cart, shared header guardrails), fixed add-to-cart price persistence for low-decimal paid items, aligned cart/free-checkout math to `total_due` values, and refreshed the canonical storefront footer timestamp.
- **Billing/cart/storefront stability pass:** Hardened `add_to_cart.php` to build schema-compatible invoice inserts dynamically (including legacy installs missing `period_start`), fixed free-checkout DB close handling so wrapper objects are never passed to `mysqli_close()`, switched cart/free-total decisions to cent-based math so low nonzero prices (e.g. $0.02) never show as FREE, improved canonical game deduplication + OS variant matching in storefront list/order pages, and aligned Steam Workshop behavior labels with the new restart/update wording. - **Billing/cart/storefront stability pass:** Hardened `add_to_cart.php` to build schema-compatible invoice inserts dynamically (including legacy installs missing `period_start`), fixed free-checkout DB close handling so wrapper objects are never passed to `mysqli_close()`, switched cart/free-total decisions to cent-based math so low nonzero prices (e.g. $0.02) never show as FREE, improved canonical game deduplication + OS variant matching in storefront list/order pages, and aligned Steam Workshop behavior labels with the new restart/update wording.

View file

@ -7,3 +7,4 @@
- Add a side-by-side before/after diff preview panel to the config_games top-level XML section editor before section saves. - Add a side-by-side before/after diff preview panel to the config_games top-level XML section editor before section saves.
- Add an integration smoke test that exercises paid checkout, free checkout, and add-to-cart on installs with/without `period_start` to prevent billing schema drift regressions. - Add an integration smoke test that exercises paid checkout, free checkout, and add-to-cart on installs with/without `period_start` to prevent billing schema drift regressions.
- Add a storefront visual-regression check at 375px and 430px breakpoints covering login, order, and cart pages to prevent mobile overflow regressions. - Add a storefront visual-regression check at 375px and 430px breakpoints covering login, order, and cart pages to prevent mobile overflow regressions.
- Complete a full pass over all `modules/billing/docs/*` game guides to standardize OS/Workshop/RCON capability statements against current XML-backed server support.

View file

@ -19,6 +19,7 @@ $docsDir = __DIR__ . '/docs';
// Get action and doc parameters // Get action and doc parameters
$action = $_GET['action'] ?? 'list'; $action = $_GET['action'] ?? 'list';
$doc = $_GET['doc'] ?? ''; $doc = $_GET['doc'] ?? '';
$docsPagePath = '/docs.php';
/** /**
* Get all documentation folders with their metadata * Get all documentation folders with their metadata
@ -63,9 +64,9 @@ function getDocCategories($docsDir) {
// Find icon file // Find icon file
$icon = ''; $icon = '';
if (file_exists($folderPath . '/icon.png')) { if (file_exists($folderPath . '/icon.png')) {
$icon = 'docs/' . $folder . '/icon.png'; $icon = '/docs/' . $folder . '/icon.png';
} elseif (file_exists($folderPath . '/icon.jpg')) { } elseif (file_exists($folderPath . '/icon.jpg')) {
$icon = 'docs/' . $folder . '/icon.jpg'; $icon = '/docs/' . $folder . '/icon.jpg';
} }
$categories[] = [ $categories[] = [
@ -137,7 +138,7 @@ uksort($grouped, function($a, $b) use ($categoryOrder) {
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate"> <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache"> <meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0"> <meta http-equiv="Expires" content="0">
<title><?php echo $action === 'view' ? 'Documentation' : 'Documentation - GameServers.World'; ?></title> <title><?php echo $action === 'view' ? 'Documentation' : 'Documentation - GSP'; ?></title>
<link rel="stylesheet" href="css/header.css"> <link rel="stylesheet" href="css/header.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw==" crossorigin="anonymous" referrerpolicy="no-referrer" /> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<style> <style>
@ -351,7 +352,7 @@ uksort($grouped, function($a, $b) use ($categoryOrder) {
<div class="container"> <div class="container">
<?php if ($action === 'view' && !empty($doc)): ?> <?php if ($action === 'view' && !empty($doc)): ?>
<!-- View specific documentation --> <!-- View specific documentation -->
<a href="docs.php" class="back-button"> Back to Documentation List</a> <a href="<?php echo htmlspecialchars($docsPagePath, ENT_QUOTES, 'UTF-8'); ?>" class="back-button"> Back to Documentation List</a>
<div class="doc-view-container"> <div class="doc-view-container">
<?php <?php
@ -401,7 +402,7 @@ uksort($grouped, function($a, $b) use ($categoryOrder) {
<div class="docs-grid"> <div class="docs-grid">
<?php foreach ((array)$docs as $doc): ?> <?php foreach ((array)$docs as $doc): ?>
<a href="docs.php?action=view&doc=<?php echo urlencode($doc['folder']); ?>" class="doc-card"> <a href="<?php echo htmlspecialchars($docsPagePath . '?action=view&doc=' . urlencode($doc['folder']), ENT_QUOTES, 'UTF-8'); ?>" class="doc-card">
<div class="doc-icon-wrapper"> <div class="doc-icon-wrapper">
<?php if (!empty($doc['icon'])): ?> <?php if (!empty($doc['icon'])): ?>
<img src="<?php echo htmlspecialchars($doc['icon']); ?>" alt="" class="doc-icon"> <img src="<?php echo htmlspecialchars($doc['icon']); ?>" alt="" class="doc-icon">
@ -428,4 +429,3 @@ uksort($grouped, function($a, $b) use ($categoryOrder) {
</div> </div>
</body> </body>
</html> </html>

View file

@ -12,7 +12,7 @@
<p>Once your payment is processed, you'll receive:</p> <p>Once your payment is processed, you'll receive:</p>
<ul> <ul>
<li>A confirmation email with your server details</li> <li>A confirmation email with your server details</li>
<li>Access to the control panel at <a href="http://panel.iaregamer.com" target="_blank">panel.iaregamer.com</a></li> <li>Access to your control panel login page</li>
<li>FTP credentials for file management</li> <li>FTP credentials for file management</li>
<li>Server IP address and port</li> <li>Server IP address and port</li>
</ul> </ul>
@ -21,7 +21,7 @@
<h3>Control Panel</h3> <h3>Control Panel</h3>
<ol> <ol>
<li>Visit <a href="http://panel.iaregamer.com" target="_blank">panel.iaregamer.com</a></li> <li>Open your panel login URL (provided in your service welcome message)</li>
<li>Log in with your account credentials</li> <li>Log in with your account credentials</li>
<li>Select your server from "My Servers"</li> <li>Select your server from "My Servers"</li>
<li>Use the control panel to start, stop, restart, and configure your server</li> <li>Use the control panel to start, stop, restart, and configure your server</li>
@ -80,7 +80,7 @@
<ul> <ul>
<li>Check the game-specific documentation for your server type</li> <li>Check the game-specific documentation for your server type</li>
<li>Review the troubleshooting guides</li> <li>Review the troubleshooting guides</li>
<li>Contact support through your account dashboard</li> <li>Open a support request from the panel support page</li>
<li>Check our community forums for tips and solutions</li> <li>Check our community forums for tips and solutions</li>
</ul> </ul>

View file

@ -1 +1 @@
Last Updated at 12:44pm on 2026-05-07 Last Updated at 12:47pm on 2026-05-08

View file

@ -154,6 +154,18 @@ function get_sync_name($server_xml)
return $sync_name; return $sync_name;
} }
function gsp_docs_url_for_game_key($game_key)
{
$game_key = trim((string)$game_key);
if ($game_key !== '') {
$docPath = __DIR__ . '/../billing/docs/' . $game_key . '/index.php';
if (is_file($docPath)) {
return '/docs.php?action=view&doc=' . rawurlencode($game_key);
}
}
return '/docs.php';
}
function exec_ogp_module() { function exec_ogp_module() {
global $db, $settings, $loggedInUserInfo; global $db, $settings, $loggedInUserInfo;
echo "<h2 class='gameMonitor " . ($db->isAdmin( $_SESSION['user_id'] ) ? "isAdminUser" : "") . "'>". get_lang("game_monitor") ."</h2>"; echo "<h2 class='gameMonitor " . ($db->isAdmin( $_SESSION['user_id'] ) ? "isAdminUser" : "") . "'>". get_lang("game_monitor") ."</h2>";
@ -260,6 +272,15 @@ $home_info = $db->getGameHomeWithoutMods($home_id);
$show_all = FALSE; $show_all = FALSE;
} }
$docsTarget = '/docs.php';
if (is_array($home_info) && !empty($home_info['game_key'])) {
$docsTarget = gsp_docs_url_for_game_key($home_info['game_key']);
}
echo "<div style='margin:10px 0 16px 0;padding:10px 12px;border:1px solid #2d2d2d;border-radius:6px;background:#171717;'>"
. "<a href='" . htmlspecialchars($docsTarget, ENT_QUOTES, 'UTF-8') . "' target='_blank' rel='noopener noreferrer' style='color:#8cb9ff;text-decoration:none;font-weight:600;'>Game Documentation</a>"
. "<span style='color:#a9a9a9;margin-left:8px;'>Opens setup and troubleshooting docs in a new tab.</span>"
. "</div>";
require("protocol/lgsl/lgsl_protocol.php"); require("protocol/lgsl/lgsl_protocol.php");
$info = $db->getUserById($_SESSION['user_id']); $info = $db->getUserById($_SESSION['user_id']);

View file

@ -125,15 +125,12 @@ function sw_admin_save_profile($db)
); );
// Per-profile default behavior fields // Per-profile default behavior fields
$valid_update_modes = array('manual', 'on_restart', 'before_start', 'scheduled'); $valid_update_modes = array('manual', 'on_restart', 'before_start');
$valid_restart_behaviors = array('none', 'if_empty', 'immediate', 'next_restart'); $valid_restart_behaviors = array('none', 'if_stopped');
$valid_hot_load = array('disabled', 'attempt');
$posted_um = $_POST['default_update_mode'] ?? 'manual'; $posted_um = $_POST['default_update_mode'] ?? 'manual';
$posted_rb = $_POST['default_restart_behavior'] ?? 'none'; $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_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_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(); $setParts = array();
foreach ($fields as $col => $val) { foreach ($fields as $col => $val) {
@ -307,7 +304,7 @@ function sw_admin_edit_form(array $profile, array $detected = array(), $showDete
<p class="sw-muted"> <p class="sw-muted">
These defaults are applied when a user enables Workshop on a server that has no saved behavior settings yet. 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. 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). All defaults are intentionally set to the safest option (manual / no automatic restart).
</p> </p>
<div class="sw-grid"> <div class="sw-grid">
<label> <label>
@ -316,23 +313,13 @@ function sw_admin_edit_form(array $profile, array $detected = array(), $showDete
<option value="manual" <?= (($profile['default_update_mode'] ?? 'manual') === 'manual') ? 'selected' : '' ?>>Manual only (safe default)</option> <option value="manual" <?= (($profile['default_update_mode'] ?? 'manual') === 'manual') ? 'selected' : '' ?>>Manual only (safe default)</option>
<option value="on_restart" <?= (($profile['default_update_mode'] ?? 'manual') === 'on_restart') ? 'selected' : '' ?>>On next restart</option> <option value="on_restart" <?= (($profile['default_update_mode'] ?? 'manual') === 'on_restart') ? 'selected' : '' ?>>On next restart</option>
<option value="before_start" <?= (($profile['default_update_mode'] ?? 'manual') === 'before_start') ? 'selected' : '' ?>>Before every server start</option> <option value="before_start" <?= (($profile['default_update_mode'] ?? 'manual') === 'before_start') ? 'selected' : '' ?>>Before every server start</option>
<option value="scheduled" <?= (($profile['default_update_mode'] ?? 'manual') === 'scheduled') ? 'selected' : '' ?>>Scheduled update check</option>
</select> </select>
</label> </label>
<label> <label>
<span>Default Restart Behavior</span> <span>Default Restart Behavior</span>
<select name="default_restart_behavior"> <select name="default_restart_behavior">
<option value="none" <?= (($profile['default_restart_behavior'] ?? 'none') === 'none') ? 'selected' : '' ?>>Do not restart automatically (safe default)</option> <option value="none" <?= (($profile['default_restart_behavior'] ?? 'none') === 'none') ? 'selected' : '' ?>>Do not restart automatically (safe default)</option>
<option value="if_empty" <?= (($profile['default_restart_behavior'] ?? 'none') === 'if_empty') ? 'selected' : '' ?>>Restart if empty</option> <option value="if_stopped" <?= (($profile['default_restart_behavior'] ?? 'none') === 'if_stopped') ? 'selected' : '' ?>>Restart only if server is stopped</option>
<option value="immediate" <?= (($profile['default_restart_behavior'] ?? 'none') === 'immediate') ? 'selected' : '' ?>>Restart after warning</option>
<option value="next_restart" <?= (($profile['default_restart_behavior'] ?? 'none') === 'next_restart') ? 'selected' : '' ?>>Install on next manual restart only</option>
</select>
</label>
<label>
<span>Default Hot-Load</span>
<select name="default_hot_load">
<option value="disabled" <?= (($profile['default_hot_load'] ?? 'disabled') === 'disabled') ? 'selected' : '' ?>>Disabled (safe default)</option>
<option value="attempt" <?= (($profile['default_hot_load'] ?? 'disabled') === 'attempt') ? 'selected' : '' ?>>Attempt hot-load if game supports it</option>
</select> </select>
</label> </label>
</div> </div>

View file

@ -87,12 +87,12 @@ if ($dry_run) {
// ── Collect home IDs to process ─────────────────────────────────────────── // ── Collect home IDs to process ───────────────────────────────────────────
if ($do_all) { if ($do_all) {
// Find all home_ids that have at least one enabled mod with a valid enabled profile. // Find all home_ids that have at least one enabled queued mod with an enabled profile.
$rows = $db->resultQuery( $rows = $db->resultQuery(
"SELECT DISTINCT m.home_id "SELECT DISTINCT m.home_id
FROM `OGP_DB_PREFIXsteam_workshop_server_mods` m FROM " . sw_table('steam_workshop_server_mods') . " m
JOIN `OGP_DB_PREFIXsteam_workshop_game_profiles` p ON p.id = m.profile_id JOIN " . sw_table('steam_workshop_game_profiles') . " p ON p.id = m.profile_id
WHERE m.enabled = 1 AND p.enabled = 1" WHERE m.enabled = 1 AND p.enabled = 1 AND m.install_status = 'queued'"
); );
$home_ids = $rows ? array_column($rows, 'home_id') : array(); $home_ids = $rows ? array_column($rows, 'home_id') : array();
} else { } else {
@ -159,14 +159,11 @@ function sw_agent_process_home($db, $home_id, $dry_run)
); );
$server_root = rtrim($server_root, '/'); $server_root = rtrim($server_root, '/');
// Load enabled mods, sorted by sort_order // Load queued+enabled mods, sorted by sort_order
$mods = sw_get_server_mods($db, $home_id) ?: array(); $mods = sw_agent_get_queued_mods($db, $home_id);
$mods = array_filter($mods, function ($m) {
return !empty($m['enabled']);
});
if (empty($mods)) { if (empty($mods)) {
echo " No enabled mods.\n"; echo " No queued Workshop updates for this server.\n";
return true; return true;
} }
@ -185,7 +182,7 @@ function sw_agent_process_home($db, $home_id, $dry_run)
// Mark as updating // Mark as updating
if (!$dry_run) { if (!$dry_run) {
$db->query( $db->query(
"UPDATE `OGP_DB_PREFIXsteam_workshop_server_mods` "UPDATE " . sw_table('steam_workshop_server_mods') . "
SET `install_status` = 'updating', `last_error` = NULL, `updated_at` = NOW() SET `install_status` = 'updating', `last_error` = NULL, `updated_at` = NOW()
WHERE `id` = $mod_id LIMIT 1" WHERE `id` = $mod_id LIMIT 1"
); );
@ -224,7 +221,7 @@ function sw_agent_process_home($db, $home_id, $dry_run)
if (!$dry_run) { if (!$dry_run) {
$safe_err = $db->realEscapeSingle($err); $safe_err = $db->realEscapeSingle($err);
$db->query( $db->query(
"UPDATE `OGP_DB_PREFIXsteam_workshop_server_mods` "UPDATE " . sw_table('steam_workshop_server_mods') . "
SET `install_status` = 'failed', `last_error` = '$safe_err', `updated_at` = NOW() SET `install_status` = 'failed', `last_error` = '$safe_err', `updated_at` = NOW()
WHERE `id` = $mod_id LIMIT 1" WHERE `id` = $mod_id LIMIT 1"
); );
@ -246,7 +243,7 @@ function sw_agent_process_home($db, $home_id, $dry_run)
if (!$dry_run) { if (!$dry_run) {
$safe_err = $db->realEscapeSingle($err); $safe_err = $db->realEscapeSingle($err);
$db->query( $db->query(
"UPDATE `OGP_DB_PREFIXsteam_workshop_server_mods` "UPDATE " . sw_table('steam_workshop_server_mods') . "
SET `install_status` = 'failed', `last_error` = '$safe_err', `updated_at` = NOW() SET `install_status` = 'failed', `last_error` = '$safe_err', `updated_at` = NOW()
WHERE `id` = $mod_id LIMIT 1" WHERE `id` = $mod_id LIMIT 1"
); );
@ -263,7 +260,7 @@ function sw_agent_process_home($db, $home_id, $dry_run)
// 4. Mark as installed // 4. Mark as installed
if (!$dry_run) { if (!$dry_run) {
$db->query( $db->query(
"UPDATE `OGP_DB_PREFIXsteam_workshop_server_mods` "UPDATE " . sw_table('steam_workshop_server_mods') . "
SET `install_status` = 'installed', SET `install_status` = 'installed',
`last_installed_at` = NOW(), `last_installed_at` = NOW(),
`last_updated_at` = NOW(), `last_updated_at` = NOW(),
@ -276,27 +273,22 @@ function sw_agent_process_home($db, $home_id, $dry_run)
echo " [OK] Installed → $install_path\n"; echo " [OK] Installed → $install_path\n";
} }
// 5. Print launch parameters
$enabled_mods = array_values(array_filter(
sw_get_server_mods($db, $home_id) ?: array(),
function ($m) { return !empty($m['enabled']); }
));
$params = sw_generate_launch_params($enabled_mods, $profile);
echo "\n Generated launch parameters:\n";
if ($params['mod']) {
echo " " . $params['mod'] . "\n";
}
if ($params['servermod']) {
echo " " . $params['servermod'] . "\n";
}
if (!$params['mod'] && !$params['servermod']) {
echo " (none)\n";
}
return $all_ok; return $all_ok;
} }
function sw_agent_get_queued_mods($db, $home_id)
{
$home_id = (int)$home_id;
$rows = $db->resultQuery(
"SELECT * FROM " . sw_table('steam_workshop_server_mods') . "
WHERE `home_id` = $home_id
AND `enabled` = 1
AND `install_status` = 'queued'
ORDER BY `sort_order` ASC, `id` ASC"
);
return $rows ? $rows : array();
}
/** /**
* Build the standard template variable map for a given home + profile. * Build the standard template variable map for a given home + profile.
* *
@ -361,7 +353,7 @@ function sw_agent_steamcmd_download(array $mod, array $profile, array $tpl_vars,
} }
// Validate that steamcmd exists // Validate that steamcmd exists
if (!$dry_run && !is_file($steamcmd) && !is_executable($steamcmd)) { if (!$dry_run && (!is_file($steamcmd) || !is_executable($steamcmd))) {
return array('ok' => false, 'error' => "SteamCMD not found or not executable: $steamcmd"); return array('ok' => false, 'error' => "SteamCMD not found or not executable: $steamcmd");
} }

View file

@ -359,17 +359,54 @@ function sw_get_server_settings($db, $home_id)
"SELECT * FROM " . sw_table('steam_workshop_server_settings') . " "SELECT * FROM " . sw_table('steam_workshop_server_settings') . "
WHERE `home_id` = $home_id LIMIT 1" WHERE `home_id` = $home_id LIMIT 1"
); );
if ($rows && isset($rows[0])) { if ($rows && isset($rows[0]) && is_array($rows[0])) {
return $rows[0]; $settings = $rows[0];
$legacyUpdateMap = array(
'scheduled' => 'manual',
);
$legacyRestartMap = array(
'if_empty' => 'if_stopped',
'next_restart' => 'if_stopped',
'immediate' => 'none',
);
$legacyScheduleMap = array(
'hourly' => 'daily',
);
if (isset($legacyUpdateMap[$settings['update_mode'] ?? ''])) {
$settings['update_mode'] = $legacyUpdateMap[$settings['update_mode']];
}
if (isset($legacyRestartMap[$settings['restart_behavior'] ?? ''])) {
$settings['restart_behavior'] = $legacyRestartMap[$settings['restart_behavior']];
}
if (isset($legacyScheduleMap[$settings['schedule_interval'] ?? ''])) {
$settings['schedule_interval'] = $legacyScheduleMap[$settings['schedule_interval']];
}
$validUpdateModes = array('manual', 'on_restart', 'before_start');
$validRestartBehaviors = array('none', 'if_stopped');
$validIntervals = array('disabled', 'daily', 'weekly');
if (!in_array($settings['update_mode'] ?? '', $validUpdateModes, true)) {
$settings['update_mode'] = 'manual';
}
if (!in_array($settings['restart_behavior'] ?? '', $validRestartBehaviors, true)) {
$settings['restart_behavior'] = 'none';
}
if (!in_array($settings['schedule_interval'] ?? '', $validIntervals, true)) {
$settings['schedule_interval'] = 'disabled';
}
return $settings;
} }
// Safe defaults manual only, no automatic restarts, hot-load off
// Safe defaults manual only, no automatic restarts, schedule disabled
return array( return array(
'home_id' => $home_id, 'home_id' => $home_id,
'update_mode' => 'manual', 'update_mode' => 'manual',
'restart_behavior' => 'none', 'restart_behavior' => 'none',
'hot_load' => 'disabled', 'schedule_interval' => 'disabled',
'warning_minutes' => 10,
'schedule_interval' => 'daily',
); );
} }
@ -378,43 +415,38 @@ function sw_get_server_settings($db, $home_id)
* *
* @param OGPDatabase $db * @param OGPDatabase $db
* @param int $home_id * @param int $home_id
* @param array $data keys: update_mode, restart_behavior, hot_load, * @param array $data keys: update_mode, restart_behavior, schedule_interval
* warning_minutes, schedule_interval
* @return bool * @return bool
*/ */
function sw_save_server_settings($db, $home_id, array $data) function sw_save_server_settings($db, $home_id, array $data)
{ {
$home_id = (int)$home_id; $home_id = (int)$home_id;
$valid_update_modes = array('manual', 'on_restart', 'before_start', 'scheduled'); $valid_update_modes = array('manual', 'on_restart', 'before_start');
$valid_restart_behaviors = array('none', 'if_empty', 'immediate', 'next_restart'); $valid_restart_behaviors = array('none', 'if_stopped');
$valid_hot_load = array('disabled', 'attempt'); $valid_intervals = array('disabled', 'daily', 'weekly');
$valid_intervals = array('hourly', 'daily', 'weekly');
$update_mode = in_array($data['update_mode'] ?? '', $valid_update_modes, true) ? $data['update_mode'] : 'manual'; $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'; $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'; $schedule_interval = in_array($data['schedule_interval'] ?? '', $valid_intervals, true) ? $data['schedule_interval'] : '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_um = $db->realEscapeSingle($update_mode);
$safe_rb = $db->realEscapeSingle($restart_behavior); $safe_rb = $db->realEscapeSingle($restart_behavior);
$safe_hl = $db->realEscapeSingle($hot_load);
$safe_si = $db->realEscapeSingle($schedule_interval); $safe_si = $db->realEscapeSingle($schedule_interval);
return (bool)$db->query( return (bool)$db->query(
"INSERT INTO " . sw_table('steam_workshop_server_settings') . " "INSERT INTO " . sw_table('steam_workshop_server_settings') . "
(`home_id`, `update_mode`, `restart_behavior`, `hot_load`, (`home_id`, `update_mode`, `restart_behavior`, `hot_load`,
`warning_minutes`, `schedule_interval`, `created_at`, `updated_at`) `warning_minutes`, `schedule_interval`, `created_at`, `updated_at`)
VALUES ($home_id, '$safe_um', '$safe_rb', '$safe_hl', VALUES ($home_id, '$safe_um', '$safe_rb', 'disabled',
$warning_minutes, '$safe_si', NOW(), NOW()) 0, '$safe_si', NOW(), NOW())
ON DUPLICATE KEY UPDATE ON DUPLICATE KEY UPDATE
`update_mode` = '$safe_um', `update_mode` = '$safe_um',
`restart_behavior` = '$safe_rb', `restart_behavior` = '$safe_rb',
`hot_load` = '$safe_hl', `hot_load` = 'disabled',
`warning_minutes` = $warning_minutes, `warning_minutes` = 0,
`schedule_interval` = '$safe_si', `schedule_interval` = '$safe_si',
`updated_at` = NOW()" `updated_at` = NOW()"
); );
} }

View file

@ -9,29 +9,55 @@
* (at your option) any later version. * (at your option) any later version.
*/ */
// ── Module metadata ──────────────────────────────────────────────────────
$module_title = "Steam Workshop"; $module_title = "Steam Workshop";
$module_version = "3.1"; $module_version = "3.2";
$db_version = 4; $db_version = 5;
$module_required = FALSE; $module_required = FALSE;
$module_menus = array( $module_menus = array(
array('subpage' => 'admin', 'name' => 'Steam Workshop', 'group' => 'admin'), array('subpage' => 'admin', 'name' => 'Steam Workshop', 'group' => 'admin'),
); );
// ── SQL helpers ────────────────────────────────────────────────────────── if (!function_exists('sw_module_db_prefix')) {
// All OGP_DB_PREFIX tokens are replaced at runtime by $db->query() / function sw_module_db_prefix()
// $db->resultQuery() before the SQL reaches MySQL. Do not replace them {
// here with literal strings. if (defined('DB_PREFIX') && DB_PREFIX !== '') {
return DB_PREFIX;
}
if (isset($GLOBALS['db_prefix']) && $GLOBALS['db_prefix'] !== '') {
return $GLOBALS['db_prefix'];
}
if (isset($GLOBALS['table_prefix']) && $GLOBALS['table_prefix'] !== '') {
return $GLOBALS['table_prefix'];
}
return 'gsp_';
}
}
$_sw_drop_old = array( if (!function_exists('sw_module_table')) {
"DROP TABLE IF EXISTS `OGP_DB_PREFIXworkshop_game_profiles`", function sw_module_table($table)
"DROP TABLE IF EXISTS `OGP_DB_PREFIXworkshop_cache`", {
"DROP TABLE IF EXISTS `OGP_DB_PREFIXserver_workshop_mods`", return '`' . sw_module_db_prefix() . $table . '`';
"DROP TABLE IF EXISTS `OGP_DB_PREFIXserver_workshop_settings`", }
}
if (!function_exists('sw_module_table_name')) {
function sw_module_table_name($table)
{
return sw_module_db_prefix() . $table;
}
}
$install_queries = array();
$legacyDrops = array(
"DROP TABLE IF EXISTS " . sw_module_table('workshop_game_profiles'),
"DROP TABLE IF EXISTS " . sw_module_table('workshop_cache'),
"DROP TABLE IF EXISTS " . sw_module_table('server_workshop_mods'),
"DROP TABLE IF EXISTS " . sw_module_table('server_workshop_settings'),
); );
$_sw_create_new = array( $schemaCreate = array(
"CREATE TABLE IF NOT EXISTS `OGP_DB_PREFIXsteam_workshop_game_profiles` ( "CREATE TABLE IF NOT EXISTS " . sw_module_table('steam_workshop_game_profiles') . " (
`id` INT NOT NULL AUTO_INCREMENT, `id` INT NOT NULL AUTO_INCREMENT,
`config_name` VARCHAR(100) NOT NULL, `config_name` VARCHAR(100) NOT NULL,
`game_name` VARCHAR(255) NOT NULL DEFAULT '', `game_name` VARCHAR(255) NOT NULL DEFAULT '',
@ -51,8 +77,8 @@ $_sw_create_new = array(
`update_script_template` TEXT NULL, `update_script_template` TEXT NULL,
`copy_bikeys_enabled` TINYINT(1) NOT NULL DEFAULT 1, `copy_bikeys_enabled` TINYINT(1) NOT NULL DEFAULT 1,
`notes` TEXT NULL, `notes` TEXT NULL,
`default_update_mode` ENUM('manual','on_restart','before_start','scheduled') NOT NULL DEFAULT 'manual', `default_update_mode` ENUM('manual','on_restart','before_start') NOT NULL DEFAULT 'manual',
`default_restart_behavior` ENUM('none','if_empty','immediate','next_restart') NOT NULL DEFAULT 'none', `default_restart_behavior` ENUM('none','if_stopped') NOT NULL DEFAULT 'none',
`default_hot_load` ENUM('disabled','attempt') NOT NULL DEFAULT 'disabled', `default_hot_load` ENUM('disabled','attempt') NOT NULL DEFAULT 'disabled',
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME NULL, `updated_at` DATETIME NULL,
@ -60,7 +86,7 @@ $_sw_create_new = array(
UNIQUE KEY `uniq_config_name` (`config_name`) UNIQUE KEY `uniq_config_name` (`config_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci", ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
"CREATE TABLE IF NOT EXISTS `OGP_DB_PREFIXsteam_workshop_server_mods` ( "CREATE TABLE IF NOT EXISTS " . sw_module_table('steam_workshop_server_mods') . " (
`id` INT NOT NULL AUTO_INCREMENT, `id` INT NOT NULL AUTO_INCREMENT,
`home_id` INT NOT NULL, `home_id` INT NOT NULL,
`profile_id` INT NOT NULL, `profile_id` INT NOT NULL,
@ -80,95 +106,133 @@ $_sw_create_new = array(
UNIQUE KEY `uniq_home_workshop` (`home_id`, `workshop_id`) UNIQUE KEY `uniq_home_workshop` (`home_id`, `workshop_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci", ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
"CREATE TABLE IF NOT EXISTS `OGP_DB_PREFIXsteam_workshop_server_settings` ( "CREATE TABLE IF NOT EXISTS " . sw_module_table('steam_workshop_server_settings') . " (
`home_id` INT NOT NULL, `home_id` INT NOT NULL,
`update_mode` ENUM('manual','on_restart','before_start','scheduled') `update_mode` ENUM('manual','on_restart','before_start')
NOT NULL DEFAULT 'manual', NOT NULL DEFAULT 'manual',
`restart_behavior` ENUM('none','if_empty','immediate','next_restart') `restart_behavior` ENUM('none','if_stopped')
NOT NULL DEFAULT 'none', NOT NULL DEFAULT 'none',
`hot_load` ENUM('disabled','attempt') `hot_load` ENUM('disabled','attempt')
NOT NULL DEFAULT 'disabled', NOT NULL DEFAULT 'disabled',
`warning_minutes` INT NOT NULL DEFAULT 10, `warning_minutes` INT NOT NULL DEFAULT 0,
`schedule_interval` VARCHAR(32) NOT NULL DEFAULT 'daily', `schedule_interval` ENUM('disabled','daily','weekly') NOT NULL DEFAULT 'disabled',
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME NULL, `updated_at` DATETIME NULL,
PRIMARY KEY (`home_id`) PRIMARY KEY (`home_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci", ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
); );
// ── Install queries ────────────────────────────────────────────────────── $install_queries[0] = array_merge($legacyDrops, $schemaCreate);
// $install_queries[3] = array_merge($legacyDrops, $schemaCreate);
// $install_queries[0] runs on fresh install (module manager iterates all keys). $install_queries[4] = 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;
// the manager safely skips undefined keys (PHP returns NULL → empty array).
$install_queries = array(); $install_queries[5] = array(
function ($db) {
$table = sw_module_table_name('steam_workshop_server_settings');
$exists = $db->resultQuery(
"SELECT COUNT(*) AS cnt FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = '" . $db->realEscapeSingle($table) . "'"
);
if (!$exists || (int)($exists[0]['cnt'] ?? 0) === 0) {
return (bool)$db->query(
"CREATE TABLE IF NOT EXISTS " . sw_module_table('steam_workshop_server_settings') . " (
`home_id` INT NOT NULL,
`update_mode` ENUM('manual','on_restart','before_start') NOT NULL DEFAULT 'manual',
`restart_behavior` ENUM('none','if_stopped') NOT NULL DEFAULT 'none',
`hot_load` ENUM('disabled','attempt') NOT NULL DEFAULT 'disabled',
`warning_minutes` INT NOT NULL DEFAULT 0,
`schedule_interval` ENUM('disabled','daily','weekly') NOT NULL DEFAULT 'disabled',
`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[0] = array_merge($_sw_drop_old, $_sw_create_new); $ok = true;
$install_queries[3] = array_merge($_sw_drop_old, $_sw_create_new); $ok = $ok && (bool)$db->query(
"UPDATE " . sw_module_table('steam_workshop_server_settings') . "
SET `update_mode` = CASE
WHEN `update_mode` = 'scheduled' THEN 'manual'
ELSE `update_mode`
END"
);
$ok = $ok && (bool)$db->query(
"UPDATE " . sw_module_table('steam_workshop_server_settings') . "
SET `restart_behavior` = CASE
WHEN `restart_behavior` IN ('if_empty','next_restart') THEN 'if_stopped'
WHEN `restart_behavior` = 'immediate' THEN 'none'
ELSE `restart_behavior`
END"
);
$ok = $ok && (bool)$db->query(
"UPDATE " . sw_module_table('steam_workshop_server_settings') . "
SET `schedule_interval` = CASE
WHEN `schedule_interval` = 'hourly' THEN 'daily'
WHEN `schedule_interval` IS NULL OR `schedule_interval` = '' THEN 'disabled'
ELSE `schedule_interval`
END"
);
$ok = $ok && (bool)$db->query(
"UPDATE " . sw_module_table('steam_workshop_server_settings') . "
SET `hot_load` = 'disabled', `warning_minutes` = 0"
);
$ok = $ok && (bool)$db->query(
"ALTER TABLE " . sw_module_table('steam_workshop_server_settings') . "
MODIFY `update_mode` ENUM('manual','on_restart','before_start') NOT NULL DEFAULT 'manual'"
);
$ok = $ok && (bool)$db->query(
"ALTER TABLE " . sw_module_table('steam_workshop_server_settings') . "
MODIFY `restart_behavior` ENUM('none','if_stopped') NOT NULL DEFAULT 'none'"
);
$ok = $ok && (bool)$db->query(
"ALTER TABLE " . sw_module_table('steam_workshop_server_settings') . "
MODIFY `schedule_interval` ENUM('disabled','daily','weekly') NOT NULL DEFAULT 'disabled'"
);
unset($_sw_drop_old, $_sw_create_new); return $ok;
// ── 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) {
function($db) { $profileTable = sw_module_table_name('steam_workshop_game_profiles');
$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'"); $exists = $db->resultQuery(
if ($r && isset($r[0]['cnt']) && (int)$r[0]['cnt'] > 0) return true; "SELECT COUNT(*) AS cnt FROM INFORMATION_SCHEMA.TABLES
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`"); WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = '" . $db->realEscapeSingle($profileTable) . "'"
}, );
// Add default_hot_load to game_profiles if missing if (!$exists || (int)($exists[0]['cnt'] ?? 0) === 0) {
function($db) { return true;
$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`"); $ok = true;
$ok = $ok && (bool)$db->query(
"UPDATE " . sw_module_table('steam_workshop_game_profiles') . "
SET `default_update_mode` = CASE
WHEN `default_update_mode` = 'scheduled' THEN 'manual'
ELSE `default_update_mode`
END"
);
$ok = $ok && (bool)$db->query(
"UPDATE " . sw_module_table('steam_workshop_game_profiles') . "
SET `default_restart_behavior` = CASE
WHEN `default_restart_behavior` IN ('if_empty','next_restart') THEN 'if_stopped'
WHEN `default_restart_behavior` = 'immediate' THEN 'none'
ELSE `default_restart_behavior`
END"
);
$ok = $ok && (bool)$db->query(
"ALTER TABLE " . sw_module_table('steam_workshop_game_profiles') . "
MODIFY `default_update_mode` ENUM('manual','on_restart','before_start') NOT NULL DEFAULT 'manual'"
);
$ok = $ok && (bool)$db->query(
"ALTER TABLE " . sw_module_table('steam_workshop_game_profiles') . "
MODIFY `default_restart_behavior` ENUM('none','if_stopped') NOT NULL DEFAULT 'none'"
);
return $ok;
}, },
); );
// ── Uninstall queries ─────────────────────────────────────────────────────
$uninstall_queries = array( $uninstall_queries = array(
"DROP TABLE IF EXISTS `OGP_DB_PREFIXsteam_workshop_server_settings`", "DROP TABLE IF EXISTS " . sw_module_table('steam_workshop_server_settings'),
"DROP TABLE IF EXISTS `OGP_DB_PREFIXsteam_workshop_server_mods`", "DROP TABLE IF EXISTS " . sw_module_table('steam_workshop_server_mods'),
"DROP TABLE IF EXISTS `OGP_DB_PREFIXsteam_workshop_game_profiles`", "DROP TABLE IF EXISTS " . sw_module_table('steam_workshop_game_profiles'),
); );

View file

@ -15,15 +15,38 @@
$module_buttons = array(); $module_buttons = array();
if (!function_exists('sw_monitor_db_prefix')) {
function sw_monitor_db_prefix()
{
if (defined('DB_PREFIX') && DB_PREFIX !== '') {
return DB_PREFIX;
}
if (isset($GLOBALS['db_prefix']) && $GLOBALS['db_prefix'] !== '') {
return $GLOBALS['db_prefix'];
}
if (isset($GLOBALS['table_prefix']) && $GLOBALS['table_prefix'] !== '') {
return $GLOBALS['table_prefix'];
}
return 'gsp_';
}
}
if (!function_exists('sw_monitor_table')) {
function sw_monitor_table($name)
{
return '`' . sw_monitor_db_prefix() . $name . '`';
}
}
// Only show the button when a Workshop profile is enabled for this game config. // Only show the button when a Workshop profile is enabled for this game config.
$_sw_profile = $db->resultQuery( $_sw_profile = $db->resultQuery(
"SELECT p.id "SELECT p.`id`
FROM OGP_DB_PREFIXsteam_workshop_game_profiles p FROM " . sw_monitor_table('steam_workshop_game_profiles') . " p
JOIN OGP_DB_PREFIXconfig_homes c ON c.game_key = p.config_name JOIN " . sw_monitor_table('config_homes') . " c ON c.`game_key` = p.`config_name`
JOIN OGP_DB_PREFIXserver_homes s ON s.home_cfg_id = c.home_cfg_id JOIN " . sw_monitor_table('server_homes') . " s ON s.`home_cfg_id` = c.`home_cfg_id`
WHERE s.home_id = " . (int)$server_home['home_id'] . " WHERE s.home_id = " . (int)$server_home['home_id'] . "
AND p.enabled = 1 AND p.enabled = 1
LIMIT 1" LIMIT 1"
); );
if (!empty($_sw_profile)) { if (!empty($_sw_profile)) {

View file

@ -42,8 +42,7 @@ function exec_ogp_module()
// Find matching Workshop profile // Find matching Workshop profile
$profile = sw_get_profile_for_home($db, $home_id); $profile = sw_get_profile_for_home($db, $home_id);
if (!$profile) { if (!$profile) {
echo '<p>Steam Workshop is not enabled for this game type (<strong>' echo '<p>Steam Workshop is not enabled for this game.</p>';
. sw_h($home['game_name']) . '</strong>).</p>';
echo '<p>An administrator must enable Workshop support for this game under ' echo '<p>An administrator must enable Workshop support for this game under '
. '<em>Steam Workshop &rsaquo; Admin</em>.</p>'; . '<em>Steam Workshop &rsaquo; Admin</em>.</p>';
return; return;
@ -293,7 +292,7 @@ function sw_user_queue_update($db, $home_id)
SET `install_status` = 'queued', `updated_at` = NOW() SET `install_status` = 'queued', `updated_at` = NOW()
WHERE `home_id` = $home_id AND `enabled` = 1" WHERE `home_id` = $home_id AND `enabled` = 1"
); );
sw_success('All enabled mods queued for update. Run the agent to process downloads.'); sw_success('All enabled mods were queued. Updates are processed automatically by the server agent.');
} }
function sw_user_save_settings($db, $home_id) function sw_user_save_settings($db, $home_id)
@ -301,9 +300,7 @@ function sw_user_save_settings($db, $home_id)
$ok = sw_save_server_settings($db, $home_id, array( $ok = sw_save_server_settings($db, $home_id, array(
'update_mode' => $_POST['update_mode'] ?? 'manual', 'update_mode' => $_POST['update_mode'] ?? 'manual',
'restart_behavior' => $_POST['restart_behavior'] ?? 'none', 'restart_behavior' => $_POST['restart_behavior'] ?? 'none',
'hot_load' => $_POST['hot_load'] ?? 'disabled', 'schedule_interval' => $_POST['schedule_interval'] ?? 'disabled',
'warning_minutes' => $_POST['warning_minutes'] ?? 10,
'schedule_interval' => $_POST['schedule_interval'] ?? 'daily',
)); ));
if ($ok) { if ($ok) {
@ -321,352 +318,234 @@ function sw_user_render($db, $home_id, array $home, array $profile)
{ {
$mods = sw_get_server_mods($db, $home_id) ?: array(); $mods = sw_get_server_mods($db, $home_id) ?: array();
$settings = sw_get_server_settings($db, $home_id); $settings = sw_get_server_settings($db, $home_id);
$queuedCount = 0;
// Generate launch params from enabled mods $failedCount = 0;
$enabled_mods = array_filter($mods, function ($m) { $installedCount = 0;
return !empty($m['enabled']); $latestUpdateAt = '';
}); $latestError = '';
$params = sw_generate_launch_params(array_values($enabled_mods), $profile); foreach ($mods as $mod) {
if (($mod['install_status'] ?? '') === 'queued') {
$queuedCount++;
} elseif (($mod['install_status'] ?? '') === 'failed') {
$failedCount++;
} elseif (($mod['install_status'] ?? '') === 'installed') {
$installedCount++;
}
if (!empty($mod['last_updated_at']) && $mod['last_updated_at'] > $latestUpdateAt) {
$latestUpdateAt = $mod['last_updated_at'];
}
if (($mod['install_status'] ?? '') === 'failed' && !empty($mod['last_error']) && $latestError === '') {
$latestError = $mod['last_error'];
}
}
$base_url = 'home.php?m=steam_workshop&p=user&home_id=' . $home_id; $base_url = 'home.php?m=steam_workshop&p=user&home_id=' . $home_id;
?> ?>
<style>
.sw-user-panel{background:#161616;border:1px solid #2f2f2f;border-radius:6px;padding:14px;margin:10px 0;color:#ececec}
.sw-user-panel h3{margin:0 0 10px 0;color:#fff}
.sw-user-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:10px}
.sw-user-grid label{display:block}
.sw-user-grid span{display:block;font-size:12px;color:#bebebe;margin-bottom:4px}
.sw-user-grid input[type=text],.sw-user-grid select{width:100%;box-sizing:border-box;background:#0d0d0d;border:1px solid #3a3a3a;color:#f0f0f0;padding:7px;border-radius:4px}
.sw-user-table-wrap{overflow-x:auto}
.sw-user-table{width:100%;border-collapse:collapse;min-width:860px}
.sw-user-table th,.sw-user-table td{border:1px solid #353535;padding:8px;vertical-align:middle}
.sw-user-table th{background:#202020;text-align:left;color:#fff}
.sw-status-ok{color:#7cdc7c;font-weight:700}
.sw-status-queued{color:#ffca63;font-weight:700}
.sw-status-failed{color:#ff8484;font-weight:700}
.sw-status-progress{color:#8cc7ff;font-weight:700}
.sw-muted{color:#b4b4b4}
</style>
<p> <div class="sw-user-panel">
<strong>Server:</strong> <?= sw_h($home['home_name']) ?> <p>
&nbsp;&nbsp; <strong>Server:</strong> <?= sw_h($home['home_name']) ?>
<strong>Game:</strong> <?= sw_h($home['game_name']) ?> &nbsp;&nbsp;<strong>Game:</strong> <?= sw_h($home['game_name']) ?>
&nbsp;&nbsp; &nbsp;&nbsp;<strong>Workshop Profile:</strong> <?= sw_h($profile['config_name']) ?>
<strong>Workshop Profile:</strong> <?= sw_h($profile['config_name']) ?>
</p>
<!-- Add Mod form -->
<h3>Add Workshop Mod</h3>
<form method="post" action="<?= sw_h($base_url) ?>">
<input type="hidden" name="action" value="add_mod">
<table>
<tr>
<td style="padding:4px 8px;"><label for="workshop_id">Workshop ID</label></td>
<td style="padding:4px 8px;">
<input type="text" id="workshop_id" name="workshop_id" value=""
placeholder="e.g. 2863534533" style="width:180px;" required>
</td>
</tr>
<tr>
<td style="padding:4px 8px;"><label for="add_mod_name">Display Name (optional)</label></td>
<td style="padding:4px 8px;">
<input type="text" id="add_mod_name" name="mod_name" value=""
placeholder="e.g. CF" style="width:180px;">
</td>
</tr>
<tr>
<td style="padding:4px 8px;"><label for="add_mod_type">Mod Type</label></td>
<td style="padding:4px 8px;">
<select id="add_mod_type" name="mod_type">
<option value="client">Client mod (-mod=)</option>
<option value="server">Server-side only (-serverMod=)</option>
</select>
</td>
</tr>
<tr>
<td></td>
<td style="padding:4px 8px;">
<button type="submit" class="button">Add Mod</button>
</td>
</tr>
</table>
</form>
<hr>
<!-- Mod list -->
<h3>Installed Mods (<?= count($mods) ?>)</h3>
<?php if (empty($mods)): ?>
<p>No mods added yet. Use the form above to add Workshop IDs.</p>
<?php else: ?>
<form method="post" action="<?= sw_h($base_url) ?>">
<table width="100%" style="border-collapse:collapse;">
<thead>
<tr style="background:#f0f0f0;">
<th style="padding:6px 8px;text-align:center;">#</th>
<th style="padding:6px 8px;text-align:left;">Workshop ID</th>
<th style="padding:6px 8px;text-align:left;">Mod Name</th>
<th style="padding:6px 8px;text-align:left;">Folder Name</th>
<th style="padding:6px 8px;text-align:center;">Type</th>
<th style="padding:6px 8px;text-align:center;">Enabled</th>
<th style="padding:6px 8px;text-align:center;">Status</th>
<th style="padding:6px 8px;text-align:center;">Order</th>
<th style="padding:6px 8px;text-align:center;">Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($mods as $idx => $mod): ?>
<tr style="border-bottom:1px solid #ddd;<?= !$mod['enabled'] ? 'opacity:0.55;' : '' ?>">
<td style="padding:6px 8px;text-align:center;"><?= $idx + 1 ?></td>
<td style="padding:6px 8px;font-family:monospace;"><?= sw_h($mod['workshop_id']) ?></td>
<!-- Inline edit: name -->
<td style="padding:6px 8px;">
<form method="post" action="<?= sw_h($base_url) ?>" style="display:inline;">
<input type="hidden" name="action" value="save_mod">
<input type="hidden" name="mod_id" value="<?= (int)$mod['id'] ?>">
<input type="hidden" name="folder_name" value="<?= sw_h($mod['folder_name']) ?>">
<input type="hidden" name="mod_type" value="<?= sw_h($mod['mod_type']) ?>">
<input type="text" name="mod_name" value="<?= sw_h($mod['mod_name']) ?>"
style="width:120px;" title="Click Save to apply">
</td>
<!-- Inline edit: folder name -->
<td style="padding:6px 8px;">
<input type="text" name="folder_name" value="<?= sw_h($mod['folder_name']) ?>"
style="width:140px;" title="Folder name inside server root">
</td>
<!-- Inline edit: mod type -->
<td style="padding:6px 8px;text-align:center;">
<select name="mod_type" style="width:100px;">
<option value="client" <?= $mod['mod_type'] === 'client' ? 'selected' : '' ?>>-mod=</option>
<option value="server" <?= $mod['mod_type'] === 'server' ? 'selected' : '' ?>>-serverMod=</option>
</select>
<button type="submit" class="button small" title="Save changes">Save</button>
</form>
</td>
<!-- Toggle enabled -->
<td style="padding:6px 8px;text-align:center;">
<form method="post" action="<?= sw_h($base_url) ?>" style="display:inline;">
<input type="hidden" name="action" value="toggle_mod">
<input type="hidden" name="mod_id" value="<?= (int)$mod['id'] ?>">
<button type="submit" class="button small"
style="<?= $mod['enabled'] ? 'background:#5cb85c;color:#fff;' : '' ?>"
title="<?= $mod['enabled'] ? 'Click to disable' : 'Click to enable' ?>">
<?= $mod['enabled'] ? 'On' : 'Off' ?>
</button>
</form>
</td>
<!-- Install status -->
<td style="padding:6px 8px;text-align:center;font-size:0.85em;">
<?php
$s = $mod['install_status'];
if ($s === 'installed') {
echo '<span style="color:green;">Installed</span>';
} elseif ($s === 'queued') {
echo '<span style="color:orange;">Queued</span>';
} elseif ($s === 'failed') {
echo '<span style="color:red;" title="' . sw_h($mod['last_error']) . '">Failed</span>';
} elseif ($s === 'updating') {
echo '<span style="color:blue;">Updating</span>';
} else {
echo '<span style="color:#999;">Not installed</span>';
}
?>
</td>
<!-- Order buttons -->
<td style="padding:6px 8px;text-align:center;white-space:nowrap;">
<form method="post" action="<?= sw_h($base_url) ?>" style="display:inline;">
<input type="hidden" name="action" value="move_up">
<input type="hidden" name="mod_id" value="<?= (int)$mod['id'] ?>">
<button type="submit" class="button small" <?= $idx === 0 ? 'disabled' : '' ?>>&#9650;</button>
</form>
<form method="post" action="<?= sw_h($base_url) ?>" style="display:inline;">
<input type="hidden" name="action" value="move_down">
<input type="hidden" name="mod_id" value="<?= (int)$mod['id'] ?>">
<button type="submit" class="button small"
<?= $idx === (count($mods) - 1) ? 'disabled' : '' ?>>&#9660;</button>
</form>
</td>
<!-- Delete -->
<td style="padding:6px 8px;text-align:center;">
<form method="post" action="<?= sw_h($base_url) ?>" style="display:inline;">
<input type="hidden" name="action" value="delete_mod">
<input type="hidden" name="mod_id" value="<?= (int)$mod['id'] ?>">
<button type="submit" class="button small danger"
onclick="return confirm('Remove this mod from the list?');"
style="background:#d9534f;color:#fff;">Remove</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</form>
<?php endif; ?>
<hr>
<!-- Launch params display -->
<h3>Generated Launch Parameters</h3>
<p style="color:#666;font-size:0.9em;">
Based on the enabled mods above (sorted by order). Copy these into your server startup command.
</p>
<?php if (empty($enabled_mods)): ?>
<p>No enabled mods launch parameters will be empty.</p>
<?php else: ?>
<?php if ($params['mod']): ?>
<p>
<strong>Client mods (<code>-mod=</code>):</strong><br>
<input type="text" value="<?= sw_h($params['mod']) ?>"
readonly style="width:100%;font-family:monospace;"
onclick="this.select();">
</p>
<?php endif; ?>
<?php if ($params['servermod']): ?>
<p>
<strong>Server-side mods (<code>-serverMod=</code>):</strong><br>
<input type="text" value="<?= sw_h($params['servermod']) ?>"
readonly style="width:100%;font-family:monospace;"
onclick="this.select();">
</p>
<?php endif; ?>
<p>
<strong>Combined:</strong><br>
<input type="text" value="<?= sw_h($params['combined']) ?>"
readonly style="width:100%;font-family:monospace;"
onclick="this.select();">
</p>
<?php endif; ?>
<hr>
<!-- Install/Update -->
<h3>Install / Update Mods</h3>
<p>
Clicking <strong>Queue Update</strong> marks all enabled mods as <em>queued</em>.
Then run the agent <strong>on the game server host</strong> (where SteamCMD and the game files are located)
to download and install the mods. Adjust the path to the panel's
<code>modules/steam_workshop/agent_update_workshop.php</code> for your server:
</p>
<pre style="background:#222;color:#eee;padding:10px 14px;border-radius:4px;overflow-x:auto;"
>php /path/to/panel/modules/steam_workshop/agent_update_workshop.php --home-id=<?= $home_id ?></pre>
<form method="post" action="<?= sw_h($base_url) ?>">
<input type="hidden" name="action" value="queue_update">
<button type="submit" class="button"
onclick="return confirm('Queue all enabled mods for update?');">
Queue Update for All Enabled Mods
</button>
</form>
<hr>
<!-- Workshop Behavior Settings -->
<h3>Workshop Behavior Settings</h3>
<p style="color:#888;font-size:0.9em;">
Configure how Workshop mods are installed and updated for this server.
All options default to the safest setting (manual only, no automatic restarts).
</p>
<form method="post" action="<?= sw_h($base_url) ?>">
<input type="hidden" name="action" value="save_settings">
<table style="border-collapse:collapse;width:100%;max-width:720px;">
<colgroup>
<col style="width:220px;">
<col>
<col style="width:340px;">
</colgroup>
<thead>
<tr style="background:#f0f0f0;">
<th style="padding:6px 8px;text-align:left;">Setting</th>
<th style="padding:6px 8px;text-align:left;">Value</th>
<th style="padding:6px 8px;text-align:left;">Help</th>
</tr>
</thead>
<tbody>
<tr style="border-bottom:1px solid #ddd;">
<td style="padding:8px;font-weight:bold;">Install / Update Mode</td>
<td style="padding:8px;">
<select name="update_mode" style="width:100%;">
<option value="manual" <?= ($settings['update_mode'] === 'manual') ? 'selected' : '' ?>>Manual only</option>
<option value="on_restart" <?= ($settings['update_mode'] === 'on_restart') ? 'selected' : '' ?>>On next restart</option>
<option value="before_start" <?= ($settings['update_mode'] === 'before_start') ? 'selected' : '' ?>>Before every server start</option>
<option value="scheduled" <?= ($settings['update_mode'] === 'scheduled') ? 'selected' : '' ?>>Scheduled update check</option>
</select>
</td>
<td style="padding:8px;font-size:0.85em;color:#555;">
<strong>Manual only</strong> mods are only updated when you click &ldquo;Queue Update&rdquo; above.<br>
<strong>On next restart</strong> queued updates are applied the next time the server restarts.<br>
<strong>Before every start</strong> the update check runs automatically each time the server starts.<br>
<strong>Scheduled</strong> the update check runs on the interval set below (requires cron / agent).
</td>
</tr>
<tr style="border-bottom:1px solid #ddd;">
<td style="padding:8px;font-weight:bold;">Restart Behavior</td>
<td style="padding:8px;">
<select name="restart_behavior" style="width:100%;">
<option value="none" <?= ($settings['restart_behavior'] === 'none') ? 'selected' : '' ?>>Do not restart automatically</option>
<option value="if_empty" <?= ($settings['restart_behavior'] === 'if_empty') ? 'selected' : '' ?>>Restart if empty</option>
<option value="immediate" <?= ($settings['restart_behavior'] === 'immediate') ? 'selected' : '' ?>>Restart after warning</option>
<option value="next_restart" <?= ($settings['restart_behavior'] === 'next_restart') ? 'selected' : '' ?>>Install on next manual restart only</option>
</select>
</td>
<td style="padding:8px;font-size:0.85em;color:#555;">
Controls what happens when new mod updates are found.<br>
<strong>Do not restart</strong> updates are staged but the server keeps running (safe default).<br>
<strong>If empty</strong> the server is restarted only when there are zero players connected.<br>
<strong>Immediate with warning</strong> a countdown warning is broadcast, then the server restarts.<br>
<strong>Next manual restart</strong> updates are installed the next time you manually stop/start the server.
</td>
</tr>
<tr style="border-bottom:1px solid #ddd;">
<td style="padding:8px;font-weight:bold;">Hot-Load</td>
<td style="padding:8px;">
<select name="hot_load" style="width:100%;">
<option value="disabled" <?= ($settings['hot_load'] === 'disabled') ? 'selected' : '' ?>>Disabled</option>
<option value="attempt" <?= ($settings['hot_load'] === 'attempt') ? 'selected' : '' ?>>Attempt hot-load if game supports it</option>
</select>
</td>
<td style="padding:8px;font-size:0.85em;color:#555;">
<strong>Disabled</strong> no hot-loading; mod changes take effect only after a server restart (safe default).<br>
<strong>Attempt</strong> if the game supports live mod reloading (e.g. via RCON), try to hot-load instead of restarting.
</td>
</tr>
<tr style="border-bottom:1px solid #ddd;">
<td style="padding:8px;font-weight:bold;">Warning Countdown</td>
<td style="padding:8px;">
<input type="number" name="warning_minutes" min="1" max="120"
value="<?= (int)$settings['warning_minutes'] ?>"
style="width:80px;"> minutes
</td>
<td style="padding:8px;font-size:0.85em;color:#555;">
Minutes of advance warning broadcast to players before an automatic restart.<br>
Only used when <em>Restart Behavior</em> is set to <strong>Restart immediately after warning</strong>.<br>
Default: 10 minutes.
</td>
</tr>
<tr style="border-bottom:1px solid #ddd;">
<td style="padding:8px;font-weight:bold;">Scheduled Check Interval</td>
<td style="padding:8px;">
<select name="schedule_interval" style="width:100%;">
<option value="hourly" <?= ($settings['schedule_interval'] === 'hourly') ? 'selected' : '' ?>>Hourly</option>
<option value="daily" <?= ($settings['schedule_interval'] === 'daily') ? 'selected' : '' ?>>Daily (default)</option>
<option value="weekly" <?= ($settings['schedule_interval'] === 'weekly') ? 'selected' : '' ?>>Weekly</option>
</select>
</td>
<td style="padding:8px;font-size:0.85em;color:#555;">
How often the scheduled update check runs.<br>
Only used when <em>Install / Update Mode</em> is set to <strong>Scheduled update check</strong>.<br>
Requires the Workshop agent to be running via cron on the game server host.
</td>
</tr>
</tbody>
</table>
<p style="margin-top:12px;">
<button type="submit" class="button">Save Behavior Settings</button>
</p> </p>
</form> <p class="sw-muted" style="margin-bottom:0;">
Queue updates from this page. The server agent applies queued updates automatically.
</p>
</div>
<div class="sw-user-panel">
<h3>Add Workshop Mod</h3>
<form method="post" action="<?= sw_h($base_url) ?>">
<input type="hidden" name="action" value="add_mod">
<div class="sw-user-grid">
<label>
<span>Workshop ID</span>
<input type="text" id="workshop_id" name="workshop_id" placeholder="e.g. 2863534533" required>
</label>
<label>
<span>Display Name (optional)</span>
<input type="text" id="add_mod_name" name="mod_name" placeholder="e.g. CF">
</label>
<label>
<span>Mod Type</span>
<select id="add_mod_type" name="mod_type">
<option value="client">Client mod</option>
<option value="server">Server-side only</option>
</select>
</label>
</div>
<p style="margin:12px 0 0;">
<button type="submit" class="button">Add Mod</button>
</p>
</form>
</div>
<div class="sw-user-panel">
<h3>Update Queue &amp; Last Result</h3>
<p>
<strong>Enabled mods:</strong> <?= count(array_filter($mods, function ($m) { return !empty($m['enabled']); })) ?>
&nbsp;&nbsp;<strong>Queued:</strong> <?= $queuedCount ?>
&nbsp;&nbsp;<strong>Installed:</strong> <?= $installedCount ?>
&nbsp;&nbsp;<strong>Failed:</strong> <?= $failedCount ?>
</p>
<p>
<strong>Last update time:</strong> <?= $latestUpdateAt ? sw_h($latestUpdateAt) : 'Never' ?>
</p>
<?php if ($latestError !== ''): ?>
<p><strong>Last error:</strong> <?= sw_h($latestError) ?></p>
<?php endif; ?>
<form method="post" action="<?= sw_h($base_url) ?>">
<input type="hidden" name="action" value="queue_update">
<button type="submit" class="button" onclick="return confirm('Queue all enabled mods for update?');">Queue Update for All Enabled Mods</button>
</form>
</div>
<div class="sw-user-panel">
<h3>Workshop Behavior Settings</h3>
<form method="post" action="<?= sw_h($base_url) ?>">
<input type="hidden" name="action" value="save_settings">
<div class="sw-user-grid">
<label>
<span>Update Mode</span>
<select name="update_mode">
<option value="manual" <?= ($settings['update_mode'] === 'manual') ? 'selected' : '' ?>>Manual only</option>
<option value="on_restart" <?= ($settings['update_mode'] === 'on_restart') ? 'selected' : '' ?>>On next restart</option>
<option value="before_start" <?= ($settings['update_mode'] === 'before_start') ? 'selected' : '' ?>>Before server start</option>
</select>
</label>
<label>
<span>Restart Behavior</span>
<select name="restart_behavior">
<option value="none" <?= ($settings['restart_behavior'] === 'none') ? 'selected' : '' ?>>Never restart automatically</option>
<option value="if_stopped" <?= ($settings['restart_behavior'] === 'if_stopped') ? 'selected' : '' ?>>Restart only if server is stopped</option>
</select>
</label>
<label>
<span>Scheduled Checks</span>
<select name="schedule_interval">
<option value="disabled" <?= ($settings['schedule_interval'] === 'disabled') ? 'selected' : '' ?>>Disabled</option>
<option value="daily" <?= ($settings['schedule_interval'] === 'daily') ? 'selected' : '' ?>>Daily</option>
<option value="weekly" <?= ($settings['schedule_interval'] === 'weekly') ? 'selected' : '' ?>>Weekly</option>
</select>
</label>
</div>
<p style="margin:12px 0 0;">
<button type="submit" class="button">Save Behavior Settings</button>
</p>
</form>
</div>
<div class="sw-user-panel">
<h3>Installed Mods (<?= count($mods) ?>)</h3>
<?php if (empty($mods)): ?>
<p>No mods added yet. Use the form above to add Workshop IDs.</p>
<?php else: ?>
<div class="sw-user-table-wrap">
<table class="sw-user-table">
<thead>
<tr>
<th>#</th>
<th>Workshop ID</th>
<th>Mod Name</th>
<th>Folder Name</th>
<th>Type</th>
<th>Enabled</th>
<th>Status</th>
<th>Last Update</th>
<th>Last Error</th>
<th>Order</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($mods as $idx => $mod): ?>
<tr style="<?= !$mod['enabled'] ? 'opacity:0.55;' : '' ?>">
<td><?= $idx + 1 ?></td>
<td style="font-family:monospace;"><?= sw_h($mod['workshop_id']) ?></td>
<td>
<form method="post" action="<?= sw_h($base_url) ?>" style="display:flex;gap:6px;align-items:center;flex-wrap:wrap;">
<input type="hidden" name="action" value="save_mod">
<input type="hidden" name="mod_id" value="<?= (int)$mod['id'] ?>">
<input type="text" name="mod_name" value="<?= sw_h($mod['mod_name']) ?>" style="width:120px;">
</td>
<td><input type="text" name="folder_name" value="<?= sw_h($mod['folder_name']) ?>" style="width:120px;"></td>
<td>
<select name="mod_type" style="width:120px;">
<option value="client" <?= $mod['mod_type'] === 'client' ? 'selected' : '' ?>>Client</option>
<option value="server" <?= $mod['mod_type'] === 'server' ? 'selected' : '' ?>>Server</option>
</select>
<button type="submit" class="button small">Save</button>
</form>
</td>
<td>
<form method="post" action="<?= sw_h($base_url) ?>" style="display:inline;">
<input type="hidden" name="action" value="toggle_mod">
<input type="hidden" name="mod_id" value="<?= (int)$mod['id'] ?>">
<button type="submit" class="button small" style="<?= $mod['enabled'] ? 'background:#5cb85c;color:#fff;' : '' ?>"><?= $mod['enabled'] ? 'On' : 'Off' ?></button>
</form>
</td>
<td>
<?php
$s = $mod['install_status'];
if ($s === 'installed') {
echo '<span class="sw-status-ok">Installed</span>';
} elseif ($s === 'queued') {
echo '<span class="sw-status-queued">Queued</span>';
} elseif ($s === 'failed') {
echo '<span class="sw-status-failed">Failed</span>';
} elseif ($s === 'updating') {
echo '<span class="sw-status-progress">Updating</span>';
} else {
echo '<span class="sw-muted">Not installed</span>';
}
?>
</td>
<td><?= !empty($mod['last_updated_at']) ? sw_h($mod['last_updated_at']) : '-' ?></td>
<?php $shortError = !empty($mod['last_error']) ? (strlen($mod['last_error']) > 70 ? (substr($mod['last_error'], 0, 67) . '...') : $mod['last_error']) : ''; ?>
<td title="<?= sw_h($mod['last_error'] ?? '') ?>"><?= $shortError !== '' ? sw_h($shortError) : '-' ?></td>
<td style="white-space:nowrap;">
<form method="post" action="<?= sw_h($base_url) ?>" style="display:inline;">
<input type="hidden" name="action" value="move_up">
<input type="hidden" name="mod_id" value="<?= (int)$mod['id'] ?>">
<button type="submit" class="button small" <?= $idx === 0 ? 'disabled' : '' ?>>&#9650;</button>
</form>
<form method="post" action="<?= sw_h($base_url) ?>" style="display:inline;">
<input type="hidden" name="action" value="move_down">
<input type="hidden" name="mod_id" value="<?= (int)$mod['id'] ?>">
<button type="submit" class="button small" <?= $idx === (count($mods) - 1) ? 'disabled' : '' ?>>&#9660;</button>
</form>
</td>
<td>
<form method="post" action="<?= sw_h($base_url) ?>" style="display:inline;">
<input type="hidden" name="action" value="delete_mod">
<input type="hidden" name="mod_id" value="<?= (int)$mod['id'] ?>">
<button type="submit" class="button small danger" onclick="return confirm('Remove this mod from the list?');" style="background:#d9534f;color:#fff;">Remove</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
<?php <?php
} }

View file

@ -22,6 +22,18 @@
* *
*/ */
function gsp_support_docs_url_for_game_key($game_key)
{
$game_key = trim((string)$game_key);
if ($game_key !== '') {
$docPath = __DIR__ . '/../billing/docs/' . $game_key . '/index.php';
if (is_file($docPath)) {
return '/docs.php?action=view&doc=' . rawurlencode($game_key);
}
}
return '/docs.php';
}
function exec_ogp_module() { function exec_ogp_module() {
global $db, $settings; global $db, $settings;
@ -81,6 +93,19 @@ if (!empty($webhook)) {
} // end if submit } // end if submit
echo '<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" integrity="sha512-DTOQO9RWCH3ppGqcWaEA1BIZOC6xxalwEsw9c2QQeAIftl+Vegovlnee1c9QX4TctnWMn13TZye+giMm8e2LwA==" crossorigin="anonymous" referrerpolicy="no-referrer" />'; echo '<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" integrity="sha512-DTOQO9RWCH3ppGqcWaEA1BIZOC6xxalwEsw9c2QQeAIftl+Vegovlnee1c9QX4TctnWMn13TZye+giMm8e2LwA==" crossorigin="anonymous" referrerpolicy="no-referrer" />';
echo "<h2>".get_lang('support')."</h2>"; echo "<h2>".get_lang('support')."</h2>";
$defaultDocsUrl = '/docs.php';
if (!empty($server_homes) && is_array($server_homes)) {
foreach ((array)$server_homes as $server_home_row) {
if (!empty($server_home_row['game_key'])) {
$defaultDocsUrl = gsp_support_docs_url_for_game_key($server_home_row['game_key']);
break;
}
}
}
echo "<div style='margin:0 auto 16px auto;max-width:600px;padding:10px 12px;border:1px solid #2d2d2d;border-radius:6px;background:#171717;'>"
. "<a id='support-doc-link' href='" . htmlspecialchars($defaultDocsUrl, ENT_QUOTES) . "' target='_blank' rel='noopener noreferrer' style='color:#8cb9ff;text-decoration:none;font-weight:600;'>Game Documentation</a>"
. "<span style='color:#a9a9a9;margin-left:8px;'>Open setup and troubleshooting docs in a new tab.</span>"
. "</div>";
echo ' echo '
<div style="background:#5865F2;border-radius:8px;padding:14px 20px;margin:0 auto 20px auto;max-width:600px;display:flex;align-items:center;gap:16px;box-shadow:0 2px 8px rgba(0,0,0,0.18);"> <div style="background:#5865F2;border-radius:8px;padding:14px 20px;margin:0 auto 20px auto;max-width:600px;display:flex;align-items:center;gap:16px;box-shadow:0 2px 8px rgba(0,0,0,0.18);">
<i class="fa-brands fa-discord" style="font-size:2.4em;color:#fff;flex-shrink:0;"></i> <i class="fa-brands fa-discord" style="font-size:2.4em;color:#fff;flex-shrink:0;"></i>
@ -97,7 +122,8 @@ if (!empty($webhook)) {
echo get_lang('select_server').":<br /><select name='gameserver' id='gameserver'>"; echo get_lang('select_server').":<br /><select name='gameserver' id='gameserver'>";
foreach ((array)$server_homes as $server_home) foreach ((array)$server_homes as $server_home)
{ {
echo "<option value='".$server_home['home_name']."'>".$server_home['home_name']."</option>"; $docUrl = gsp_support_docs_url_for_game_key($server_home['game_key'] ?? '');
echo "<option value='".htmlspecialchars($server_home['home_name'], ENT_QUOTES)."' data-doc-url='".htmlspecialchars($docUrl, ENT_QUOTES)."'>".htmlspecialchars($server_home['home_name'], ENT_QUOTES)."</option>";
} }
echo "</select><br /><br />"; echo "</select><br /><br />";
@ -138,6 +164,15 @@ if (!empty($webhook)) {
return false; return false;
} }
} }
$(document).ready(function(){
function updateSupportDocLink(){
var selected = $('#gameserver option:selected');
var url = selected.data('doc-url') || '/docs.php';
$('#support-doc-link').attr('href', url);
}
$('#gameserver').on('change', updateSupportDocLink);
updateSupportDocLink();
});
</script> </script>
<?php <?php
} // End function } // End function