diff --git a/Panel/js/modules/addonsmanager.js b/Panel/js/modules/addonsmanager.js index ab52e9df..a8856049 100644 --- a/Panel/js/modules/addonsmanager.js +++ b/Panel/js/modules/addonsmanager.js @@ -9,20 +9,16 @@ $(function() { var methodToRows = { download_zip: ['#scm-row-url', '#scm-row-path'], - steam_workshop: ['#scm-row-workshop-app-id', '#scm-row-target-path-template', '#scm-row-optional-folder-name', '#scm-row-post-script', '#scm-row-launch-param-additions'], + steam_workshop: ['#scm-row-workshop-xml-info'], post_script: ['#scm-row-post-script'], config_edit: ['#scm-row-path', '#scm-row-config-edit-rule'] }; var allRows = [ '#scm-row-url', '#scm-row-path', - '#scm-row-workshop-id', - '#scm-row-workshop-app-id', - '#scm-row-target-path-template', - '#scm-row-optional-folder-name', + '#scm-row-workshop-xml-info', '#scm-row-post-script', - '#scm-row-config-edit-rule', - '#scm-row-launch-param-additions' + '#scm-row-config-edit-rule' ]; var $method = $('#scm-install-method'); var $help = $('#scm-install-method-help'); @@ -49,9 +45,6 @@ $(function() { var $userSelect = $('#scm-user-addon-select'); var $userWorkshopRows = $('.scm-user-workshop-row'); var $userWorkshopId = $('#scm-user-workshop-id'); - var $userWorkshopAppId = $('#scm-user-workshop-app-id'); - var $userTargetTemplate = $('#scm-user-target-path-template'); - var $userOptionalFolderName = $('#scm-user-optional-folder-name'); var $userPreview = $('#scm-user-target-path-preview'); function updateUserWorkshopUi() { @@ -67,20 +60,10 @@ $(function() { if (!$userWorkshopId.val()) { $userWorkshopId.val(String($selected.data('workshopItemId') || '')); } - if (!$userWorkshopAppId.val()) { - $userWorkshopAppId.val(String($selected.data('workshopAppId') || $userWorkshopAppId.data('defaultAppId') || '')); - } - if (!$userTargetTemplate.val()) { - $userTargetTemplate.val(String($selected.data('targetPathTemplate') || $userTargetTemplate.data('defaultTemplate') || '')); - } - if (!$userOptionalFolderName.val()) { - $userOptionalFolderName.val(String($selected.data('optionalFolderName') || '')); - } - var workshopId = $.trim($userWorkshopId.val()); - var workshopAppId = $.trim($userWorkshopAppId.val()) || String($selected.data('workshopAppId') || $userWorkshopAppId.data('defaultAppId') || ''); - var folderName = $.trim($userOptionalFolderName.val()) || (workshopId ? '@' + workshopId : '@{WORKSHOP_ID}'); - var targetTemplate = $.trim($userTargetTemplate.val()) || String($selected.data('targetPathTemplate') || $userTargetTemplate.data('defaultTemplate') || ''); + var workshopAppId = String($userPreview.data('workshopAppId') || ''); + var folderName = workshopId ? '@' + workshopId : '@{WORKSHOP_ID}'; + var targetTemplate = String($userPreview.data('targetTemplate') || $userPreview.text() || ''); var previewValues = { '{SERVER_ROOT}': String($userPreview.data('serverRoot') || ''), '{GAME_ROOT}': String($userPreview.data('gameRoot') || ''), @@ -96,15 +79,9 @@ $(function() { if ($userSelect.length) { $userSelect.on('change', function() { $userWorkshopId.val(String($(this).find('option:selected').data('workshopItemId') || '')); - $userWorkshopAppId.val(String($(this).find('option:selected').data('workshopAppId') || '')); - $userTargetTemplate.val(String($(this).find('option:selected').data('targetPathTemplate') || '')); - $userOptionalFolderName.val(String($(this).find('option:selected').data('optionalFolderName') || '')); updateUserWorkshopUi(); }); $userWorkshopId.on('input', updateUserWorkshopUi); - $userWorkshopAppId.on('input', updateUserWorkshopUi); - $userTargetTemplate.on('input', updateUserWorkshopUi); - $userOptionalFolderName.on('input', updateUserWorkshopUi); updateUserWorkshopUi(); } }); diff --git a/Panel/modules/addonsmanager/addons_installer.php b/Panel/modules/addonsmanager/addons_installer.php index 8dd78e70..b114d154 100644 --- a/Panel/modules/addonsmanager/addons_installer.php +++ b/Panel/modules/addonsmanager/addons_installer.php @@ -184,7 +184,7 @@ function exec_ogp_module() { $content_version = isset($addon_info['content_version']) ? $addon_info['content_version'] : ''; $requires_stop = !empty($addon_info['requires_stop']) ? 1 : 0; $user_override_keys = ($install_method === 'steam_workshop') - ? array('workshop_item_id', 'workshop_app_id', 'target_path_template', 'optional_folder_name') + ? array('workshop_item_id') : array(); $install_payload = scm_collect_install_payload($addon_info, $_REQUEST, $user_override_keys); $post_script = ''; @@ -276,18 +276,23 @@ function exec_ogp_module() { $cache_mode ); $_SESSION['scm_history_id_' . $home_id . '_' . $addon_id] = $history_id; - scm_log_content_install_action(array( - 'addon_id' => (int)$addon_id, - 'addon_name' => isset($addon_info['name']) ? $addon_info['name'] : '', - 'content_type' => $install_method, - 'home_id' => (int)$home_id, - 'home_cfg_id' => (int)$home_info['home_cfg_id'], - 'workshop_id' => isset($install_payload['workshop_item_id']) ? (string)$install_payload['workshop_item_id'] : '', - 'target_path' => ($install_method === 'steam_workshop') - ? (string)$install_payload['target_path_template'] - : (string)$install_payload['path'], - 'action' => 'started', - )); + $log_target_path = (string)$install_payload['path']; + if ($install_method === 'steam_workshop') { + $log_target_path = scm_extract_workshop_install_path($server_xml); + if ($log_target_path === '') { + $log_target_path = scm_get_default_workshop_target_template(scm_detect_workshop_install_strategy($home_info, $server_xml)); + } + } + scm_log_content_install_action(array( + 'addon_id' => (int)$addon_id, + 'addon_name' => isset($addon_info['name']) ? $addon_info['name'] : '', + 'content_type' => $install_method, + 'home_id' => (int)$home_id, + 'home_cfg_id' => (int)$home_info['home_cfg_id'], + 'workshop_id' => isset($install_payload['workshop_item_id']) ? (string)$install_payload['workshop_item_id'] : '', + 'target_path' => $log_target_path, + 'action' => 'started', + )); if ($install_method === 'steam_workshop') { scm_ensure_workshop_schema($db); $workshop_runtime = scm_build_workshop_runtime_context($db, $home_info, $server_xml, $install_payload, $validation_message); @@ -305,15 +310,14 @@ function exec_ogp_module() { 'addon_id' => (int)$addon_id, 'target_path_template' => $target_path_template, 'target_path_resolved' => $target_path_resolved, - 'optional_folder_name' => trim((string)$install_payload['optional_folder_name']), 'config_edit_rule' => trim((string)$addon_info['config_edit_rule']), - 'launch_param_additions' => trim((string)$addon_info['launch_param_additions']), + 'launch_param_additions' => isset($server_xml->workshop_support->startup_param_format) ? trim((string)$server_xml->workshop_support->startup_param_format) : '', 'workshop_app_id' => $workshop_app_id, 'steam_app_id' => $steam_app_id, 'steamcmd_path' => isset($workshop_runtime['steamcmd_path']) ? (string)$workshop_runtime['steamcmd_path'] : '', 'workshop_download_dir' => isset($workshop_runtime['workshop_download_dir']) ? (string)$workshop_runtime['workshop_download_dir'] : '', 'server_root' => isset($workshop_runtime['server_root']) ? (string)$workshop_runtime['server_root'] : rtrim((string)$home_info['home_path'], '/'), - 'post_install_script' => trim((string)$post_script), + 'post_install_script' => scm_workshop_post_install_action($server_xml), ); $workshop_error = ''; $workshop_ok = scm_workshop_write_manifest_and_run($db, $home_info, $server_xml, 'install', array($workshop_item_id), $workshop_error, $extra_manifest); @@ -581,14 +585,11 @@ function exec_ogp_module() { $selected_addon = isset($addons[0]) ? $addons[0] : array(); $default_install_method = isset($selected_addon['install_method']) ? scm_get_install_method_default($selected_addon['install_method']) : ''; $is_workshop_default = ($default_install_method === 'steam_workshop'); - $workshop_profile = function_exists('sw_get_profile_for_home') ? sw_get_profile_for_home($db, (int)$home_id) : false; - $default_workshop_app_id = !empty($selected_addon['workshop_app_id']) - ? trim((string)$selected_addon['workshop_app_id']) - : ((is_array($workshop_profile) && !empty($workshop_profile['workshop_app_id'])) ? (string)$workshop_profile['workshop_app_id'] : scm_extract_workshop_app_id($server_xml)); - $default_target_template = !empty($selected_addon['target_path_template']) - ? trim((string)$selected_addon['target_path_template']) - : ((is_array($workshop_profile) && !empty($workshop_profile['install_path_template'])) ? (string)$workshop_profile['install_path_template'] : '{SERVER_ROOT}/{MOD_FOLDER}'); - $default_optional_folder_name = !empty($selected_addon['optional_folder_name']) ? trim((string)$selected_addon['optional_folder_name']) : ''; + $default_workshop_app_id = scm_extract_workshop_app_id($server_xml); + $default_target_template = scm_extract_workshop_install_path($server_xml); + if ($default_target_template === '') { + $default_target_template = scm_get_default_workshop_target_template(scm_detect_workshop_install_strategy($home_info, $server_xml)); + } ?>
| Workshop App ID Override | - - |
| - - | |
|
-
- Supported placeholders: {SERVER_ROOT}, {GAME_ROOT}, {WORKSHOP_ID}, {WORKSHOP_APP_ID}, {STEAM_APP_ID}, {FOLDER_NAME}, {MOD_FOLDER}
- | |
| Target Path Preview |
+ Workshop App ID, install path, and launch parameter format are defined in the game XML.
|
| diff --git a/Panel/modules/addonsmanager/addons_manager.php b/Panel/modules/addonsmanager/addons_manager.php index d818cb64..8fb52f30 100644 --- a/Panel/modules/addonsmanager/addons_manager.php +++ b/Panel/modules/addonsmanager/addons_manager.php @@ -14,7 +14,7 @@ * 3. A script-driven installer (post_script only, no download required). * 4. A Minecraft server jar / version switcher (future: install_method=minecraft_jar). * 5. A DayZ/Epoch/Arma profile copy (future: install_method=profile_copy). - * 6. A Steam Workshop content bundle (future: install_method=steam_workshop). + * 6. A Steam Workshop entry point (install behavior comes from game XML). * 7. A config preset (type=config). * 8. A full server profile built from multiple actions (type=profile). * @@ -26,6 +26,7 @@ // Central category map — defines all valid addon_type values and their labels. require_once(dirname(__FILE__) . '/server_content_categories.php'); require_once(dirname(__FILE__) . '/server_content_helpers.php'); +require_once("modules/config_games/server_config_parser.php"); function exec_ogp_module() { @@ -59,13 +60,19 @@ function exec_ogp_module() { $fields['restart_after_install'] = !empty($_POST['restart_after_install']) ? 1 : 0; $fields['is_cacheable'] = !empty($_POST['is_cacheable']) ? 1 : 0; $fields['description'] = isset($_POST['description']) ? $_POST['description'] : ''; - $fields['workshop_item_id'] = isset($_POST['workshop_item_id']) ? trim((string)$_POST['workshop_item_id']) : ''; - $fields['workshop_app_id'] = isset($_POST['workshop_app_id']) ? trim((string)$_POST['workshop_app_id']) : ''; - $fields['target_path_template']= isset($_POST['target_path_template']) ? trim((string)$_POST['target_path_template']) : ''; - $fields['optional_folder_name']= isset($_POST['optional_folder_name']) ? trim((string)$_POST['optional_folder_name']) : ''; + $fields['workshop_item_id'] = ''; + $fields['workshop_app_id'] = ''; + $fields['target_path_template']= ''; + $fields['optional_folder_name']= ''; $fields['config_edit_rule'] = isset($_POST['config_edit_rule']) ? trim((string)$_POST['config_edit_rule']) : ''; - $fields['launch_param_additions'] = isset($_POST['launch_param_additions']) ? trim((string)$_POST['launch_param_additions']) : ''; + $fields['launch_param_additions'] = ''; $fields['addon_type'] = scm_get_addon_type_from_install_method($fields['install_method']); + if ($fields['install_method'] === 'steam_workshop') { + $fields['url'] = ''; + $fields['path'] = ''; + $fields['post_script'] = ''; + $fields['config_edit_rule'] = ''; + } if ($fields['name'] === '') { @@ -75,6 +82,10 @@ function exec_ogp_module() { { print_failure(get_lang("select_a_game_type")); } + elseif ($fields['install_method'] === 'steam_workshop' && (!($game_cfg = $db->getGameCfg($fields['home_cfg_id'])) || !($server_xml = read_server_config(SERVER_CONFIG_LOCATION . "/" . $game_cfg['home_cfg_file'])) || !scm_workshop_is_supported($server_xml))) + { + print_failure('Steam Workshop support must be configured in the selected game XML before creating a Workshop content entry.'); + } else { $validation_payload = array( @@ -115,12 +126,7 @@ function exec_ogp_module() { $restart_after_install = isset($_POST['restart_after_install']) ? (int)$_POST['restart_after_install'] : 0; $is_cacheable = isset($_POST['is_cacheable']) ? (int)$_POST['is_cacheable'] : 0; $description = isset($_POST['description']) ? $_POST['description'] : ""; - $workshop_item_id = isset($_POST['workshop_item_id']) ? $_POST['workshop_item_id'] : ""; - $workshop_app_id = isset($_POST['workshop_app_id']) ? $_POST['workshop_app_id'] : ""; - $target_path_template = isset($_POST['target_path_template']) ? $_POST['target_path_template'] : ""; - $optional_folder_name = isset($_POST['optional_folder_name']) ? $_POST['optional_folder_name'] : ""; $config_edit_rule = isset($_POST['config_edit_rule']) ? $_POST['config_edit_rule'] : ""; - $launch_param_additions = isset($_POST['launch_param_additions']) ? $_POST['launch_param_additions'] : ""; if (isset($_POST['addon_id']) && (int)$_POST['addon_id'] > 0 && isset($_POST['edit'])) { @@ -143,12 +149,7 @@ function exec_ogp_module() { $restart_after_install = isset($addon_info['restart_after_install']) ? (int)$addon_info['restart_after_install'] : 0; $is_cacheable = isset($addon_info['is_cacheable']) ? (int)$addon_info['is_cacheable'] : 0; $description = isset($addon_info['description']) ? $addon_info['description'] : ""; - $workshop_item_id = isset($addon_info['workshop_item_id']) ? $addon_info['workshop_item_id'] : ""; - $workshop_app_id = isset($addon_info['workshop_app_id']) ? $addon_info['workshop_app_id'] : ""; - $target_path_template = isset($addon_info['target_path_template']) ? $addon_info['target_path_template'] : ""; - $optional_folder_name = isset($addon_info['optional_folder_name']) ? $addon_info['optional_folder_name'] : ""; $config_edit_rule = isset($addon_info['config_edit_rule']) ? $addon_info['config_edit_rule'] : ""; - $launch_param_additions = isset($addon_info['launch_param_additions']) ? $addon_info['launch_param_additions'] : ""; } ?> | -|
| - Default Workshop IDs (Optional) + Steam Workshop | - - Optional. Users enter the actual Workshop IDs they want installed from their own server page. This field is not required. - | -
| - Game Compatibility (Workshop App ID) - | -- - | -
| - - | -- - Supported placeholders: {HOME_ID}, {SERVER_ROOT}, {GAME_ROOT}, {WORKSHOP_ID}, {WORKSHOP_APP_ID}, {STEAM_APP_ID}, {FOLDER_NAME}, {MOD_FOLDER} - | -
| - - | -
-
+ Steam Workshop behavior is configured by the selected game's XML
workshop_support block. Users install Workshop items from their server management page. |
| - - | -- - | -
diff --git a/Panel/modules/addonsmanager/server_content_actions.php b/Panel/modules/addonsmanager/server_content_actions.php
index bd8ec1a5..66e29001 100644
--- a/Panel/modules/addonsmanager/server_content_actions.php
+++ b/Panel/modules/addonsmanager/server_content_actions.php
@@ -153,9 +153,6 @@ function server_content_resolve_script_path(array $home_info, $script_key, array
if ($server_xml === false) {
return array(false, false);
}
- if ($script_path === '' && $script_key === 'workshop') {
- $script_path = scm_get_workshop_script_path($home_info, $server_xml);
- }
if ($script_path === '' && $script_key !== '' && isset($server_xml->$script_key)) {
$script_path = trim((string)$server_xml->$script_key);
}
@@ -172,10 +169,6 @@ function server_content_execute_manifest($home_id, $manifest_path, $script_key,
if ($server_xml === false) {
return server_content_result('failed', 'Unable to load server XML configuration.', array('home_id' => (int)$home_id));
}
- $script_path = trim((string)$script_path);
- if ($script_path === '' || !preg_match('/^[^\r\n\0]+$/', $script_path)) {
- return server_content_result('failed', 'Configured server content script path is invalid.', array('script_key' => (string)$script_key));
- }
$remote = server_content_create_remote($home_info);
if ($remote->status_chk() !== 1) {
return server_content_result('failed', 'Agent is offline.', array('remote_server_id' => (int)$home_info['remote_server_id']));
@@ -188,8 +181,14 @@ function server_content_execute_manifest($home_id, $manifest_path, $script_key,
}
$script_path = $prepared_path;
}
- elseif ((int)$remote->rfile_exists($script_path) !== 1) {
- return server_content_result('failed', 'Server content script was not found on agent host.', array('script_path' => $script_path));
+ else {
+ $script_path = trim((string)$script_path);
+ if ($script_path === '' || !preg_match('/^[^\r\n\0]+$/', $script_path)) {
+ return server_content_result('failed', 'Configured server content script path is invalid.', array('script_key' => (string)$script_key));
+ }
+ if ((int)$remote->rfile_exists($script_path) !== 1) {
+ return server_content_result('failed', 'Server content script was not found on agent host.', array('script_path' => $script_path));
+ }
}
$command = "bash " . escapeshellarg($script_path) . " " . escapeshellarg((string)$manifest_path) . " ; echo __GSP_SERVER_CONTENT_EXIT:$?";
$output = $remote->exec($command);
@@ -262,7 +261,7 @@ function server_content_install_updates($home_id, $options = array())
}
$manifest_context = scm_workshop_build_manifest_context($db, $home_info, $server_xml, $workshop_ids, array());
if (empty($manifest_context['workshop_app_id'])) {
- return server_content_result('failed', 'Workshop App ID is missing. Configure it in game XML or the Server Content template.');
+ return server_content_result('failed', 'Workshop App ID is missing from the game XML workshop_support block.');
}
if (empty($options['check_only'])) {
scm_workshop_update_rows_state($db, (int)$home_info['home_id'], $workshop_ids, 'installing', null, false, false);
diff --git a/Panel/modules/addonsmanager/server_content_helpers.php b/Panel/modules/addonsmanager/server_content_helpers.php
index c5e6a080..d1d37760 100644
--- a/Panel/modules/addonsmanager/server_content_helpers.php
+++ b/Panel/modules/addonsmanager/server_content_helpers.php
@@ -105,12 +105,30 @@ function scm_ensure_workshop_schema($db)
`tags` TEXT NULL,
`game_key` VARCHAR(128) NULL,
`local_cache_path` VARCHAR(512) NULL,
+ `author` VARCHAR(255) NULL,
+ `thumbnail_url` VARCHAR(512) NULL,
UNIQUE KEY `uniq_workshop_app` (`workshop_id`, `app_id`),
KEY `idx_app_id` (`app_id`),
KEY `idx_install_count` (`install_count`),
KEY `idx_last_installed` (`last_installed`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"
);
+ $catalog_table = $db->realEscapeSingle(OGP_DB_PREFIX . 'server_content_workshop_catalog');
+ foreach (array(
+ 'author' => "VARCHAR(255) NULL AFTER `title`",
+ 'thumbnail_url' => "VARCHAR(512) NULL AFTER `author`",
+ ) as $col => $definition) {
+ $escaped_col = $db->realEscapeSingle($col);
+ $col_check = $db->resultQuery(
+ "SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS
+ WHERE TABLE_SCHEMA = DATABASE()
+ AND TABLE_NAME = '{$catalog_table}'
+ AND COLUMN_NAME = '{$escaped_col}'"
+ );
+ if (empty($col_check)) {
+ $db->query("ALTER TABLE `".OGP_DB_PREFIX."server_content_workshop_catalog` ADD COLUMN `{$col}` {$definition}");
+ }
+ }
return $ok;
}
@@ -225,20 +243,37 @@ function scm_workshop_catalog_sort_sql($sort)
'published_date' => 'published_date DESC, title ASC, workshop_id ASC',
'last_updated' => 'last_updated DESC, title ASC, workshop_id ASC',
'last_installed' => 'last_installed DESC, title ASC, workshop_id ASC',
+ 'workshop_id' => 'CAST(workshop_id AS UNSIGNED) ASC, workshop_id ASC',
);
return isset($allowed[$sort]) ? $allowed[$sort] : $allowed['last_installed'];
}
-function scm_get_workshop_catalog_rows($db, $app_id = '', $sort = 'last_installed', $limit = 50)
+function scm_get_workshop_catalog_rows($db, $app_id = '', $sort = 'last_installed', $limit = 50, $query = '', $tag = '')
{
if (!scm_ensure_workshop_schema($db)) {
return array();
}
- $where = '';
+ $where_parts = array();
$app_id = trim((string)$app_id);
if ($app_id !== '' && preg_match('/^[0-9]+$/', $app_id)) {
- $where = "WHERE app_id='" . $db->realEscapeSingle($app_id) . "'";
+ $where_parts[] = "app_id='" . $db->realEscapeSingle($app_id) . "'";
}
+ $query = trim((string)$query);
+ if ($query !== '') {
+ $query_id = scm_extract_workshop_item_id($query);
+ if ($query_id !== '') {
+ $where_parts[] = "workshop_id='" . $db->realEscapeSingle($query_id) . "'";
+ } else {
+ $like = "%" . $db->realEscapeSingle($query) . "%";
+ $where_parts[] = "(title LIKE '{$like}' OR author LIKE '{$like}' OR tags LIKE '{$like}' OR game_key LIKE '{$like}')";
+ }
+ }
+ $tag = trim((string)$tag);
+ if ($tag !== '') {
+ $like = "%" . $db->realEscapeSingle($tag) . "%";
+ $where_parts[] = "tags LIKE '{$like}'";
+ }
+ $where = empty($where_parts) ? '' : ('WHERE ' . implode(' AND ', $where_parts));
$limit = (int)$limit;
if ($limit <= 0 || $limit > 200) {
$limit = 50;
@@ -266,14 +301,18 @@ function scm_workshop_record_catalog_items($db, $workshop_app_id, array $item_id
}
$detail = isset($item_details[$item_id]) && is_array($item_details[$item_id]) ? $item_details[$item_id] : array();
$title = isset($detail['title']) ? (string)$detail['title'] : '';
+ $author = isset($detail['author']) ? (string)$detail['author'] : '';
+ $thumbnail = isset($detail['thumbnail_url']) ? (string)$detail['thumbnail_url'] : '';
$install_path = isset($detail['target_path_resolved']) ? (string)$detail['target_path_resolved'] : '';
$db->query(
"INSERT INTO `".OGP_DB_PREFIX."server_content_workshop_catalog`
- (workshop_id, app_id, title, install_count, first_seen, last_installed, last_updated, game_key, local_cache_path)
+ (workshop_id, app_id, title, author, thumbnail_url, install_count, first_seen, last_installed, last_updated, game_key, local_cache_path)
VALUES (
'".$db->realEscapeSingle($item_id)."',
'".$db->realEscapeSingle($workshop_app_id)."',
".($title === '' ? "NULL" : "'".$db->realEscapeSingle($title)."'").",
+ ".($author === '' ? "NULL" : "'".$db->realEscapeSingle($author)."'").",
+ ".($thumbnail === '' ? "NULL" : "'".$db->realEscapeSingle($thumbnail)."'").",
1,
NOW(),
NOW(),
@@ -284,6 +323,8 @@ function scm_workshop_record_catalog_items($db, $workshop_app_id, array $item_id
ON DUPLICATE KEY UPDATE
install_count=install_count+1,
title=IF(VALUES(title) IS NULL OR VALUES(title)='', title, VALUES(title)),
+ author=IF(VALUES(author) IS NULL OR VALUES(author)='', author, VALUES(author)),
+ thumbnail_url=IF(VALUES(thumbnail_url) IS NULL OR VALUES(thumbnail_url)='', thumbnail_url, VALUES(thumbnail_url)),
last_installed=NOW(),
last_updated=".($mark_update ? "NOW()" : "last_updated").",
game_key=IF(VALUES(game_key) IS NULL OR VALUES(game_key)='', game_key, VALUES(game_key)),
@@ -390,23 +431,20 @@ function scm_extract_workshop_app_id($server_xml)
return $value;
}
}
- $candidates = array(
- 'workshop_app_id',
- 'workshop_appid',
- 'steam_workshop_app_id',
- 'steam_workshop_appid',
- );
- foreach ((array)$candidates as $candidate) {
- if (isset($server_xml->$candidate)) {
- $value = trim((string)$server_xml->$candidate);
- if ($value !== '' && preg_match('/^[0-9]+$/', $value)) {
- return $value;
- }
- }
- }
return "";
}
+function scm_workshop_is_supported($server_xml)
+{
+ if (!isset($server_xml->workshop_support)) {
+ return false;
+ }
+ if (isset($server_xml->workshop_support->enabled) && !scm_workshop_xml_bool((string)$server_xml->workshop_support->enabled, true)) {
+ return false;
+ }
+ return scm_extract_workshop_app_id($server_xml) !== '';
+}
+
function scm_extract_workshop_steam_app_id($server_xml)
{
if (isset($server_xml->workshop_support->steam_app_id)) {
@@ -444,42 +482,15 @@ function scm_workshop_xml_bool($value, $default = false)
return (bool)$default;
}
-function scm_get_workshop_script_path(array $home_info, $server_xml)
+function scm_workshop_mod_prefix($server_xml)
{
- $key = scm_is_windows_home($home_info) ? 'workshop_script_windows' : 'workshop_script_linux';
- $nested_key = scm_is_windows_home($home_info) ? 'script_windows' : 'script_linux';
- if (isset($server_xml->workshop_support->$nested_key)) {
- $xml_path = trim((string)$server_xml->workshop_support->$nested_key);
- if ($xml_path !== '' && preg_match('/^[^\\r\\n\\0]+$/', $xml_path)) {
- return $xml_path;
+ if (isset($server_xml->workshop_support->mod_prefix)) {
+ $prefix = trim((string)$server_xml->workshop_support->mod_prefix);
+ if ($prefix !== '' && strpos($prefix, '/') === false && strpos($prefix, '\\') === false && strpos($prefix, "\0") === false) {
+ return $prefix;
}
}
- if (isset($server_xml->$key)) {
- $xml_path = trim((string)$server_xml->$key);
- if ($xml_path !== '' && preg_match('/^[^\\r\\n\\0]+$/', $xml_path)) {
- return $xml_path;
- }
- }
- return scm_is_windows_home($home_info) ? SCM_WORKSHOP_SCRIPT_WINDOWS_DEFAULT : SCM_WORKSHOP_SCRIPT_LINUX_DEFAULT;
-}
-
-function scm_get_configured_workshop_script_path(array $home_info, $server_xml)
-{
- $key = scm_is_windows_home($home_info) ? 'workshop_script_windows' : 'workshop_script_linux';
- $nested_key = scm_is_windows_home($home_info) ? 'script_windows' : 'script_linux';
- if (isset($server_xml->workshop_support->$nested_key)) {
- $xml_path = trim((string)$server_xml->workshop_support->$nested_key);
- if ($xml_path !== '' && preg_match('/^[^\\r\\n\\0]+$/', $xml_path)) {
- return $xml_path;
- }
- }
- if (isset($server_xml->$key)) {
- $xml_path = trim((string)$server_xml->$key);
- if ($xml_path !== '' && preg_match('/^[^\\r\\n\\0]+$/', $xml_path)) {
- return $xml_path;
- }
- }
- return '';
+ return '@';
}
function scm_get_bundled_workshop_script_source(array $home_info)
@@ -488,22 +499,6 @@ function scm_get_bundled_workshop_script_source(array $home_info)
return dirname(__FILE__) . '/scripts/workshop/' . $filename;
}
-function scm_is_default_workshop_script_name($script_path)
-{
- $base = basename(trim((string)$script_path));
- return in_array($base, array(SCM_WORKSHOP_SCRIPT_LINUX_DEFAULT, SCM_WORKSHOP_SCRIPT_WINDOWS_DEFAULT), true);
-}
-
-function scm_is_legacy_panel_workshop_script_path($script_path)
-{
- $script_path = trim((string)$script_path);
- if ($script_path === '') {
- return false;
- }
- return (strpos($script_path, '/var/www/html/') === 0 && strpos($script_path, '/Panel/modules/addonsmanager/scripts/workshop/') !== false)
- || (strpos($script_path, '/OGP_User_Files/modules/addonsmanager/scripts/') !== false);
-}
-
function scm_get_agent_managed_workshop_script_path(array $home_info)
{
$home_path = rtrim(clean_path((string)$home_info['home_path']), '/');
@@ -518,16 +513,6 @@ function scm_get_agent_managed_workshop_script_path(array $home_info)
function scm_prepare_workshop_script_for_agent($remote, array $home_info, $server_xml, &$error = '')
{
$error = '';
- $configured_path = scm_get_configured_workshop_script_path($home_info, $server_xml);
- if ($configured_path !== '' && !scm_is_default_workshop_script_name($configured_path) && !scm_is_legacy_panel_workshop_script_path($configured_path)) {
- scm_log_content_install_action(array(
- 'type' => 'workshop_script_deprecated',
- 'home_id' => isset($home_info['home_id']) ? (int)$home_info['home_id'] : 0,
- 'configured_path' => $configured_path,
- 'message' => 'Configured static Workshop script ignored; Server Content generates a per-job script and runs it through the generic agent exec path.',
- ));
- }
-
$source_path = scm_get_bundled_workshop_script_source($home_info);
if (!is_file($source_path)) {
$error = 'Panel Workshop job template is missing: ' . $source_path;
@@ -649,7 +634,7 @@ function scm_get_install_method_help_text()
{
return array(
'download_zip' => 'Download and extract a ZIP, RAR, or archive file.',
- 'steam_workshop' => 'Configure how users may install Steam Workshop items for this game. Users enter the actual Workshop IDs from their server page.',
+ 'steam_workshop' => 'Users install Steam Workshop items from their server page. App IDs, install paths, key-copy rules, and launch parameter format come from the game XML.',
'config_edit' => 'Install configuration files, profiles, or templates.',
'post_script' => 'Run a custom scripted installation process.',
);
@@ -669,7 +654,7 @@ function scm_get_install_method_validation_errors()
{
return array(
'download_zip' => 'Please enter a download URL.',
- 'steam_workshop' => 'Please configure Workshop App ID or ensure the game XML provides one.',
+ 'steam_workshop' => 'Workshop behavior is configured in the game XML.',
'config_edit' => 'Please enter the config target and edit action.',
'post_script' => 'Please enter the installer script/action.',
);
@@ -749,16 +734,6 @@ function scm_validate_download_content(array $payload, &$message = '')
function scm_validate_workshop_content(array $payload, &$message = '')
{
- // workshop_item_id is NOT required for admin content templates.
- // Users supply Workshop IDs on their server page (workshop_content.php).
- if (!scm_validate_numeric_content_value(isset($payload['workshop_app_id']) ? $payload['workshop_app_id'] : '', 'Workshop App ID must be numeric.', $message, true)) {
- return false;
- }
- $folder_name = isset($payload['optional_folder_name']) ? trim((string)$payload['optional_folder_name']) : '';
- if ($folder_name !== '' && (strpos($folder_name, '..') !== false || preg_match('/[\\\\\\/]/', $folder_name))) {
- $message = 'Optional folder name must be a single folder name.';
- return false;
- }
$message = '';
return true;
}
@@ -828,64 +803,21 @@ function scm_validate_install_method_payload($install_method, array $payload, &$
function scm_build_workshop_runtime_context($db, array $home_info, $server_xml, array $payload, &$message = '')
{
- // workshop_item_id is now optional in admin templates; validate only the
- // numeric format constraints (workshop_app_id, optional_folder_name).
if (!scm_validate_workshop_content($payload, $message)) {
return false;
}
$workshop_item_id = trim((string)(isset($payload['workshop_item_id']) ? $payload['workshop_item_id'] : ''));
- $target_path_template = trim((string)(isset($payload['target_path_template']) ? $payload['target_path_template'] : ''));
- $optional_folder_name = trim((string)(isset($payload['optional_folder_name']) ? $payload['optional_folder_name'] : ''));
- $workshop_app_id_override = trim((string)(isset($payload['workshop_app_id']) ? $payload['workshop_app_id'] : ''));
$install_strategy = isset($payload['install_strategy']) ? trim((string)$payload['install_strategy']) : '';
- $fallback_profile = function_exists('sw_get_profile_for_home') ? sw_get_profile_for_home($db, (int)$home_info['home_id']) : false;
- $resolved = function_exists('steam_workshop_install_item_to_home')
- ? steam_workshop_install_item_to_home($db, $home_info, $workshop_item_id, $target_path_template, array(
- 'optional_folder_name' => $optional_folder_name,
- 'workshop_app_id' => $workshop_app_id_override,
- ))
- : array('ok' => false);
-
- if (!empty($resolved['ok'])) {
- $profile = (isset($resolved['profile']) && is_array($resolved['profile'])) ? $resolved['profile'] : array();
- $message = '';
- return array(
- 'workshop_item_id' => $workshop_item_id,
- 'workshop_app_id' => isset($resolved['workshop_app_id']) ? (string)$resolved['workshop_app_id'] : '',
- 'steam_app_id' => isset($resolved['steam_app_id']) ? (string)$resolved['steam_app_id'] : '',
- 'folder_name' => isset($resolved['folder_name']) ? (string)$resolved['folder_name'] : ($optional_folder_name !== '' ? $optional_folder_name : '@' . $workshop_item_id),
- 'target_path_template' => isset($resolved['target_path_template']) ? (string)$resolved['target_path_template'] : $target_path_template,
- 'target_path_resolved' => isset($resolved['target_path_resolved']) ? (string)$resolved['target_path_resolved'] : '',
- 'server_root' => rtrim((string)$home_info['home_path'], '/'),
- 'steamcmd_path' => isset($profile['steamcmd_path']) ? trim((string)$profile['steamcmd_path']) : '',
- 'workshop_download_dir' => (isset($profile['workshop_download_dir_template']) && trim((string)$profile['workshop_download_dir_template']) !== '')
- ? sw_apply_template((string)$profile['workshop_download_dir_template'], (array)$resolved['vars'])
- : '',
- );
- }
-
- $fallback_workshop_app_id = $workshop_app_id_override;
- if ($fallback_workshop_app_id === '' && is_array($fallback_profile) && !empty($fallback_profile['workshop_app_id'])) {
- $fallback_workshop_app_id = (string)$fallback_profile['workshop_app_id'];
- }
- if ($fallback_workshop_app_id === '') {
- $fallback_workshop_app_id = scm_extract_workshop_app_id($server_xml);
- }
- $steam_app_id = (is_array($fallback_profile) && !empty($fallback_profile['steam_app_id'])) ? (string)$fallback_profile['steam_app_id'] : scm_extract_workshop_steam_app_id($server_xml);
- $folder_name = ($optional_folder_name !== '') ? $optional_folder_name : '@' . $workshop_item_id;
- $effective_template = $target_path_template;
- if ($effective_template === '') {
- if (is_array($fallback_profile) && !empty($fallback_profile['install_path_template'])) {
- $effective_template = (string)$fallback_profile['install_path_template'];
- } else {
- $xml_install_path = scm_extract_workshop_install_path($server_xml);
- $effective_template = $xml_install_path !== '' ? $xml_install_path : scm_get_default_workshop_target_template($install_strategy);
- }
- }
+ $workshop_app_id = scm_extract_workshop_app_id($server_xml);
+ $steam_app_id = scm_extract_workshop_steam_app_id($server_xml);
+ $folder_prefix = scm_workshop_mod_prefix($server_xml);
+ $folder_name = $folder_prefix . $workshop_item_id;
+ $xml_install_path = scm_extract_workshop_install_path($server_xml);
+ $effective_template = $xml_install_path !== '' ? $xml_install_path : scm_get_default_workshop_target_template($install_strategy);
$placeholder_map = scm_build_placeholder_map($home_info, array('exe_location' => isset($server_xml->exe_location) ? (string)$server_xml->exe_location : ''), array(
'WORKSHOP_ID' => $workshop_item_id,
- 'WORKSHOP_APP_ID' => $fallback_workshop_app_id,
+ 'WORKSHOP_APP_ID' => $workshop_app_id,
'STEAM_APP_ID' => $steam_app_id,
'FOLDER_NAME' => $folder_name,
'MOD_FOLDER' => $folder_name,
@@ -893,39 +825,25 @@ function scm_build_workshop_runtime_context($db, array $home_info, $server_xml,
$message = '';
return array(
'workshop_item_id' => $workshop_item_id,
- 'workshop_app_id' => $fallback_workshop_app_id,
+ 'workshop_app_id' => $workshop_app_id,
'steam_app_id' => $steam_app_id,
'folder_name' => $folder_name,
'target_path_template' => $effective_template,
'target_path_resolved' => scm_apply_placeholders($effective_template, $placeholder_map),
'server_root' => rtrim((string)$home_info['home_path'], '/'),
- 'steamcmd_path' => (is_array($fallback_profile) && !empty($fallback_profile['steamcmd_path'])) ? (string)$fallback_profile['steamcmd_path'] : '',
+ 'steamcmd_path' => '',
'workshop_download_dir' => '',
);
}
function scm_detect_workshop_install_strategy(array $home_info, $server_xml, array $template = array())
{
- if (!empty($template['install_strategy'])) {
- $strategy = trim((string)$template['install_strategy']);
- if (preg_match('/^[a-z0-9_\-]+$/i', $strategy)) {
- return strtolower($strategy);
- }
- }
if (isset($server_xml->workshop_support->install_strategy)) {
$strategy = trim((string)$server_xml->workshop_support->install_strategy);
if ($strategy !== '' && preg_match('/^[a-z0-9_\-]+$/i', $strategy)) {
return strtolower($strategy);
}
}
- foreach (array('workshop_install_strategy', 'install_strategy') as $tag) {
- if (isset($server_xml->$tag)) {
- $strategy = trim((string)$server_xml->$tag);
- if ($strategy !== '' && preg_match('/^[a-z0-9_\-]+$/i', $strategy)) {
- return strtolower($strategy);
- }
- }
- }
$game_key = strtolower((string)(isset($home_info['game_key']) ? $home_info['game_key'] : ''));
$cfg_file = strtolower((string)(isset($home_info['home_cfg_file']) ? $home_info['home_cfg_file'] : ''));
$name = strtolower((string)(isset($home_info['game_name']) ? $home_info['game_name'] : ''));
@@ -947,11 +865,6 @@ function scm_workshop_should_copy_keys($server_xml, $install_strategy)
return scm_workshop_xml_bool((string)$attrs['enabled'], false);
}
}
- foreach (array('workshop_copy_keys', 'copy_workshop_keys') as $tag) {
- if (isset($server_xml->$tag)) {
- return scm_workshop_xml_bool((string)$server_xml->$tag, false);
- }
- }
return in_array((string)$install_strategy, array('dayz_mod_folder', 'arma_mod_folder'), true);
}
@@ -968,6 +881,18 @@ function scm_workshop_keys_target_path($server_xml, array $home_info)
return scm_apply_placeholders($template, $map);
}
+function scm_workshop_post_install_action($server_xml)
+{
+ if (!isset($server_xml->workshop_support->post_install_action)) {
+ return '';
+ }
+ $action = trim((string)$server_xml->workshop_support->post_install_action);
+ if ($action === '' || strpos($action, "\0") !== false || strpos($action, "\r") !== false || strpos($action, "\n") !== false) {
+ return '';
+ }
+ return $action;
+}
+
function scm_build_placeholder_map(array $home_info, array $server_context = array(), array $overrides = array())
{
$home_id = (int)(isset($home_info['home_id']) ? $home_info['home_id'] : 0);
@@ -1006,6 +931,44 @@ function scm_apply_placeholders($template, array $placeholder_map)
return str_replace(array_keys($placeholder_map), array_values($placeholder_map), $template);
}
+function scm_get_workshop_enabled_games($query = '', $tag = '')
+{
+ $games = array();
+ $config_dir = defined('SERVER_CONFIG_LOCATION') ? SERVER_CONFIG_LOCATION : 'modules/config_games/server_configs/';
+ $schema = defined('XML_SCHEMA') ? XML_SCHEMA : 'modules/config_games/schema_server_config.xml';
+ $query = strtolower(trim((string)$query));
+ $tag = strtolower(trim((string)$tag));
+ foreach (glob(rtrim($config_dir, '/') . '/*.xml') ?: array() as $file) {
+ $xml = @simplexml_load_file($file);
+ if ($xml === false || !scm_workshop_is_supported($xml)) {
+ continue;
+ }
+ $game_key = isset($xml->game_key) ? (string)$xml->game_key : '';
+ $game_name = isset($xml->game_name) ? (string)$xml->game_name : basename($file, '.xml');
+ $app_id = scm_extract_workshop_app_id($xml);
+ $haystack = strtolower($game_key . ' ' . $game_name . ' ' . basename($file) . ' ' . $app_id);
+ if ($query !== '' && strpos($haystack, $query) === false) {
+ continue;
+ }
+ if ($tag !== '' && strpos($haystack, $tag) === false) {
+ continue;
+ }
+ $games[] = array(
+ 'game_key' => $game_key,
+ 'game_name' => $game_name,
+ 'config_file' => basename($file),
+ 'workshop_app_id' => $app_id,
+ 'steam_app_id' => scm_extract_workshop_steam_app_id($xml),
+ 'install_strategy' => isset($xml->workshop_support->install_strategy) ? (string)$xml->workshop_support->install_strategy : '',
+ 'schema' => $schema,
+ );
+ }
+ usort($games, function ($a, $b) {
+ return strcasecmp($a['game_name'] . $a['config_file'], $b['game_name'] . $b['config_file']);
+ });
+ return $games;
+}
+
function scm_content_logs_dir()
{
return dirname(__FILE__) . '/logs';
diff --git a/Panel/modules/addonsmanager/tests/workshop_helpers_test.php b/Panel/modules/addonsmanager/tests/workshop_helpers_test.php
index 25d127f7..51b94b1f 100644
--- a/Panel/modules/addonsmanager/tests/workshop_helpers_test.php
+++ b/Panel/modules/addonsmanager/tests/workshop_helpers_test.php
@@ -88,16 +88,11 @@ scm_workshop_test_assert(isset($windowsRemote->files[$script]), 'writes Windows/
scm_workshop_test_assert(strpos($windowsRemote->files[$script], '+runscript') !== false, 'Windows/Cygwin per-job script runs SteamCMD through runscript');
scm_workshop_test_assert($error === '', 'default Windows/Cygwin job staging does not report missing agent script');
-$configuredXml = simplexml_load_string('Server Content: ".htmlentities($home_info['home_name'])."\n". " | |