Panel/Panel/modules/addonsmanager/module.php
copilot-swe-agent[bot] 6dd5088613
feat: redesign SCM workshop handling – remove workshop_item_id requirement, add user ID entry, deprecate steam_workshop menu
- server_content_helpers.php: remove workshop_item_id from required fields/validation; support newline-separated IDs; add scm_validate_workshop_user_ids(); update help text; add new template policy columns to ensure_phase2_schema; add content_id guard to ensure_workshop_schema
- addons_manager.php: rename admin workshop_item_id row to "Default Workshop IDs (Optional)" with updated label
- user_addons.php: route workshop_item type to workshop_content page instead of addons installer
- workshop_content.php: accept addon_id param; show template info; use textarea for multi-ID entry; update heading/help text
- workshop_action.php: accept addon_id param; resolve workshop_app_id from addon template; pass extra_manifest to manifest-and-run; store content_id on insert; use newline/comma-separated IDs
- addons_installer.php: redirect workshop_item addon_type to workshop_content page
- module.php: db_version 6 with allow_user_workshop_ids, max_workshop_ids, required_workshop_ids, blocked_workshop_ids on addons; content_id on server_content_workshop
- server_content_categories.php: rename "Steam Workshop Item" → "Steam Workshop Mods"
- steam_workshop/module.php: empty $module_menus, mark deprecated, bump version to 3.3
- addonsmanager.js: remove #scm-row-workshop-id from steam_workshop admin rows

Agent-Logs-Url: https://github.com/GameServerPanel/GSP/sessions/6a328ac8-2f82-4943-93a9-7660bf42a2fd

Co-authored-by: iaretechnician <2749183+iaretechnician@users.noreply.github.com>
2026-05-20 16:47:47 +00:00

263 lines
12 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/*
*
* GSP - Game Server Panel (a heavily customized fork of OGP maintained by WDS)
*
* Module: addonsmanager → Server Content Manager
* ─────────────────────────────────────────────────────────────────────────────
* The module folder and DB table names are intentionally unchanged for
* backward compatibility. Only UI labels have been updated to the new
* "Server Content" terminology.
*
* db_version history:
* 1 initial schema (addons table, addon_type VARCHAR(7))
* 2 expand addon_type to VARCHAR(32) to support extended content types
* (workshop=8 chars, and any future type up to 32 chars)
* 3 add server_content_workshop table for per-server Workshop item selections
* 4 Phase 2: add install_method / content_version / requires_stop /
* backup_before_install / restart_after_install / is_cacheable /
* description columns to addons table; add server_content_manifest
* and server_content_install_history tables
* 5 add workshop_item_id / workshop_app_id / target_path_template /
* optional_folder_name / config_edit_rule / launch_param_additions
* columns to addons table
* 6 add admin template policy columns to addons table
* (allow_user_workshop_ids, max_workshop_ids, required_workshop_ids,
* blocked_workshop_ids); add content_id column to
* server_content_workshop so user installs link to their template
*
*/
// Module general information
$module_title = "Server Content Manager";
$module_version = "2.4";
$db_version = 6;
$module_required = TRUE;
$module_menus = array(
array( 'subpage' => 'addons_manager', 'name' => 'Server Content Manager', 'group' => 'admin' )
);
// ── db_version 1 : initial install ───────────────────────────────────────────
$install_queries = array();
$install_queries[0] = array(
"CREATE TABLE IF NOT EXISTS `".OGP_DB_PREFIX."addons` (
`addon_id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
`name` VARCHAR(80) NOT NULL,
`url` VARCHAR(200) NOT NULL,
`path` VARCHAR(80) NOT NULL,
`addon_type` VARCHAR(7) NOT NULL,
`home_cfg_id` VARCHAR(7) NOT NULL,
`post_script` longtext NOT NULL,
`group_id` int(11) NULL
) ENGINE=MyISAM;"
);
// ── db_version 2 : expand addon_type to VARCHAR(32) ──────────────────────────
// Required so extended content types such as 'workshop' (8 chars) can be stored.
// MODIFY is safe on existing installs; existing 'plugin'/'mappack'/'config'
// values are preserved without alteration.
$install_queries[1] = array(
"ALTER TABLE `".OGP_DB_PREFIX."addons`
MODIFY `addon_type` VARCHAR(32) NOT NULL;"
);
// ── db_version 3 : workshop item selections per server home ───────────────────
$install_queries[2] = array(
"CREATE TABLE IF NOT EXISTS `".OGP_DB_PREFIX."server_content_workshop` (
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
`home_id` INT NOT NULL,
`home_cfg_id` INT NOT NULL,
`remote_server_id` INT NULL,
`workshop_app_id` VARCHAR(32) NULL,
`workshop_item_id` VARCHAR(64) NOT NULL,
`title` VARCHAR(255) NULL,
`install_state` VARCHAR(32) NOT NULL DEFAULT 'selected',
`last_installed_at` DATETIME NULL,
`last_updated_at` DATETIME NULL,
`last_error` TEXT NULL,
`created_by` INT NULL,
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME NULL,
UNIQUE KEY `uniq_home_workshop_item` (`home_id`, `workshop_item_id`),
KEY `idx_home_id` (`home_id`),
KEY `idx_home_cfg_id` (`home_cfg_id`),
KEY `idx_install_state` (`install_state`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;"
);
// ── db_version 4 : Phase 2 install_method, per-server manifest, install history ──
//
// Uses a PHP callable so each ALTER is applied only when the column does not
// already exist (safe for repeated runs, compatible with all MySQL versions).
//
$install_queries[3] = array(
function ($db) {
$prefix = OGP_DB_PREFIX;
// ── Extend the addons table with Phase 2 columns ──────────────────────
$new_columns = array(
'install_method' => "VARCHAR(32) NOT NULL DEFAULT 'download_zip' AFTER `group_id`",
'content_version' => "VARCHAR(64) NULL AFTER `install_method`",
'requires_stop' => "TINYINT(1) NOT NULL DEFAULT 1 AFTER `content_version`",
'backup_before_install' => "TINYINT(1) NOT NULL DEFAULT 1 AFTER `requires_stop`",
'restart_after_install' => "TINYINT(1) NOT NULL DEFAULT 0 AFTER `backup_before_install`",
'is_cacheable' => "TINYINT(1) NOT NULL DEFAULT 0 AFTER `restart_after_install`",
'description' => "TEXT NULL AFTER `is_cacheable`",
);
foreach ($new_columns as $col => $definition) {
$escaped_col = $db->realEscapeSingle($col);
$escaped_table = $db->realEscapeSingle($prefix . 'addons');
$check = $db->resultQuery(
"SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = '{$escaped_table}'
AND COLUMN_NAME = '{$escaped_col}'"
);
if (empty($check)) {
if (!$db->query("ALTER TABLE `{$prefix}addons` ADD COLUMN `{$col}` {$definition}")) {
return false;
}
}
}
// ── Per-server installed-content manifest ─────────────────────────────
if (!$db->query(
"CREATE TABLE IF NOT EXISTS `{$prefix}server_content_manifest` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
`home_id` INT NOT NULL,
`addon_id` INT NOT NULL,
`install_method` VARCHAR(32) NOT NULL DEFAULT 'download_zip',
`content_version` VARCHAR(64) NULL,
`install_state` VARCHAR(32) NOT NULL DEFAULT 'installed',
`checksum_sha256` VARCHAR(64) NULL,
`source_url` VARCHAR(255) NULL,
`installed_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`installed_by` INT NULL,
`updated_at` DATETIME NULL,
`notes` TEXT NULL,
UNIQUE KEY `uniq_home_addon` (`home_id`, `addon_id`),
KEY `idx_home_id` (`home_id`),
KEY `idx_addon_id` (`addon_id`),
KEY `idx_install_state` (`install_state`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"
)) {
return false;
}
// ── Install history (one row per install attempt) ─────────────────────
if (!$db->query(
"CREATE TABLE IF NOT EXISTS `{$prefix}server_content_install_history` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
`home_id` INT NOT NULL,
`addon_id` INT NOT NULL,
`installed_by` INT NULL,
`started_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`completed_at` DATETIME NULL,
`install_state` VARCHAR(32) NOT NULL DEFAULT 'started',
`install_method` VARCHAR(32) NULL,
`content_version` VARCHAR(64) NULL,
`source_url` VARCHAR(255) NULL,
`cache_mode_used` VARCHAR(32) NULL,
`result_code` INT NULL,
`log_output` MEDIUMTEXT NULL,
KEY `idx_home_id` (`home_id`),
KEY `idx_addon_id` (`addon_id`),
KEY `idx_started_at` (`started_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"
)) {
return false;
}
return true;
},
);
// ── db_version 5 : content-type specific metadata for Workshop/config/folder actions ──
$install_queries[4] = array(
function ($db) {
$prefix = OGP_DB_PREFIX;
$new_columns = array(
'workshop_item_id' => "VARCHAR(64) NULL AFTER `description`",
'workshop_app_id' => "VARCHAR(32) NULL AFTER `workshop_item_id`",
'target_path_template' => "VARCHAR(255) NULL AFTER `workshop_app_id`",
'optional_folder_name' => "VARCHAR(255) NULL AFTER `target_path_template`",
'config_edit_rule' => "TEXT NULL AFTER `optional_folder_name`",
'launch_param_additions' => "VARCHAR(255) NULL AFTER `config_edit_rule`",
);
foreach ($new_columns as $col => $definition) {
$escaped_col = $db->realEscapeSingle($col);
$escaped_table = $db->realEscapeSingle($prefix . 'addons');
$check = $db->resultQuery(
"SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = '{$escaped_table}'
AND COLUMN_NAME = '{$escaped_col}'"
);
if (empty($check)) {
if (!$db->query("ALTER TABLE `{$prefix}addons` ADD COLUMN `{$col}` {$definition}")) {
return false;
}
}
}
return true;
},
);
// ── db_version 6 : admin template policy columns + content_id on workshop rows ──
//
// allow_user_workshop_ids whether users may enter their own IDs (default 1)
// max_workshop_ids optional cap on how many IDs a user may install
// required_workshop_ids JSON list of IDs that must always be installed
// blocked_workshop_ids JSON list of IDs that must not be installed
// content_id on server_content_workshop links a user install row back to
// the admin content template so the correct workshop_app_id is used.
//
$install_queries[5] = array(
function ($db) {
$prefix = OGP_DB_PREFIX;
// ── New policy columns on the addons (content template) table ─────────
$addon_columns = array(
'allow_user_workshop_ids' => "TINYINT(1) NOT NULL DEFAULT 1 AFTER `blocked_workshop_ids`",
'max_workshop_ids' => "INT NULL AFTER `allow_user_workshop_ids`",
'required_workshop_ids' => "TEXT NULL AFTER `max_workshop_ids`",
'blocked_workshop_ids' => "TEXT NULL AFTER `launch_param_additions`",
);
foreach ($addon_columns as $col => $definition) {
$escaped_col = $db->realEscapeSingle($col);
$escaped_table = $db->realEscapeSingle($prefix . 'addons');
$check = $db->resultQuery(
"SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = '{$escaped_table}'
AND COLUMN_NAME = '{$escaped_col}'"
);
if (empty($check)) {
if (!$db->query("ALTER TABLE `{$prefix}addons` ADD COLUMN `{$col}` {$definition}")) {
return false;
}
}
}
// ── content_id on server_content_workshop ─────────────────────────────
$wk_table = $db->realEscapeSingle($prefix . 'server_content_workshop');
$col_check = $db->resultQuery(
"SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = '{$wk_table}'
AND COLUMN_NAME = 'content_id'"
);
if (empty($col_check)) {
if (!$db->query(
"ALTER TABLE `{$prefix}server_content_workshop`
ADD COLUMN `content_id` INT NULL AFTER `id`,
ADD KEY `idx_content_id` (`content_id`)"
)) {
return false;
}
}
return true;
},
);
?>