gpt woekshop edit

This commit is contained in:
Frank Harris 2026-06-09 08:37:24 -05:00
parent 26c92ef6a2
commit c8287596b5
16 changed files with 6549 additions and 17 deletions

View file

@ -0,0 +1,133 @@
#!/usr/bin/env bash
set -u
MANIFEST="${1:-}"
if [ -z "$MANIFEST" ] || [ ! -f "$MANIFEST" ]; then
echo "ERROR: Workshop manifest missing: $MANIFEST"
exit 2
fi
MANIFEST_DIR="$(dirname "$MANIFEST")"
LOG="$MANIFEST_DIR/workshop_install.log"
touch "$LOG"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG"
}
json_string() {
local key="$1"
perl -0777 -ne '
my $key = shift @ARGV;
if (/"\Q$key\E"\s*:\s*"([^"]*)"/s) {
my $v = $1;
$v =~ s/\\\//\//g;
print $v;
}
' "$key" "$MANIFEST"
}
json_items() {
perl -0777 -ne '
if (/"items"\s*:\s*\[(.*?)\]/s) {
my $x = $1;
while ($x =~ /"([0-9]{3,20})"/g) { print "$1\n"; }
while ($x =~ /(?<![0-9])([0-9]{3,20})(?![0-9])/g) { print "$1\n"; }
}
' "$MANIFEST" | awk '!seen[$0]++'
}
ACTION="$(json_string action)"
APPID="$(json_string workshop_app_id)"
SERVER_PATH="$(json_string server_path)"
[ -z "$ACTION" ] && ACTION="install"
[ -z "$SERVER_PATH" ] && SERVER_PATH="$(pwd)"
if [ -z "$APPID" ]; then
log "ERROR: workshop_app_id missing from manifest."
exit 3
fi
ITEMS="$(json_items)"
if [ -z "$ITEMS" ]; then
log "ERROR: no Workshop item IDs found in manifest."
exit 4
fi
find_steamcmd() {
for c in "${STEAMCMD_PATH:-}" steamcmd steamcmd.sh steamcmd.exe \
/OGP/steamcmd/steamcmd.exe /OGP/steamcmd/steamcmd.sh \
"$SERVER_PATH/steamcmd/steamcmd.exe" "$SERVER_PATH/steamcmd/steamcmd.sh" \
/home/gameserver/steamcmd/steamcmd.sh "$HOME/steamcmd/steamcmd.sh" "$HOME/steamcmd/steamcmd.exe"
do
[ -z "$c" ] && continue
if command -v "$c" >/dev/null 2>&1; then command -v "$c"; return 0; fi
if [ -f "$c" ]; then echo "$c"; return 0; fi
done
return 1
}
log "GSP Workshop job starting. action=$ACTION appid=$APPID server_path=$SERVER_PATH"
if [ "$ACTION" = "remove" ]; then
for id in $ITEMS; do
log "Removing Workshop item files for $id if present."
rm -rf "$SERVER_PATH/@$id" "$SERVER_PATH/workshop/@$id" "$SERVER_PATH/steamapps/workshop/content/$APPID/$id"
done
log "Remove job complete."
exit 0
fi
STEAMCMD="$(find_steamcmd)" || {
log "ERROR: steamcmd was not found. Install SteamCMD on the agent host or set STEAMCMD_PATH."
exit 127
}
RUNSCRIPT="$MANIFEST_DIR/steamcmd_workshop_$$.txt"
{
echo "@ShutdownOnFailedCommand 0"
echo "@NoPromptForPassword 1"
echo "login anonymous"
echo "force_install_dir $SERVER_PATH"
for id in $ITEMS; do
echo "workshop_download_item $APPID $id validate"
done
echo "quit"
} > "$RUNSCRIPT"
log "Using SteamCMD: $STEAMCMD"
log "Running SteamCMD runscript: $RUNSCRIPT"
"$STEAMCMD" +runscript "$RUNSCRIPT" 2>&1 | tee -a "$LOG"
rc=${PIPESTATUS[0]}
if [ "$rc" -ne 0 ]; then
log "ERROR: SteamCMD failed with exit $rc"
exit "$rc"
fi
for id in $ITEMS; do
SRC="$SERVER_PATH/steamapps/workshop/content/$APPID/$id"
DST="$SERVER_PATH/@$id"
if [ ! -d "$SRC" ]; then
log "ERROR: downloaded Workshop source not found: $SRC"
exit 5
fi
log "Installing Workshop item $id to $DST"
rm -rf "$DST"
cp -a "$SRC" "$DST"
if [ -d "$DST/keys" ]; then
mkdir -p "$SERVER_PATH/keys"
find "$DST/keys" -type f -iname '*.bikey' -exec cp -f {} "$SERVER_PATH/keys/" \;
fi
done
log "Workshop job complete."
exit 0

View file

@ -287,6 +287,58 @@ function scm_get_workshop_catalog_rows($db, $app_id = '', $sort = 'last_installe
return is_array($rows) ? $rows : array();
}
function scm_fetch_steam_workshop_details(array $item_ids)
{
$ids = array();
foreach ($item_ids as $id) {
$id = (string)$id;
if (preg_match('/^[0-9]{3,20}$/', $id)) {
$ids[$id] = $id;
}
}
if (empty($ids)) {
return array();
}
$post = array('itemcount' => count($ids));
$i = 0;
foreach (array_values($ids) as $id) {
$post['publishedfileids['.$i.']'] = $id;
$i++;
}
$context = stream_context_create(array('http' => array(
'method' => 'POST',
'header' => "Content-type: application/x-www-form-urlencoded\r\n",
'content' => http_build_query($post),
'timeout' => 8,
)));
$json = @file_get_contents('https://api.steampowered.com/ISteamRemoteStorage/GetPublishedFileDetails/v1/', false, $context);
if ($json === false || $json === '') {
$json = @file_get_contents('http://api.steampowered.com/ISteamRemoteStorage/GetPublishedFileDetails/v1/', false, $context);
}
if ($json === false || $json === '') {
return array();
}
$data = json_decode($json, true);
if (!is_array($data) || empty($data['response']['publishedfiledetails'])) {
return array();
}
$out = array();
foreach ((array)$data['response']['publishedfiledetails'] as $row) {
if (empty($row['publishedfileid'])) {
continue;
}
$id = (string)$row['publishedfileid'];
$out[$id] = array(
'title' => isset($row['title']) ? (string)$row['title'] : '',
'author' => '',
'thumbnail_url' => isset($row['preview_url']) ? (string)$row['preview_url'] : '',
'published_date' => !empty($row['time_created']) ? date('Y-m-d H:i:s', (int)$row['time_created']) : null,
'last_updated' => !empty($row['time_updated']) ? date('Y-m-d H:i:s', (int)$row['time_updated']) : null,
);
}
return $out;
}
function scm_workshop_record_catalog_items($db, $workshop_app_id, array $item_ids, array $home_info = array(), array $item_details = array(), $mark_update = false)
{
if (empty($item_ids) || !scm_ensure_workshop_schema($db)) {
@ -294,6 +346,8 @@ function scm_workshop_record_catalog_items($db, $workshop_app_id, array $item_id
}
$workshop_app_id = preg_match('/^[0-9]+$/', (string)$workshop_app_id) ? (string)$workshop_app_id : '';
$game_key = isset($home_info['game_key']) ? (string)$home_info['game_key'] : '';
$steam_details = scm_fetch_steam_workshop_details($item_ids);
$steam_details = scm_fetch_steam_workshop_details($item_ids);
foreach ($item_ids as $item_id) {
$item_id = (string)$item_id;
if (!preg_match('/^[0-9]+$/', $item_id)) {
@ -303,6 +357,22 @@ function scm_workshop_record_catalog_items($db, $workshop_app_id, array $item_id
$title = isset($detail['title']) ? (string)$detail['title'] : '';
$author = isset($detail['author']) ? (string)$detail['author'] : '';
$thumbnail = isset($detail['thumbnail_url']) ? (string)$detail['thumbnail_url'] : '';
if (isset($steam_details[$item_id])) {
if ($title === '' && !empty($steam_details[$item_id]['title'])) {
$title = (string)$steam_details[$item_id]['title'];
}
if ($thumbnail === '' && !empty($steam_details[$item_id]['thumbnail_url'])) {
$thumbnail = (string)$steam_details[$item_id]['thumbnail_url'];
}
}
if (isset($steam_details[$item_id])) {
if ($title === '' && !empty($steam_details[$item_id]['title'])) {
$title = (string)$steam_details[$item_id]['title'];
}
if ($thumbnail === '' && !empty($steam_details[$item_id]['thumbnail_url'])) {
$thumbnail = (string)$steam_details[$item_id]['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`

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -372,7 +372,29 @@ function scm_workshop_handle_action($db, array $home_info, $user_id, $action, $r
return true;
}
if ($action === 'update_selected' || $action === 'remove_selected' || $action === 'download_selected') {
if ($action === 'remove_selected') {
$item_ids = scm_workshop_filter_existing_ids($db, $home_id, scm_parse_selected_workshop_ids($selected_ids));
if (empty($item_ids)) {
$message = 'Select one or more saved Workshop IDs to remove.';
return false;
}
$escaped_ids = array();
foreach ($item_ids as $item_id) {
$escaped_ids[] = "'" . $db->realEscapeSingle((string)$item_id) . "'";
}
$db->query(
"DELETE FROM `" . OGP_DB_PREFIX . "server_content_workshop`
WHERE home_id=" . (int)$home_id . "
AND workshop_item_id IN (" . implode(",", $escaped_ids) . ")"
);
scm_workshop_log_action($db, $home_id, $user_id, "remove_selected ids=" . implode(',', $item_ids) . " status=db_removed");
$is_error = false;
$message = 'Selected Workshop item(s) removed from this server list. Installed files, if any, can be cleaned up separately.';
return true;
}
if ($action === 'update_selected' || $action === 'download_selected') {
$item_ids = scm_workshop_filter_existing_ids($db, $home_id, scm_parse_selected_workshop_ids($selected_ids));
if (empty($item_ids)) {
$message = 'Select one or more saved Workshop IDs.';

View file

@ -0,0 +1,453 @@
<?php
/*
*
* GSP - Workshop Content actions (Phase 1)
*
*/
require_once("includes/lib_remote.php");
require_once("modules/config_games/server_config_parser.php");
require_once(dirname(__FILE__) . '/server_content_helpers.php');
function scm_workshop_log_action($db, $home_id, $user_id, $message)
{
$db->logger("server_content_workshop home_id=".(int)$home_id." user_id=".(int)$user_id." ".$message);
}
function scm_workshop_update_rows_state($db, $home_id, array $item_ids, $state, $error = null, $mark_install = false, $mark_update = false)
{
if (empty($item_ids)) {
return true;
}
$escaped_ids = array();
foreach ($item_ids as $item_id) {
$escaped_ids[] = "'" . $db->realEscapeSingle((string)$item_id) . "'";
}
$set = array(
"install_state='" . $db->realEscapeSingle($state) . "'",
"updated_at=NOW()",
);
if ($mark_install) {
$set[] = "last_installed_at=NOW()";
}
if ($mark_update) {
$set[] = "last_updated_at=NOW()";
}
if ($error === null) {
$set[] = "last_error=NULL";
} else {
$set[] = "last_error='" . $db->realEscapeSingle($error) . "'";
}
$query = "UPDATE `".OGP_DB_PREFIX."server_content_workshop`
SET ".implode(", ", $set)."
WHERE home_id=".(int)$home_id." AND workshop_item_id IN (".implode(",", $escaped_ids).")";
return (bool)$db->query($query);
}
function scm_workshop_filter_existing_ids($db, $home_id, array $item_ids)
{
if (empty($item_ids)) {
return array();
}
$escaped_ids = array();
foreach ($item_ids as $item_id) {
$escaped_ids[] = "'" . $db->realEscapeSingle((string)$item_id) . "'";
}
$rows = $db->resultQuery(
"SELECT workshop_item_id FROM `".OGP_DB_PREFIX."server_content_workshop`
WHERE home_id=".(int)$home_id." AND workshop_item_id IN (".implode(",", $escaped_ids).")"
);
$allowed = array();
if (is_array($rows)) {
foreach ((array)$rows as $row) {
$allowed[(string)$row['workshop_item_id']] = (string)$row['workshop_item_id'];
}
}
return array_values($allowed);
}
function scm_workshop_get_content_template($db, $addon_id)
{
$addon_id = (int)$addon_id;
if ($addon_id <= 0) {
return array();
}
scm_ensure_phase2_schema($db);
$rows = $db->resultQuery(
"SELECT addon_id, name, content_version, description
FROM `" . OGP_DB_PREFIX . "addons`
WHERE addon_id=" . $addon_id . " AND install_method='steam_workshop'
LIMIT 1"
);
return (is_array($rows) && !empty($rows)) ? $rows[0] : array();
}
function scm_workshop_build_manifest_context($db, array $home_info, $server_xml, array $item_ids, array $template = array())
{
$install_strategy = scm_detect_workshop_install_strategy($home_info, $server_xml, $template);
$copy_keys = scm_workshop_should_copy_keys($server_xml, $install_strategy);
$xml_install_path = scm_extract_workshop_install_path($server_xml);
$keys_target_path = scm_workshop_keys_target_path($server_xml, $home_info);
$item_details = array();
$resolved_app_id = '';
$steam_app_id = '';
foreach ($item_ids as $item_id) {
$payload = array(
'workshop_item_id' => (string)$item_id,
'install_strategy' => $install_strategy,
);
$message = '';
$runtime = scm_build_workshop_runtime_context($db, $home_info, $server_xml, $payload, $message);
if ($runtime === false) {
$runtime = array();
}
$item_app_id = isset($runtime['workshop_app_id']) ? (string)$runtime['workshop_app_id'] : '';
if ($resolved_app_id === '' && $item_app_id !== '') {
$resolved_app_id = $item_app_id;
}
if ($steam_app_id === '' && !empty($runtime['steam_app_id'])) {
$steam_app_id = (string)$runtime['steam_app_id'];
}
$item_details[(string)$item_id] = array(
'workshop_item_id' => (string)$item_id,
'title' => '',
'folder_name' => isset($runtime['folder_name']) && $runtime['folder_name'] !== '' ? (string)$runtime['folder_name'] : '@' . $item_id,
'target_path_template' => isset($runtime['target_path_template']) ? (string)$runtime['target_path_template'] : scm_get_default_workshop_target_template($install_strategy),
'target_path_resolved' => isset($runtime['target_path_resolved']) ? (string)$runtime['target_path_resolved'] : '',
'install_strategy' => $install_strategy,
'copy_keys' => $copy_keys ? 1 : 0,
'keys_target_path' => $keys_target_path,
);
}
if ($resolved_app_id === '') {
$resolved_app_id = scm_extract_workshop_app_id($server_xml);
}
if ($steam_app_id === '') {
$steam_app_id = scm_extract_workshop_steam_app_id($server_xml);
}
return array(
'workshop_app_id' => $resolved_app_id,
'steam_app_id' => $steam_app_id,
'server_root' => rtrim((string)$home_info['home_path'], '/'),
'install_strategy' => $install_strategy,
'copy_keys' => $copy_keys ? 1 : 0,
'target_path_template' => $xml_install_path !== '' ? $xml_install_path : scm_get_default_workshop_target_template($install_strategy),
'keys_target_path' => $keys_target_path,
'post_install_script' => scm_workshop_post_install_action($server_xml),
'launch_param_additions' => isset($server_xml->workshop_support->startup_param_format) ? trim((string)$server_xml->workshop_support->startup_param_format) : '',
'content_template_id' => isset($template['addon_id']) ? (int)$template['addon_id'] : 0,
'content_template_name' => isset($template['name']) ? (string)$template['name'] : '',
'item_details' => $item_details,
);
}
function scm_workshop_write_manifest_and_run($db, array $home_info, $server_xml, $action, array $item_ids, &$error = '', array $extra_manifest = array(), &$result_details = array())
{
$error = '';
$result_details = array();
if (empty($item_ids)) {
$error = 'No Workshop IDs were selected for this action.';
return false;
}
$manifest_path = scm_get_workshop_manifest_path($home_info);
if ($manifest_path === false) {
$error = 'Manifest path validation failed for this server home.';
return false;
}
$home_path = rtrim(clean_path((string)$home_info['home_path']), '/');
if (!scm_path_is_under_home($home_path, $manifest_path)) {
$error = 'Manifest path is outside of the server home.';
return false;
}
$manifest_dir = dirname($manifest_path);
$manifest = array(
'manifest_version' => 1,
'action' => (string)$action,
'home_id' => (int)$home_info['home_id'],
'home_cfg_id' => (int)$home_info['home_cfg_id'],
'game_path' => $home_path,
'server_path' => $home_path,
'workshop_app_id' => (!empty($extra_manifest['workshop_app_id']) ? (string)$extra_manifest['workshop_app_id'] : scm_extract_workshop_app_id($server_xml)),
'steam_app_id' => !empty($extra_manifest['steam_app_id']) ? (string)$extra_manifest['steam_app_id'] : '',
'items' => array_values($item_ids),
'item_details' => !empty($extra_manifest['item_details']) && is_array($extra_manifest['item_details']) ? $extra_manifest['item_details'] : array(),
'install_strategy' => !empty($extra_manifest['install_strategy']) ? (string)$extra_manifest['install_strategy'] : '',
'target_path' => !empty($extra_manifest['target_path_template']) ? (string)$extra_manifest['target_path_template'] : scm_get_default_workshop_target_template(!empty($extra_manifest['install_strategy']) ? (string)$extra_manifest['install_strategy'] : ''),
'generated_at' => date('Y-m-d H:i:s'),
);
if (!empty($extra_manifest)) {
$manifest['extra'] = $extra_manifest;
}
$manifest_json = json_encode($manifest);
if ($manifest_json === false) {
$error = 'Failed to encode workshop manifest JSON.';
return false;
}
$remote = new OGPRemoteLibrary(
$home_info['agent_ip'],
$home_info['agent_port'],
$home_info['encryption_key'],
$home_info['timeout']
);
$remote->exec("mkdir -p " . escapeshellarg($manifest_dir));
if ((int)$remote->remote_writefile($manifest_path, $manifest_json) !== 1) {
$error = 'Failed to write workshop manifest to remote server.';
return false;
}
$script_path = scm_prepare_workshop_script_for_agent($remote, $home_info, $server_xml, $error);
if ($script_path === false) {
return false;
}
$command = "bash " . escapeshellarg($script_path) . " " . escapeshellarg($manifest_path) . " ; echo __GSP_WORKSHOP_EXIT:$?";
$output = $remote->exec($command);
if (!is_string($output) || $output === '') {
$error = 'Workshop script did not return an execution status.';
return false;
}
if (!preg_match('/__GSP_WORKSHOP_EXIT:(\d+)/', $output, $matches)) {
$error = 'Workshop script exit marker not found in output.';
return false;
}
$result_details = array(
'manifest_path' => $manifest_path,
'script_path' => $script_path,
'log_path' => clean_path($manifest_dir . (scm_is_windows_home($home_info) ? '/workshop_install_windows.log' : '/workshop_install.log')),
'output' => trim(preg_replace('/__GSP_WORKSHOP_EXIT:\d+/', '', $output)),
);
$exit_code = (int)$matches[1];
if ($exit_code !== 0) {
$error = 'Workshop script failed (exit '.$exit_code.'): '.trim($output);
return false;
}
return true;
}
function scm_workshop_handle_action($db, array $home_info, $user_id, $action, $raw_ids, array $selected_ids, &$message, &$is_error, $addon_id = 0, array $options = array())
{
$message = '';
$is_error = true;
if (!scm_ensure_workshop_schema($db)) {
$message = 'Workshop schema migration failed.';
return false;
}
scm_ensure_phase2_schema($db);
$home_id = (int)$home_info['home_id'];
$user_id = (int)$user_id;
$addon_id = (int)$addon_id;
$server_xml = read_server_config(SERVER_CONFIG_LOCATION . "/" . $home_info['home_cfg_file']);
if ($server_xml === false) {
$message = 'Unable to read server configuration for workshop action.';
return false;
}
if (!scm_workshop_is_supported($server_xml)) {
$message = 'This game XML does not enable Steam Workshop support. Add a valid workshop_support block before installing Workshop items.';
return false;
}
$template = scm_workshop_get_content_template($db, $addon_id);
if ($action === 'install_new') {
$invalid = array();
$item_ids = scm_parse_workshop_ids($raw_ids, $invalid);
if (!empty($invalid)) {
$message = 'Invalid Workshop item entries. Use a numeric Workshop ID or Steam Workshop URL: ' . implode(', ', $invalid);
return false;
}
if (empty($item_ids)) {
$message = 'Enter at least one Steam Workshop ID or Workshop URL.';
return false;
}
$manifest_context = scm_workshop_build_manifest_context($db, $home_info, $server_xml, $item_ids, $template);
$resolved_app_id = isset($manifest_context['workshop_app_id']) ? (string)$manifest_context['workshop_app_id'] : '';
if ($resolved_app_id === '') {
$message = 'Workshop App ID is missing from the game XML workshop_support block.';
return false;
}
// Check whether the content_id column exists (added in db_version 6).
$has_content_id_col = (bool)$db->resultQuery(
"SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = '" . $db->realEscapeSingle(OGP_DB_PREFIX . 'server_content_workshop') . "'
AND COLUMN_NAME = 'content_id'"
);
$next_order_rows = $db->resultQuery(
"SELECT COALESCE(MAX(load_order), 0) AS max_order FROM `".OGP_DB_PREFIX."server_content_workshop`
WHERE home_id=".$home_id
);
$next_order = (is_array($next_order_rows) && isset($next_order_rows[0]['max_order'])) ? (int)$next_order_rows[0]['max_order'] : 0;
foreach ($item_ids as $item_id) {
$item_detail = isset($manifest_context['item_details'][(string)$item_id]) ? $manifest_context['item_details'][(string)$item_id] : array();
$install_path = isset($item_detail['target_path_resolved']) ? (string)$item_detail['target_path_resolved'] : '';
$install_strategy = isset($item_detail['install_strategy']) ? (string)$item_detail['install_strategy'] : (string)$manifest_context['install_strategy'];
$next_order++;
$content_id_col = $has_content_id_col && $addon_id > 0 ? ", content_id" : '';
$content_id_val = $has_content_id_col && $addon_id > 0 ? ", " . $addon_id : '';
$content_id_upd = $has_content_id_col && $addon_id > 0 ? ", content_id=VALUES(content_id)" : '';
$query = "INSERT INTO `".OGP_DB_PREFIX."server_content_workshop`
(home_id, home_cfg_id, remote_server_id, workshop_app_id, workshop_item_id, install_path, install_strategy, enabled, load_order, install_state, created_by, created_at, updated_at" . $content_id_col . ")
VALUES (
".$home_id.",
".(int)$home_info['home_cfg_id'].",
".(int)$home_info['remote_server_id'].",
'".$db->realEscapeSingle($resolved_app_id)."',
'".$db->realEscapeSingle($item_id)."',
'".$db->realEscapeSingle($install_path)."',
'".$db->realEscapeSingle($install_strategy)."',
1,
".$next_order.",
'queued',
".$user_id.",
NOW(),
NOW()
" . $content_id_val . "
)
ON DUPLICATE KEY UPDATE
home_cfg_id=VALUES(home_cfg_id),
remote_server_id=VALUES(remote_server_id),
workshop_app_id=VALUES(workshop_app_id),
install_path=VALUES(install_path),
install_strategy=VALUES(install_strategy),
enabled=1,
install_state='queued',
last_error=NULL,
updated_at=NOW()" . $content_id_upd;
$db->query($query);
}
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'installing', null, false, false);
$error = '';
$details = array();
$ok = scm_workshop_write_manifest_and_run($db, $home_info, $server_xml, 'install', $item_ids, $error, $manifest_context, $details);
if ($ok) {
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'installed', null, true, true);
scm_workshop_record_catalog_items($db, $resolved_app_id, $item_ids, $home_info, isset($manifest_context['item_details']) ? $manifest_context['item_details'] : array(), true);
scm_workshop_log_action($db, $home_id, $user_id, "install_new ids=".implode(',', $item_ids)." addon_id=".$addon_id." status=success");
$is_error = false;
$message = 'Workshop item(s) installed successfully. Manifest: '.scm_h(isset($details['manifest_path']) ? $details['manifest_path'] : '').' Log: '.scm_h(isset($details['log_path']) ? $details['log_path'] : '');
return true;
}
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'failed', $error, false, false);
scm_workshop_log_action($db, $home_id, $user_id, "install_new ids=".implode(',', $item_ids)." addon_id=".$addon_id." status=failed error=".$error);
$message = $error;
return false;
}
if ($action === 'enable_selected' || $action === 'disable_selected' || $action === 'save_update_policy') {
$item_ids = scm_workshop_filter_existing_ids($db, $home_id, scm_parse_selected_workshop_ids($selected_ids));
if (empty($item_ids)) {
$message = 'Select one or more saved Workshop IDs.';
return false;
}
if ($action === 'save_update_policy') {
$policy = isset($options['update_policy']) ? (string)$options['update_policy'] : 'manual';
if (!scm_workshop_set_update_policy($db, $home_id, $item_ids, $policy)) {
$message = 'Failed to save Workshop update policy.';
return false;
}
$is_error = false;
$message = 'Workshop update policy saved for selected item(s).';
return true;
}
$enabled = ($action === 'enable_selected') ? 1 : 0;
if (!scm_workshop_set_enabled($db, $home_id, $item_ids, $enabled)) {
$message = 'Failed to update Workshop enabled state.';
return false;
}
$is_error = false;
$message = $enabled ? 'Selected Workshop item(s) enabled.' : 'Selected Workshop item(s) disabled.';
return true;
}
if ($action === 'update_selected' || $action === 'remove_selected' || $action === 'download_selected') {
$item_ids = scm_workshop_filter_existing_ids($db, $home_id, scm_parse_selected_workshop_ids($selected_ids));
if (empty($item_ids)) {
$message = 'Select one or more saved Workshop IDs.';
return false;
}
$target_action = ($action === 'remove_selected') ? 'remove' : (($action === 'download_selected') ? 'download_only' : 'update');
$manifest_context = scm_workshop_build_manifest_context($db, $home_info, $server_xml, $item_ids, $template);
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'installing', null, false, false);
$error = '';
$details = array();
$ok = scm_workshop_write_manifest_and_run($db, $home_info, $server_xml, $target_action, $item_ids, $error, $manifest_context, $details);
if ($ok) {
if ($target_action === 'remove') {
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'removed', null, false, true);
} elseif ($target_action === 'download_only') {
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'downloaded', null, false, true);
scm_workshop_set_update_policy($db, $home_id, $item_ids, 'install_on_restart');
scm_workshop_record_catalog_items($db, isset($manifest_context['workshop_app_id']) ? (string)$manifest_context['workshop_app_id'] : '', $item_ids, $home_info, isset($manifest_context['item_details']) ? $manifest_context['item_details'] : array(), true);
} else {
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'installed', null, false, true);
scm_workshop_record_catalog_items($db, isset($manifest_context['workshop_app_id']) ? (string)$manifest_context['workshop_app_id'] : '', $item_ids, $home_info, isset($manifest_context['item_details']) ? $manifest_context['item_details'] : array(), true);
}
scm_workshop_log_action($db, $home_id, $user_id, $action." ids=".implode(',', $item_ids)." addon_id=".$addon_id." status=success");
$is_error = false;
if ($target_action === 'remove') {
$message = 'Selected Workshop item(s) removed.';
} elseif ($target_action === 'download_only') {
$message = 'Selected Workshop item(s) downloaded and marked for install on next restart.';
} else {
$message = 'Selected Workshop item(s) updated successfully.';
}
$message .= ' Log: ' . scm_h(isset($details['log_path']) ? $details['log_path'] : '');
return true;
}
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'failed', $error, false, false);
scm_workshop_log_action($db, $home_id, $user_id, $action." ids=".implode(',', $item_ids)." addon_id=".$addon_id." status=failed error=".$error);
$message = $error;
return false;
}
if ($action === 'update_all') {
$rows = $db->resultQuery(
"SELECT workshop_item_id FROM `".OGP_DB_PREFIX."server_content_workshop`
WHERE home_id=".$home_id." AND install_state<>'removed'"
);
$item_ids = array();
if (is_array($rows)) {
foreach ((array)$rows as $row) {
$item_ids[] = (string)$row['workshop_item_id'];
}
}
$item_ids = scm_parse_selected_workshop_ids($item_ids);
if (empty($item_ids)) {
$message = 'No Workshop IDs are currently saved for this server.';
return false;
}
$manifest_context = scm_workshop_build_manifest_context($db, $home_info, $server_xml, $item_ids, $template);
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'installing', null, false, false);
$error = '';
$details = array();
$ok = scm_workshop_write_manifest_and_run($db, $home_info, $server_xml, 'update', $item_ids, $error, $manifest_context, $details);
if ($ok) {
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'installed', null, false, true);
scm_workshop_record_catalog_items($db, isset($manifest_context['workshop_app_id']) ? (string)$manifest_context['workshop_app_id'] : '', $item_ids, $home_info, isset($manifest_context['item_details']) ? $manifest_context['item_details'] : array(), true);
scm_workshop_log_action($db, $home_id, $user_id, "update_all ids=".implode(',', $item_ids)." addon_id=".$addon_id." status=success");
$is_error = false;
$message = 'All saved Workshop item(s) updated successfully. Log: ' . scm_h(isset($details['log_path']) ? $details['log_path'] : '');
return true;
}
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'failed', $error, false, false);
scm_workshop_log_action($db, $home_id, $user_id, "update_all ids=".implode(',', $item_ids)." addon_id=".$addon_id." status=failed error=".$error);
$message = $error;
return false;
}
$message = 'Invalid workshop action.';
return false;
}

View file

@ -0,0 +1,453 @@
<?php
/*
*
* GSP - Workshop Content actions (Phase 1)
*
*/
require_once("includes/lib_remote.php");
require_once("modules/config_games/server_config_parser.php");
require_once(dirname(__FILE__) . '/server_content_helpers.php');
function scm_workshop_log_action($db, $home_id, $user_id, $message)
{
$db->logger("server_content_workshop home_id=".(int)$home_id." user_id=".(int)$user_id." ".$message);
}
function scm_workshop_update_rows_state($db, $home_id, array $item_ids, $state, $error = null, $mark_install = false, $mark_update = false)
{
if (empty($item_ids)) {
return true;
}
$escaped_ids = array();
foreach ($item_ids as $item_id) {
$escaped_ids[] = "'" . $db->realEscapeSingle((string)$item_id) . "'";
}
$set = array(
"install_state='" . $db->realEscapeSingle($state) . "'",
"updated_at=NOW()",
);
if ($mark_install) {
$set[] = "last_installed_at=NOW()";
}
if ($mark_update) {
$set[] = "last_updated_at=NOW()";
}
if ($error === null) {
$set[] = "last_error=NULL";
} else {
$set[] = "last_error='" . $db->realEscapeSingle($error) . "'";
}
$query = "UPDATE `".OGP_DB_PREFIX."server_content_workshop`
SET ".implode(", ", $set)."
WHERE home_id=".(int)$home_id." AND workshop_item_id IN (".implode(",", $escaped_ids).")";
return (bool)$db->query($query);
}
function scm_workshop_filter_existing_ids($db, $home_id, array $item_ids)
{
if (empty($item_ids)) {
return array();
}
$escaped_ids = array();
foreach ($item_ids as $item_id) {
$escaped_ids[] = "'" . $db->realEscapeSingle((string)$item_id) . "'";
}
$rows = $db->resultQuery(
"SELECT workshop_item_id FROM `".OGP_DB_PREFIX."server_content_workshop`
WHERE home_id=".(int)$home_id." AND workshop_item_id IN (".implode(",", $escaped_ids).")"
);
$allowed = array();
if (is_array($rows)) {
foreach ((array)$rows as $row) {
$allowed[(string)$row['workshop_item_id']] = (string)$row['workshop_item_id'];
}
}
return array_values($allowed);
}
function scm_workshop_get_content_template($db, $addon_id)
{
$addon_id = (int)$addon_id;
if ($addon_id <= 0) {
return array();
}
scm_ensure_phase2_schema($db);
$rows = $db->resultQuery(
"SELECT addon_id, name, content_version, description
FROM `" . OGP_DB_PREFIX . "addons`
WHERE addon_id=" . $addon_id . " AND install_method='steam_workshop'
LIMIT 1"
);
return (is_array($rows) && !empty($rows)) ? $rows[0] : array();
}
function scm_workshop_build_manifest_context($db, array $home_info, $server_xml, array $item_ids, array $template = array())
{
$install_strategy = scm_detect_workshop_install_strategy($home_info, $server_xml, $template);
$copy_keys = scm_workshop_should_copy_keys($server_xml, $install_strategy);
$xml_install_path = scm_extract_workshop_install_path($server_xml);
$keys_target_path = scm_workshop_keys_target_path($server_xml, $home_info);
$item_details = array();
$resolved_app_id = '';
$steam_app_id = '';
foreach ($item_ids as $item_id) {
$payload = array(
'workshop_item_id' => (string)$item_id,
'install_strategy' => $install_strategy,
);
$message = '';
$runtime = scm_build_workshop_runtime_context($db, $home_info, $server_xml, $payload, $message);
if ($runtime === false) {
$runtime = array();
}
$item_app_id = isset($runtime['workshop_app_id']) ? (string)$runtime['workshop_app_id'] : '';
if ($resolved_app_id === '' && $item_app_id !== '') {
$resolved_app_id = $item_app_id;
}
if ($steam_app_id === '' && !empty($runtime['steam_app_id'])) {
$steam_app_id = (string)$runtime['steam_app_id'];
}
$item_details[(string)$item_id] = array(
'workshop_item_id' => (string)$item_id,
'title' => '',
'folder_name' => isset($runtime['folder_name']) && $runtime['folder_name'] !== '' ? (string)$runtime['folder_name'] : '@' . $item_id,
'target_path_template' => isset($runtime['target_path_template']) ? (string)$runtime['target_path_template'] : scm_get_default_workshop_target_template($install_strategy),
'target_path_resolved' => isset($runtime['target_path_resolved']) ? (string)$runtime['target_path_resolved'] : '',
'install_strategy' => $install_strategy,
'copy_keys' => $copy_keys ? 1 : 0,
'keys_target_path' => $keys_target_path,
);
}
if ($resolved_app_id === '') {
$resolved_app_id = scm_extract_workshop_app_id($server_xml);
}
if ($steam_app_id === '') {
$steam_app_id = scm_extract_workshop_steam_app_id($server_xml);
}
return array(
'workshop_app_id' => $resolved_app_id,
'steam_app_id' => $steam_app_id,
'server_root' => rtrim((string)$home_info['home_path'], '/'),
'install_strategy' => $install_strategy,
'copy_keys' => $copy_keys ? 1 : 0,
'target_path_template' => $xml_install_path !== '' ? $xml_install_path : scm_get_default_workshop_target_template($install_strategy),
'keys_target_path' => $keys_target_path,
'post_install_script' => scm_workshop_post_install_action($server_xml),
'launch_param_additions' => isset($server_xml->workshop_support->startup_param_format) ? trim((string)$server_xml->workshop_support->startup_param_format) : '',
'content_template_id' => isset($template['addon_id']) ? (int)$template['addon_id'] : 0,
'content_template_name' => isset($template['name']) ? (string)$template['name'] : '',
'item_details' => $item_details,
);
}
function scm_workshop_write_manifest_and_run($db, array $home_info, $server_xml, $action, array $item_ids, &$error = '', array $extra_manifest = array(), &$result_details = array())
{
$error = '';
$result_details = array();
if (empty($item_ids)) {
$error = 'No Workshop IDs were selected for this action.';
return false;
}
$manifest_path = scm_get_workshop_manifest_path($home_info);
if ($manifest_path === false) {
$error = 'Manifest path validation failed for this server home.';
return false;
}
$home_path = rtrim(clean_path((string)$home_info['home_path']), '/');
if (!scm_path_is_under_home($home_path, $manifest_path)) {
$error = 'Manifest path is outside of the server home.';
return false;
}
$manifest_dir = dirname($manifest_path);
$manifest = array(
'manifest_version' => 1,
'action' => (string)$action,
'home_id' => (int)$home_info['home_id'],
'home_cfg_id' => (int)$home_info['home_cfg_id'],
'game_path' => $home_path,
'server_path' => $home_path,
'workshop_app_id' => (!empty($extra_manifest['workshop_app_id']) ? (string)$extra_manifest['workshop_app_id'] : scm_extract_workshop_app_id($server_xml)),
'steam_app_id' => !empty($extra_manifest['steam_app_id']) ? (string)$extra_manifest['steam_app_id'] : '',
'items' => array_values($item_ids),
'item_details' => !empty($extra_manifest['item_details']) && is_array($extra_manifest['item_details']) ? $extra_manifest['item_details'] : array(),
'install_strategy' => !empty($extra_manifest['install_strategy']) ? (string)$extra_manifest['install_strategy'] : '',
'target_path' => !empty($extra_manifest['target_path_template']) ? (string)$extra_manifest['target_path_template'] : scm_get_default_workshop_target_template(!empty($extra_manifest['install_strategy']) ? (string)$extra_manifest['install_strategy'] : ''),
'generated_at' => date('Y-m-d H:i:s'),
);
if (!empty($extra_manifest)) {
$manifest['extra'] = $extra_manifest;
}
$manifest_json = json_encode($manifest);
if ($manifest_json === false) {
$error = 'Failed to encode workshop manifest JSON.';
return false;
}
$remote = new OGPRemoteLibrary(
$home_info['agent_ip'],
$home_info['agent_port'],
$home_info['encryption_key'],
$home_info['timeout']
);
$remote->exec("mkdir -p " . escapeshellarg($manifest_dir));
if ((int)$remote->remote_writefile($manifest_path, $manifest_json) !== 1) {
$error = 'Failed to write workshop manifest to remote server.';
return false;
}
$script_path = scm_prepare_workshop_script_for_agent($remote, $home_info, $server_xml, $error);
if ($script_path === false) {
return false;
}
$command = "bash " . escapeshellarg($script_path) . " " . escapeshellarg($manifest_path) . " ; echo __GSP_WORKSHOP_EXIT:$?";
$output = $remote->exec($command);
if (!is_string($output) || $output === '') {
$error = 'Workshop script did not return an execution status.';
return false;
}
if (!preg_match('/__GSP_WORKSHOP_EXIT:(\d+)/', $output, $matches)) {
$error = 'Workshop script exit marker not found in output.';
return false;
}
$result_details = array(
'manifest_path' => $manifest_path,
'script_path' => $script_path,
'log_path' => clean_path($manifest_dir . (scm_is_windows_home($home_info) ? '/workshop_install_windows.log' : '/workshop_install.log')),
'output' => trim(preg_replace('/__GSP_WORKSHOP_EXIT:\d+/', '', $output)),
);
$exit_code = (int)$matches[1];
if ($exit_code !== 0) {
$error = 'Workshop script failed (exit '.$exit_code.'): '.trim($output);
return false;
}
return true;
}
function scm_workshop_handle_action($db, array $home_info, $user_id, $action, $raw_ids, array $selected_ids, &$message, &$is_error, $addon_id = 0, array $options = array())
{
$message = '';
$is_error = true;
if (!scm_ensure_workshop_schema($db)) {
$message = 'Workshop schema migration failed.';
return false;
}
scm_ensure_phase2_schema($db);
$home_id = (int)$home_info['home_id'];
$user_id = (int)$user_id;
$addon_id = (int)$addon_id;
$server_xml = read_server_config(SERVER_CONFIG_LOCATION . "/" . $home_info['home_cfg_file']);
if ($server_xml === false) {
$message = 'Unable to read server configuration for workshop action.';
return false;
}
if (!scm_workshop_is_supported($server_xml)) {
$message = 'This game XML does not enable Steam Workshop support. Add a valid workshop_support block before installing Workshop items.';
return false;
}
$template = scm_workshop_get_content_template($db, $addon_id);
if ($action === 'install_new') {
$invalid = array();
$item_ids = scm_parse_workshop_ids($raw_ids, $invalid);
if (!empty($invalid)) {
$message = 'Invalid Workshop item entries. Use a numeric Workshop ID or Steam Workshop URL: ' . implode(', ', $invalid);
return false;
}
if (empty($item_ids)) {
$message = 'Enter at least one Steam Workshop ID or Workshop URL.';
return false;
}
$manifest_context = scm_workshop_build_manifest_context($db, $home_info, $server_xml, $item_ids, $template);
$resolved_app_id = isset($manifest_context['workshop_app_id']) ? (string)$manifest_context['workshop_app_id'] : '';
if ($resolved_app_id === '') {
$message = 'Workshop App ID is missing from the game XML workshop_support block.';
return false;
}
// Check whether the content_id column exists (added in db_version 6).
$has_content_id_col = (bool)$db->resultQuery(
"SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = '" . $db->realEscapeSingle(OGP_DB_PREFIX . 'server_content_workshop') . "'
AND COLUMN_NAME = 'content_id'"
);
$next_order_rows = $db->resultQuery(
"SELECT COALESCE(MAX(load_order), 0) AS max_order FROM `".OGP_DB_PREFIX."server_content_workshop`
WHERE home_id=".$home_id
);
$next_order = (is_array($next_order_rows) && isset($next_order_rows[0]['max_order'])) ? (int)$next_order_rows[0]['max_order'] : 0;
foreach ($item_ids as $item_id) {
$item_detail = isset($manifest_context['item_details'][(string)$item_id]) ? $manifest_context['item_details'][(string)$item_id] : array();
$install_path = isset($item_detail['target_path_resolved']) ? (string)$item_detail['target_path_resolved'] : '';
$install_strategy = isset($item_detail['install_strategy']) ? (string)$item_detail['install_strategy'] : (string)$manifest_context['install_strategy'];
$next_order++;
$content_id_col = $has_content_id_col && $addon_id > 0 ? ", content_id" : '';
$content_id_val = $has_content_id_col && $addon_id > 0 ? ", " . $addon_id : '';
$content_id_upd = $has_content_id_col && $addon_id > 0 ? ", content_id=VALUES(content_id)" : '';
$query = "INSERT INTO `".OGP_DB_PREFIX."server_content_workshop`
(home_id, home_cfg_id, remote_server_id, workshop_app_id, workshop_item_id, install_path, install_strategy, enabled, load_order, install_state, created_by, created_at, updated_at" . $content_id_col . ")
VALUES (
".$home_id.",
".(int)$home_info['home_cfg_id'].",
".(int)$home_info['remote_server_id'].",
'".$db->realEscapeSingle($resolved_app_id)."',
'".$db->realEscapeSingle($item_id)."',
'".$db->realEscapeSingle($install_path)."',
'".$db->realEscapeSingle($install_strategy)."',
1,
".$next_order.",
'queued',
".$user_id.",
NOW(),
NOW()
" . $content_id_val . "
)
ON DUPLICATE KEY UPDATE
home_cfg_id=VALUES(home_cfg_id),
remote_server_id=VALUES(remote_server_id),
workshop_app_id=VALUES(workshop_app_id),
install_path=VALUES(install_path),
install_strategy=VALUES(install_strategy),
enabled=1,
install_state='queued',
last_error=NULL,
updated_at=NOW()" . $content_id_upd;
$db->query($query);
}
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'installing', null, false, false);
$error = '';
$details = array();
$ok = scm_workshop_write_manifest_and_run($db, $home_info, $server_xml, 'install', $item_ids, $error, $manifest_context, $details);
if ($ok) {
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'installed', null, true, true);
scm_workshop_record_catalog_items($db, $resolved_app_id, $item_ids, $home_info, isset($manifest_context['item_details']) ? $manifest_context['item_details'] : array(), true);
scm_workshop_log_action($db, $home_id, $user_id, "install_new ids=".implode(',', $item_ids)." addon_id=".$addon_id." status=success");
$is_error = false;
$message = 'Workshop item(s) installed successfully. Manifest: '.scm_h(isset($details['manifest_path']) ? $details['manifest_path'] : '').' Log: '.scm_h(isset($details['log_path']) ? $details['log_path'] : '');
return true;
}
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'failed', $error, false, false);
scm_workshop_log_action($db, $home_id, $user_id, "install_new ids=".implode(',', $item_ids)." addon_id=".$addon_id." status=failed error=".$error);
$message = $error;
return false;
}
if ($action === 'enable_selected' || $action === 'disable_selected' || $action === 'save_update_policy') {
$item_ids = scm_workshop_filter_existing_ids($db, $home_id, scm_parse_selected_workshop_ids($selected_ids));
if (empty($item_ids)) {
$message = 'Select one or more saved Workshop IDs.';
return false;
}
if ($action === 'save_update_policy') {
$policy = isset($options['update_policy']) ? (string)$options['update_policy'] : 'manual';
if (!scm_workshop_set_update_policy($db, $home_id, $item_ids, $policy)) {
$message = 'Failed to save Workshop update policy.';
return false;
}
$is_error = false;
$message = 'Workshop update policy saved for selected item(s).';
return true;
}
$enabled = ($action === 'enable_selected') ? 1 : 0;
if (!scm_workshop_set_enabled($db, $home_id, $item_ids, $enabled)) {
$message = 'Failed to update Workshop enabled state.';
return false;
}
$is_error = false;
$message = $enabled ? 'Selected Workshop item(s) enabled.' : 'Selected Workshop item(s) disabled.';
return true;
}
if ($action === 'update_selected' || $action === 'remove_selected' || $action === 'download_selected') {
$item_ids = scm_workshop_filter_existing_ids($db, $home_id, scm_parse_selected_workshop_ids($selected_ids));
if (empty($item_ids)) {
$message = 'Select one or more saved Workshop IDs.';
return false;
}
$target_action = ($action === 'remove_selected') ? 'remove' : (($action === 'download_selected') ? 'download_only' : 'update');
$manifest_context = scm_workshop_build_manifest_context($db, $home_info, $server_xml, $item_ids, $template);
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'installing', null, false, false);
$error = '';
$details = array();
$ok = scm_workshop_write_manifest_and_run($db, $home_info, $server_xml, $target_action, $item_ids, $error, $manifest_context, $details);
if ($ok) {
if ($target_action === 'remove') {
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'removed', null, false, true);
} elseif ($target_action === 'download_only') {
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'downloaded', null, false, true);
scm_workshop_set_update_policy($db, $home_id, $item_ids, 'install_on_restart');
scm_workshop_record_catalog_items($db, isset($manifest_context['workshop_app_id']) ? (string)$manifest_context['workshop_app_id'] : '', $item_ids, $home_info, isset($manifest_context['item_details']) ? $manifest_context['item_details'] : array(), true);
} else {
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'installed', null, false, true);
scm_workshop_record_catalog_items($db, isset($manifest_context['workshop_app_id']) ? (string)$manifest_context['workshop_app_id'] : '', $item_ids, $home_info, isset($manifest_context['item_details']) ? $manifest_context['item_details'] : array(), true);
}
scm_workshop_log_action($db, $home_id, $user_id, $action." ids=".implode(',', $item_ids)." addon_id=".$addon_id." status=success");
$is_error = false;
if ($target_action === 'remove') {
$message = 'Selected Workshop item(s) removed.';
} elseif ($target_action === 'download_only') {
$message = 'Selected Workshop item(s) downloaded and marked for install on next restart.';
} else {
$message = 'Selected Workshop item(s) updated successfully.';
}
$message .= ' Log: ' . scm_h(isset($details['log_path']) ? $details['log_path'] : '');
return true;
}
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'failed', $error, false, false);
scm_workshop_log_action($db, $home_id, $user_id, $action." ids=".implode(',', $item_ids)." addon_id=".$addon_id." status=failed error=".$error);
$message = $error;
return false;
}
if ($action === 'update_all') {
$rows = $db->resultQuery(
"SELECT workshop_item_id FROM `".OGP_DB_PREFIX."server_content_workshop`
WHERE home_id=".$home_id." AND install_state<>'removed'"
);
$item_ids = array();
if (is_array($rows)) {
foreach ((array)$rows as $row) {
$item_ids[] = (string)$row['workshop_item_id'];
}
}
$item_ids = scm_parse_selected_workshop_ids($item_ids);
if (empty($item_ids)) {
$message = 'No Workshop IDs are currently saved for this server.';
return false;
}
$manifest_context = scm_workshop_build_manifest_context($db, $home_info, $server_xml, $item_ids, $template);
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'installing', null, false, false);
$error = '';
$details = array();
$ok = scm_workshop_write_manifest_and_run($db, $home_info, $server_xml, 'update', $item_ids, $error, $manifest_context, $details);
if ($ok) {
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'installed', null, false, true);
scm_workshop_record_catalog_items($db, isset($manifest_context['workshop_app_id']) ? (string)$manifest_context['workshop_app_id'] : '', $item_ids, $home_info, isset($manifest_context['item_details']) ? $manifest_context['item_details'] : array(), true);
scm_workshop_log_action($db, $home_id, $user_id, "update_all ids=".implode(',', $item_ids)." addon_id=".$addon_id." status=success");
$is_error = false;
$message = 'All saved Workshop item(s) updated successfully. Log: ' . scm_h(isset($details['log_path']) ? $details['log_path'] : '');
return true;
}
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'failed', $error, false, false);
scm_workshop_log_action($db, $home_id, $user_id, "update_all ids=".implode(',', $item_ids)." addon_id=".$addon_id." status=failed error=".$error);
$message = $error;
return false;
}
$message = 'Invalid workshop action.';
return false;
}

View file

@ -0,0 +1,377 @@
<?php
/*
*
* GSP - Workshop Content actions (Phase 1)
*
*/
require_once("includes/lib_remote.php");
require_once("modules/config_games/server_config_parser.php");
require_once(dirname(__FILE__) . '/server_content_helpers.php');
function scm_workshop_log_action($db, $home_id, $user_id, $message)
{
$db->logger("server_content_workshop home_id=".(int)$home_id." user_id=".(int)$user_id." ".$message);
}
function scm_workshop_update_rows_state($db, $home_id, array $item_ids, $state, $error = null, $mark_install = false, $mark_update = false)
{
if (empty($item_ids)) {
return true;
}
$escaped_ids = array();
foreach ($item_ids as $item_id) {
$escaped_ids[] = "'" . $db->realEscapeSingle((string)$item_id) . "'";
}
$set = array(
"install_state='" . $db->realEscapeSingle($state) . "'",
"updated_at=NOW()",
);
if ($mark_install) {
$set[] = "last_installed_at=NOW()";
}
if ($mark_update) {
$set[] = "last_updated_at=NOW()";
}
if ($error === null) {
$set[] = "last_error=NULL";
} else {
$set[] = "last_error='" . $db->realEscapeSingle($error) . "'";
}
$query = "UPDATE `".OGP_DB_PREFIX."server_content_workshop`
SET ".implode(", ", $set)."
WHERE home_id=".(int)$home_id." AND workshop_item_id IN (".implode(",", $escaped_ids).")";
return (bool)$db->query($query);
}
function scm_workshop_filter_existing_ids($db, $home_id, array $item_ids)
{
if (empty($item_ids)) {
return array();
}
$escaped_ids = array();
foreach ($item_ids as $item_id) {
$escaped_ids[] = "'" . $db->realEscapeSingle((string)$item_id) . "'";
}
$rows = $db->resultQuery(
"SELECT workshop_item_id FROM `".OGP_DB_PREFIX."server_content_workshop`
WHERE home_id=".(int)$home_id." AND workshop_item_id IN (".implode(",", $escaped_ids).")"
);
$allowed = array();
if (is_array($rows)) {
foreach ((array)$rows as $row) {
$allowed[(string)$row['workshop_item_id']] = (string)$row['workshop_item_id'];
}
}
return array_values($allowed);
}
function scm_workshop_get_content_template($db, $addon_id)
{
$addon_id = (int)$addon_id;
if ($addon_id <= 0) {
return array();
}
scm_ensure_phase2_schema($db);
$rows = $db->resultQuery(
"SELECT addon_id, name, content_version, description
FROM `" . OGP_DB_PREFIX . "addons`
WHERE addon_id=" . $addon_id . " AND install_method='steam_workshop'
LIMIT 1"
);
return (is_array($rows) && !empty($rows)) ? $rows[0] : array();
}
function scm_workshop_build_manifest_context($db, array $home_info, $server_xml, array $item_ids, array $template = array())
{
$install_strategy = scm_detect_workshop_install_strategy($home_info, $server_xml, $template);
$copy_keys = scm_workshop_should_copy_keys($server_xml, $install_strategy);
$xml_install_path = scm_extract_workshop_install_path($server_xml);
$keys_target_path = scm_workshop_keys_target_path($server_xml, $home_info);
$item_details = array();
$resolved_app_id = '';
$steam_app_id = '';
foreach ($item_ids as $item_id) {
$payload = array(
'workshop_item_id' => (string)$item_id,
'install_strategy' => $install_strategy,
);
$message = '';
$runtime = scm_build_workshop_runtime_context($db, $home_info, $server_xml, $payload, $message);
if ($runtime === false) {
$runtime = array();
}
$item_app_id = isset($runtime['workshop_app_id']) ? (string)$runtime['workshop_app_id'] : '';
if ($resolved_app_id === '' && $item_app_id !== '') {
$resolved_app_id = $item_app_id;
}
if ($steam_app_id === '' && !empty($runtime['steam_app_id'])) {
$steam_app_id = (string)$runtime['steam_app_id'];
}
$item_details[(string)$item_id] = array(
'workshop_item_id' => (string)$item_id,
'title' => '',
'folder_name' => isset($runtime['folder_name']) && $runtime['folder_name'] !== '' ? (string)$runtime['folder_name'] : '@' . $item_id,
'target_path_template' => isset($runtime['target_path_template']) ? (string)$runtime['target_path_template'] : scm_get_default_workshop_target_template($install_strategy),
'target_path_resolved' => isset($runtime['target_path_resolved']) ? (string)$runtime['target_path_resolved'] : '',
'install_strategy' => $install_strategy,
'copy_keys' => $copy_keys ? 1 : 0,
'keys_target_path' => $keys_target_path,
);
}
if ($resolved_app_id === '') {
$resolved_app_id = scm_extract_workshop_app_id($server_xml);
}
if ($steam_app_id === '') {
$steam_app_id = scm_extract_workshop_steam_app_id($server_xml);
}
return array(
'workshop_app_id' => $resolved_app_id,
'steam_app_id' => $steam_app_id,
'server_root' => rtrim((string)$home_info['home_path'], '/'),
'install_strategy' => $install_strategy,
'copy_keys' => $copy_keys ? 1 : 0,
'target_path_template' => $xml_install_path !== '' ? $xml_install_path : scm_get_default_workshop_target_template($install_strategy),
'keys_target_path' => $keys_target_path,
'post_install_script' => scm_workshop_post_install_action($server_xml),
'launch_param_additions' => isset($server_xml->workshop_support->startup_param_format) ? trim((string)$server_xml->workshop_support->startup_param_format) : '',
'content_template_id' => isset($template['addon_id']) ? (int)$template['addon_id'] : 0,
'content_template_name' => isset($template['name']) ? (string)$template['name'] : '',
'item_details' => $item_details,
);
}
function scm_workshop_write_manifest_and_run($db, array $home_info, $server_xml, $action, array $item_ids, &$error = '', array $extra_manifest = array(), &$result_details = array())
{
$error = '';
$result_details = array();
$error = 'Workshop install/update is currently blocked because the legacy static agent-side workshop script path is still being used. The workshop system must be migrated to Panel-generated SteamCMD job execution.';
return false;
}
function scm_workshop_handle_action($db, array $home_info, $user_id, $action, $raw_ids, array $selected_ids, &$message, &$is_error, $addon_id = 0, array $options = array())
{
$message = '';
$is_error = true;
if (!scm_ensure_workshop_schema($db)) {
$message = 'Workshop schema migration failed.';
return false;
}
scm_ensure_phase2_schema($db);
$home_id = (int)$home_info['home_id'];
$user_id = (int)$user_id;
$addon_id = (int)$addon_id;
$server_xml = read_server_config(SERVER_CONFIG_LOCATION . "/" . $home_info['home_cfg_file']);
if ($server_xml === false) {
$message = 'Unable to read server configuration for workshop action.';
return false;
}
if (!scm_workshop_is_supported($server_xml)) {
$message = 'This game XML does not enable Steam Workshop support. Add a valid workshop_support block before installing Workshop items.';
return false;
}
$template = scm_workshop_get_content_template($db, $addon_id);
if ($action === 'install_new') {
$invalid = array();
$item_ids = scm_parse_workshop_ids($raw_ids, $invalid);
if (!empty($invalid)) {
$message = 'Invalid Workshop item entries. Use a numeric Workshop ID or Steam Workshop URL: ' . implode(', ', $invalid);
return false;
}
if (empty($item_ids)) {
$message = 'Enter at least one Steam Workshop ID or Workshop URL.';
return false;
}
$manifest_context = scm_workshop_build_manifest_context($db, $home_info, $server_xml, $item_ids, $template);
$resolved_app_id = isset($manifest_context['workshop_app_id']) ? (string)$manifest_context['workshop_app_id'] : '';
if ($resolved_app_id === '') {
$message = 'Workshop App ID is missing from the game XML workshop_support block.';
return false;
}
// Check whether the content_id column exists (added in db_version 6).
$has_content_id_col = (bool)$db->resultQuery(
"SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = '" . $db->realEscapeSingle(OGP_DB_PREFIX . 'server_content_workshop') . "'
AND COLUMN_NAME = 'content_id'"
);
$next_order_rows = $db->resultQuery(
"SELECT COALESCE(MAX(load_order), 0) AS max_order FROM `".OGP_DB_PREFIX."server_content_workshop`
WHERE home_id=".$home_id
);
$next_order = (is_array($next_order_rows) && isset($next_order_rows[0]['max_order'])) ? (int)$next_order_rows[0]['max_order'] : 0;
foreach ($item_ids as $item_id) {
$item_detail = isset($manifest_context['item_details'][(string)$item_id]) ? $manifest_context['item_details'][(string)$item_id] : array();
$install_path = isset($item_detail['target_path_resolved']) ? (string)$item_detail['target_path_resolved'] : '';
$install_strategy = isset($item_detail['install_strategy']) ? (string)$item_detail['install_strategy'] : (string)$manifest_context['install_strategy'];
$next_order++;
$content_id_col = $has_content_id_col && $addon_id > 0 ? ", content_id" : '';
$content_id_val = $has_content_id_col && $addon_id > 0 ? ", " . $addon_id : '';
$content_id_upd = $has_content_id_col && $addon_id > 0 ? ", content_id=VALUES(content_id)" : '';
$query = "INSERT INTO `".OGP_DB_PREFIX."server_content_workshop`
(home_id, home_cfg_id, remote_server_id, workshop_app_id, workshop_item_id, install_path, install_strategy, enabled, load_order, install_state, created_by, created_at, updated_at" . $content_id_col . ")
VALUES (
".$home_id.",
".(int)$home_info['home_cfg_id'].",
".(int)$home_info['remote_server_id'].",
'".$db->realEscapeSingle($resolved_app_id)."',
'".$db->realEscapeSingle($item_id)."',
'".$db->realEscapeSingle($install_path)."',
'".$db->realEscapeSingle($install_strategy)."',
1,
".$next_order.",
'queued',
".$user_id.",
NOW(),
NOW()
" . $content_id_val . "
)
ON DUPLICATE KEY UPDATE
home_cfg_id=VALUES(home_cfg_id),
remote_server_id=VALUES(remote_server_id),
workshop_app_id=VALUES(workshop_app_id),
install_path=VALUES(install_path),
install_strategy=VALUES(install_strategy),
enabled=1,
install_state='queued',
last_error=NULL,
updated_at=NOW()" . $content_id_upd;
$db->query($query);
}
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'installing', null, false, false);
$error = '';
$details = array();
$ok = scm_workshop_write_manifest_and_run($db, $home_info, $server_xml, 'install', $item_ids, $error, $manifest_context, $details);
if ($ok) {
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'installed', null, true, true);
scm_workshop_record_catalog_items($db, $resolved_app_id, $item_ids, $home_info, isset($manifest_context['item_details']) ? $manifest_context['item_details'] : array(), true);
scm_workshop_log_action($db, $home_id, $user_id, "install_new ids=".implode(',', $item_ids)." addon_id=".$addon_id." status=success");
$is_error = false;
$message = 'Workshop item(s) installed successfully. Manifest: '.scm_h(isset($details['manifest_path']) ? $details['manifest_path'] : '').' Log: '.scm_h(isset($details['log_path']) ? $details['log_path'] : '');
return true;
}
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'failed', $error, false, false);
scm_workshop_log_action($db, $home_id, $user_id, "install_new ids=".implode(',', $item_ids)." addon_id=".$addon_id." status=failed error=".$error);
$message = $error;
return false;
}
if ($action === 'enable_selected' || $action === 'disable_selected' || $action === 'save_update_policy') {
$item_ids = scm_workshop_filter_existing_ids($db, $home_id, scm_parse_selected_workshop_ids($selected_ids));
if (empty($item_ids)) {
$message = 'Select one or more saved Workshop IDs.';
return false;
}
if ($action === 'save_update_policy') {
$policy = isset($options['update_policy']) ? (string)$options['update_policy'] : 'manual';
if (!scm_workshop_set_update_policy($db, $home_id, $item_ids, $policy)) {
$message = 'Failed to save Workshop update policy.';
return false;
}
$is_error = false;
$message = 'Workshop update policy saved for selected item(s).';
return true;
}
$enabled = ($action === 'enable_selected') ? 1 : 0;
if (!scm_workshop_set_enabled($db, $home_id, $item_ids, $enabled)) {
$message = 'Failed to update Workshop enabled state.';
return false;
}
$is_error = false;
$message = $enabled ? 'Selected Workshop item(s) enabled.' : 'Selected Workshop item(s) disabled.';
return true;
}
if ($action === 'update_selected' || $action === 'remove_selected' || $action === 'download_selected') {
$item_ids = scm_workshop_filter_existing_ids($db, $home_id, scm_parse_selected_workshop_ids($selected_ids));
if (empty($item_ids)) {
$message = 'Select one or more saved Workshop IDs.';
return false;
}
$target_action = ($action === 'remove_selected') ? 'remove' : (($action === 'download_selected') ? 'download_only' : 'update');
$manifest_context = scm_workshop_build_manifest_context($db, $home_info, $server_xml, $item_ids, $template);
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'installing', null, false, false);
$error = '';
$details = array();
$ok = scm_workshop_write_manifest_and_run($db, $home_info, $server_xml, $target_action, $item_ids, $error, $manifest_context, $details);
if ($ok) {
if ($target_action === 'remove') {
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'removed', null, false, true);
} elseif ($target_action === 'download_only') {
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'downloaded', null, false, true);
scm_workshop_set_update_policy($db, $home_id, $item_ids, 'install_on_restart');
scm_workshop_record_catalog_items($db, isset($manifest_context['workshop_app_id']) ? (string)$manifest_context['workshop_app_id'] : '', $item_ids, $home_info, isset($manifest_context['item_details']) ? $manifest_context['item_details'] : array(), true);
} else {
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'installed', null, false, true);
scm_workshop_record_catalog_items($db, isset($manifest_context['workshop_app_id']) ? (string)$manifest_context['workshop_app_id'] : '', $item_ids, $home_info, isset($manifest_context['item_details']) ? $manifest_context['item_details'] : array(), true);
}
scm_workshop_log_action($db, $home_id, $user_id, $action." ids=".implode(',', $item_ids)." addon_id=".$addon_id." status=success");
$is_error = false;
if ($target_action === 'remove') {
$message = 'Selected Workshop item(s) removed.';
} elseif ($target_action === 'download_only') {
$message = 'Selected Workshop item(s) downloaded and marked for install on next restart.';
} else {
$message = 'Selected Workshop item(s) updated successfully.';
}
$message .= ' Log: ' . scm_h(isset($details['log_path']) ? $details['log_path'] : '');
return true;
}
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'failed', $error, false, false);
scm_workshop_log_action($db, $home_id, $user_id, $action." ids=".implode(',', $item_ids)." addon_id=".$addon_id." status=failed error=".$error);
$message = $error;
return false;
}
if ($action === 'update_all') {
$rows = $db->resultQuery(
"SELECT workshop_item_id FROM `".OGP_DB_PREFIX."server_content_workshop`
WHERE home_id=".$home_id." AND install_state<>'removed'"
);
$item_ids = array();
if (is_array($rows)) {
foreach ((array)$rows as $row) {
$item_ids[] = (string)$row['workshop_item_id'];
}
}
$item_ids = scm_parse_selected_workshop_ids($item_ids);
if (empty($item_ids)) {
$message = 'No Workshop IDs are currently saved for this server.';
return false;
}
$manifest_context = scm_workshop_build_manifest_context($db, $home_info, $server_xml, $item_ids, $template);
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'installing', null, false, false);
$error = '';
$details = array();
$ok = scm_workshop_write_manifest_and_run($db, $home_info, $server_xml, 'update', $item_ids, $error, $manifest_context, $details);
if ($ok) {
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'installed', null, false, true);
scm_workshop_record_catalog_items($db, isset($manifest_context['workshop_app_id']) ? (string)$manifest_context['workshop_app_id'] : '', $item_ids, $home_info, isset($manifest_context['item_details']) ? $manifest_context['item_details'] : array(), true);
scm_workshop_log_action($db, $home_id, $user_id, "update_all ids=".implode(',', $item_ids)." addon_id=".$addon_id." status=success");
$is_error = false;
$message = 'All saved Workshop item(s) updated successfully. Log: ' . scm_h(isset($details['log_path']) ? $details['log_path'] : '');
return true;
}
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'failed', $error, false, false);
scm_workshop_log_action($db, $home_id, $user_id, "update_all ids=".implode(',', $item_ids)." addon_id=".$addon_id." status=failed error=".$error);
$message = $error;
return false;
}
$message = 'Invalid workshop action.';
return false;
}

View file

@ -0,0 +1,453 @@
<?php
/*
*
* GSP - Workshop Content actions (Phase 1)
*
*/
require_once("includes/lib_remote.php");
require_once("modules/config_games/server_config_parser.php");
require_once(dirname(__FILE__) . '/server_content_helpers.php');
function scm_workshop_log_action($db, $home_id, $user_id, $message)
{
$db->logger("server_content_workshop home_id=".(int)$home_id." user_id=".(int)$user_id." ".$message);
}
function scm_workshop_update_rows_state($db, $home_id, array $item_ids, $state, $error = null, $mark_install = false, $mark_update = false)
{
if (empty($item_ids)) {
return true;
}
$escaped_ids = array();
foreach ($item_ids as $item_id) {
$escaped_ids[] = "'" . $db->realEscapeSingle((string)$item_id) . "'";
}
$set = array(
"install_state='" . $db->realEscapeSingle($state) . "'",
"updated_at=NOW()",
);
if ($mark_install) {
$set[] = "last_installed_at=NOW()";
}
if ($mark_update) {
$set[] = "last_updated_at=NOW()";
}
if ($error === null) {
$set[] = "last_error=NULL";
} else {
$set[] = "last_error='" . $db->realEscapeSingle($error) . "'";
}
$query = "UPDATE `".OGP_DB_PREFIX."server_content_workshop`
SET ".implode(", ", $set)."
WHERE home_id=".(int)$home_id." AND workshop_item_id IN (".implode(",", $escaped_ids).")";
return (bool)$db->query($query);
}
function scm_workshop_filter_existing_ids($db, $home_id, array $item_ids)
{
if (empty($item_ids)) {
return array();
}
$escaped_ids = array();
foreach ($item_ids as $item_id) {
$escaped_ids[] = "'" . $db->realEscapeSingle((string)$item_id) . "'";
}
$rows = $db->resultQuery(
"SELECT workshop_item_id FROM `".OGP_DB_PREFIX."server_content_workshop`
WHERE home_id=".(int)$home_id." AND workshop_item_id IN (".implode(",", $escaped_ids).")"
);
$allowed = array();
if (is_array($rows)) {
foreach ((array)$rows as $row) {
$allowed[(string)$row['workshop_item_id']] = (string)$row['workshop_item_id'];
}
}
return array_values($allowed);
}
function scm_workshop_get_content_template($db, $addon_id)
{
$addon_id = (int)$addon_id;
if ($addon_id <= 0) {
return array();
}
scm_ensure_phase2_schema($db);
$rows = $db->resultQuery(
"SELECT addon_id, name, content_version, description
FROM `" . OGP_DB_PREFIX . "addons`
WHERE addon_id=" . $addon_id . " AND install_method='steam_workshop'
LIMIT 1"
);
return (is_array($rows) && !empty($rows)) ? $rows[0] : array();
}
function scm_workshop_build_manifest_context($db, array $home_info, $server_xml, array $item_ids, array $template = array())
{
$install_strategy = scm_detect_workshop_install_strategy($home_info, $server_xml, $template);
$copy_keys = scm_workshop_should_copy_keys($server_xml, $install_strategy);
$xml_install_path = scm_extract_workshop_install_path($server_xml);
$keys_target_path = scm_workshop_keys_target_path($server_xml, $home_info);
$item_details = array();
$resolved_app_id = '';
$steam_app_id = '';
foreach ($item_ids as $item_id) {
$payload = array(
'workshop_item_id' => (string)$item_id,
'install_strategy' => $install_strategy,
);
$message = '';
$runtime = scm_build_workshop_runtime_context($db, $home_info, $server_xml, $payload, $message);
if ($runtime === false) {
$runtime = array();
}
$item_app_id = isset($runtime['workshop_app_id']) ? (string)$runtime['workshop_app_id'] : '';
if ($resolved_app_id === '' && $item_app_id !== '') {
$resolved_app_id = $item_app_id;
}
if ($steam_app_id === '' && !empty($runtime['steam_app_id'])) {
$steam_app_id = (string)$runtime['steam_app_id'];
}
$item_details[(string)$item_id] = array(
'workshop_item_id' => (string)$item_id,
'title' => '',
'folder_name' => isset($runtime['folder_name']) && $runtime['folder_name'] !== '' ? (string)$runtime['folder_name'] : '@' . $item_id,
'target_path_template' => isset($runtime['target_path_template']) ? (string)$runtime['target_path_template'] : scm_get_default_workshop_target_template($install_strategy),
'target_path_resolved' => isset($runtime['target_path_resolved']) ? (string)$runtime['target_path_resolved'] : '',
'install_strategy' => $install_strategy,
'copy_keys' => $copy_keys ? 1 : 0,
'keys_target_path' => $keys_target_path,
);
}
if ($resolved_app_id === '') {
$resolved_app_id = scm_extract_workshop_app_id($server_xml);
}
if ($steam_app_id === '') {
$steam_app_id = scm_extract_workshop_steam_app_id($server_xml);
}
return array(
'workshop_app_id' => $resolved_app_id,
'steam_app_id' => $steam_app_id,
'server_root' => rtrim((string)$home_info['home_path'], '/'),
'install_strategy' => $install_strategy,
'copy_keys' => $copy_keys ? 1 : 0,
'target_path_template' => $xml_install_path !== '' ? $xml_install_path : scm_get_default_workshop_target_template($install_strategy),
'keys_target_path' => $keys_target_path,
'post_install_script' => scm_workshop_post_install_action($server_xml),
'launch_param_additions' => isset($server_xml->workshop_support->startup_param_format) ? trim((string)$server_xml->workshop_support->startup_param_format) : '',
'content_template_id' => isset($template['addon_id']) ? (int)$template['addon_id'] : 0,
'content_template_name' => isset($template['name']) ? (string)$template['name'] : '',
'item_details' => $item_details,
);
}
function scm_workshop_write_manifest_and_run($db, array $home_info, $server_xml, $action, array $item_ids, &$error = '', array $extra_manifest = array(), &$result_details = array())
{
$error = '';
$result_details = array();
if (empty($item_ids)) {
$error = 'No Workshop IDs were selected for this action.';
return false;
}
$manifest_path = scm_get_workshop_manifest_path($home_info);
if ($manifest_path === false) {
$error = 'Manifest path validation failed for this server home.';
return false;
}
$home_path = rtrim(clean_path((string)$home_info['home_path']), '/');
if (!scm_path_is_under_home($home_path, $manifest_path)) {
$error = 'Manifest path is outside of the server home.';
return false;
}
$manifest_dir = dirname($manifest_path);
$manifest = array(
'manifest_version' => 1,
'action' => (string)$action,
'home_id' => (int)$home_info['home_id'],
'home_cfg_id' => (int)$home_info['home_cfg_id'],
'game_path' => $home_path,
'server_path' => $home_path,
'workshop_app_id' => (!empty($extra_manifest['workshop_app_id']) ? (string)$extra_manifest['workshop_app_id'] : scm_extract_workshop_app_id($server_xml)),
'steam_app_id' => !empty($extra_manifest['steam_app_id']) ? (string)$extra_manifest['steam_app_id'] : '',
'items' => array_values($item_ids),
'item_details' => !empty($extra_manifest['item_details']) && is_array($extra_manifest['item_details']) ? $extra_manifest['item_details'] : array(),
'install_strategy' => !empty($extra_manifest['install_strategy']) ? (string)$extra_manifest['install_strategy'] : '',
'target_path' => !empty($extra_manifest['target_path_template']) ? (string)$extra_manifest['target_path_template'] : scm_get_default_workshop_target_template(!empty($extra_manifest['install_strategy']) ? (string)$extra_manifest['install_strategy'] : ''),
'generated_at' => date('Y-m-d H:i:s'),
);
if (!empty($extra_manifest)) {
$manifest['extra'] = $extra_manifest;
}
$manifest_json = json_encode($manifest);
if ($manifest_json === false) {
$error = 'Failed to encode workshop manifest JSON.';
return false;
}
$remote = new OGPRemoteLibrary(
$home_info['agent_ip'],
$home_info['agent_port'],
$home_info['encryption_key'],
$home_info['timeout']
);
$remote->exec("mkdir -p " . escapeshellarg($manifest_dir));
if ((int)$remote->remote_writefile($manifest_path, $manifest_json) !== 1) {
$error = 'Failed to write workshop manifest to remote server.';
return false;
}
$script_path = scm_prepare_workshop_script_for_agent($remote, $home_info, $server_xml, $error);
if ($script_path === false) {
return false;
}
$command = "bash " . escapeshellarg($script_path) . " " . escapeshellarg($manifest_path) . " ; echo __GSP_WORKSHOP_EXIT:$?";
$output = $remote->exec($command);
if (!is_string($output) || $output === '') {
$error = 'Workshop script did not return an execution status.';
return false;
}
if (!preg_match('/__GSP_WORKSHOP_EXIT:(\d+)/', $output, $matches)) {
$error = 'Workshop script exit marker not found in output.';
return false;
}
$result_details = array(
'manifest_path' => $manifest_path,
'script_path' => $script_path,
'log_path' => clean_path($manifest_dir . (scm_is_windows_home($home_info) ? '/workshop_install_windows.log' : '/workshop_install.log')),
'output' => trim(preg_replace('/__GSP_WORKSHOP_EXIT:\d+/', '', $output)),
);
$exit_code = (int)$matches[1];
if ($exit_code !== 0) {
$error = 'Workshop script failed (exit '.$exit_code.'): '.trim($output);
return false;
}
return true;
}
function scm_workshop_handle_action($db, array $home_info, $user_id, $action, $raw_ids, array $selected_ids, &$message, &$is_error, $addon_id = 0, array $options = array())
{
$message = '';
$is_error = true;
if (!scm_ensure_workshop_schema($db)) {
$message = 'Workshop schema migration failed.';
return false;
}
scm_ensure_phase2_schema($db);
$home_id = (int)$home_info['home_id'];
$user_id = (int)$user_id;
$addon_id = (int)$addon_id;
$server_xml = read_server_config(SERVER_CONFIG_LOCATION . "/" . $home_info['home_cfg_file']);
if ($server_xml === false) {
$message = 'Unable to read server configuration for workshop action.';
return false;
}
if (!scm_workshop_is_supported($server_xml)) {
$message = 'This game XML does not enable Steam Workshop support. Add a valid workshop_support block before installing Workshop items.';
return false;
}
$template = scm_workshop_get_content_template($db, $addon_id);
if ($action === 'install_new') {
$invalid = array();
$item_ids = scm_parse_workshop_ids($raw_ids, $invalid);
if (!empty($invalid)) {
$message = 'Invalid Workshop item entries. Use a numeric Workshop ID or Steam Workshop URL: ' . implode(', ', $invalid);
return false;
}
if (empty($item_ids)) {
$message = 'Enter at least one Steam Workshop ID or Workshop URL.';
return false;
}
$manifest_context = scm_workshop_build_manifest_context($db, $home_info, $server_xml, $item_ids, $template);
$resolved_app_id = isset($manifest_context['workshop_app_id']) ? (string)$manifest_context['workshop_app_id'] : '';
if ($resolved_app_id === '') {
$message = 'Workshop App ID is missing from the game XML workshop_support block.';
return false;
}
// Check whether the content_id column exists (added in db_version 6).
$has_content_id_col = (bool)$db->resultQuery(
"SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = '" . $db->realEscapeSingle(OGP_DB_PREFIX . 'server_content_workshop') . "'
AND COLUMN_NAME = 'content_id'"
);
$next_order_rows = $db->resultQuery(
"SELECT COALESCE(MAX(load_order), 0) AS max_order FROM `".OGP_DB_PREFIX."server_content_workshop`
WHERE home_id=".$home_id
);
$next_order = (is_array($next_order_rows) && isset($next_order_rows[0]['max_order'])) ? (int)$next_order_rows[0]['max_order'] : 0;
foreach ($item_ids as $item_id) {
$item_detail = isset($manifest_context['item_details'][(string)$item_id]) ? $manifest_context['item_details'][(string)$item_id] : array();
$install_path = isset($item_detail['target_path_resolved']) ? (string)$item_detail['target_path_resolved'] : '';
$install_strategy = isset($item_detail['install_strategy']) ? (string)$item_detail['install_strategy'] : (string)$manifest_context['install_strategy'];
$next_order++;
$content_id_col = $has_content_id_col && $addon_id > 0 ? ", content_id" : '';
$content_id_val = $has_content_id_col && $addon_id > 0 ? ", " . $addon_id : '';
$content_id_upd = $has_content_id_col && $addon_id > 0 ? ", content_id=VALUES(content_id)" : '';
$query = "INSERT INTO `".OGP_DB_PREFIX."server_content_workshop`
(home_id, home_cfg_id, remote_server_id, workshop_app_id, workshop_item_id, install_path, install_strategy, enabled, load_order, install_state, created_by, created_at, updated_at" . $content_id_col . ")
VALUES (
".$home_id.",
".(int)$home_info['home_cfg_id'].",
".(int)$home_info['remote_server_id'].",
'".$db->realEscapeSingle($resolved_app_id)."',
'".$db->realEscapeSingle($item_id)."',
'".$db->realEscapeSingle($install_path)."',
'".$db->realEscapeSingle($install_strategy)."',
1,
".$next_order.",
'queued',
".$user_id.",
NOW(),
NOW()
" . $content_id_val . "
)
ON DUPLICATE KEY UPDATE
home_cfg_id=VALUES(home_cfg_id),
remote_server_id=VALUES(remote_server_id),
workshop_app_id=VALUES(workshop_app_id),
install_path=VALUES(install_path),
install_strategy=VALUES(install_strategy),
enabled=1,
install_state='queued',
last_error=NULL,
updated_at=NOW()" . $content_id_upd;
$db->query($query);
}
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'installing', null, false, false);
$error = '';
$details = array();
$ok = scm_workshop_write_manifest_and_run($db, $home_info, $server_xml, 'install', $item_ids, $error, $manifest_context, $details);
if ($ok) {
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'installed', null, true, true);
scm_workshop_record_catalog_items($db, $resolved_app_id, $item_ids, $home_info, isset($manifest_context['item_details']) ? $manifest_context['item_details'] : array(), true);
scm_workshop_log_action($db, $home_id, $user_id, "install_new ids=".implode(',', $item_ids)." addon_id=".$addon_id." status=success");
$is_error = false;
$message = 'Workshop item(s) installed successfully. Manifest: '.scm_h(isset($details['manifest_path']) ? $details['manifest_path'] : '').' Log: '.scm_h(isset($details['log_path']) ? $details['log_path'] : '');
return true;
}
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'failed', $error, false, false);
scm_workshop_log_action($db, $home_id, $user_id, "install_new ids=".implode(',', $item_ids)." addon_id=".$addon_id." status=failed error=".$error);
$message = $error;
return false;
}
if ($action === 'enable_selected' || $action === 'disable_selected' || $action === 'save_update_policy') {
$item_ids = scm_workshop_filter_existing_ids($db, $home_id, scm_parse_selected_workshop_ids($selected_ids));
if (empty($item_ids)) {
$message = 'Select one or more saved Workshop IDs.';
return false;
}
if ($action === 'save_update_policy') {
$policy = isset($options['update_policy']) ? (string)$options['update_policy'] : 'manual';
if (!scm_workshop_set_update_policy($db, $home_id, $item_ids, $policy)) {
$message = 'Failed to save Workshop update policy.';
return false;
}
$is_error = false;
$message = 'Workshop update policy saved for selected item(s).';
return true;
}
$enabled = ($action === 'enable_selected') ? 1 : 0;
if (!scm_workshop_set_enabled($db, $home_id, $item_ids, $enabled)) {
$message = 'Failed to update Workshop enabled state.';
return false;
}
$is_error = false;
$message = $enabled ? 'Selected Workshop item(s) enabled.' : 'Selected Workshop item(s) disabled.';
return true;
}
if ($action === 'update_selected' || $action === 'remove_selected' || $action === 'download_selected') {
$item_ids = scm_workshop_filter_existing_ids($db, $home_id, scm_parse_selected_workshop_ids($selected_ids));
if (empty($item_ids)) {
$message = 'Select one or more saved Workshop IDs.';
return false;
}
$target_action = ($action === 'remove_selected') ? 'remove' : (($action === 'download_selected') ? 'download_only' : 'update');
$manifest_context = scm_workshop_build_manifest_context($db, $home_info, $server_xml, $item_ids, $template);
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'installing', null, false, false);
$error = '';
$details = array();
$ok = scm_workshop_write_manifest_and_run($db, $home_info, $server_xml, $target_action, $item_ids, $error, $manifest_context, $details);
if ($ok) {
if ($target_action === 'remove') {
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'removed', null, false, true);
} elseif ($target_action === 'download_only') {
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'downloaded', null, false, true);
scm_workshop_set_update_policy($db, $home_id, $item_ids, 'install_on_restart');
scm_workshop_record_catalog_items($db, isset($manifest_context['workshop_app_id']) ? (string)$manifest_context['workshop_app_id'] : '', $item_ids, $home_info, isset($manifest_context['item_details']) ? $manifest_context['item_details'] : array(), true);
} else {
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'installed', null, false, true);
scm_workshop_record_catalog_items($db, isset($manifest_context['workshop_app_id']) ? (string)$manifest_context['workshop_app_id'] : '', $item_ids, $home_info, isset($manifest_context['item_details']) ? $manifest_context['item_details'] : array(), true);
}
scm_workshop_log_action($db, $home_id, $user_id, $action." ids=".implode(',', $item_ids)." addon_id=".$addon_id." status=success");
$is_error = false;
if ($target_action === 'remove') {
$message = 'Selected Workshop item(s) removed.';
} elseif ($target_action === 'download_only') {
$message = 'Selected Workshop item(s) downloaded and marked for install on next restart.';
} else {
$message = 'Selected Workshop item(s) updated successfully.';
}
$message .= ' Log: ' . scm_h(isset($details['log_path']) ? $details['log_path'] : '');
return true;
}
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'failed', $error, false, false);
scm_workshop_log_action($db, $home_id, $user_id, $action." ids=".implode(',', $item_ids)." addon_id=".$addon_id." status=failed error=".$error);
$message = $error;
return false;
}
if ($action === 'update_all') {
$rows = $db->resultQuery(
"SELECT workshop_item_id FROM `".OGP_DB_PREFIX."server_content_workshop`
WHERE home_id=".$home_id." AND install_state<>'removed'"
);
$item_ids = array();
if (is_array($rows)) {
foreach ((array)$rows as $row) {
$item_ids[] = (string)$row['workshop_item_id'];
}
}
$item_ids = scm_parse_selected_workshop_ids($item_ids);
if (empty($item_ids)) {
$message = 'No Workshop IDs are currently saved for this server.';
return false;
}
$manifest_context = scm_workshop_build_manifest_context($db, $home_info, $server_xml, $item_ids, $template);
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'installing', null, false, false);
$error = '';
$details = array();
$ok = scm_workshop_write_manifest_and_run($db, $home_info, $server_xml, 'update', $item_ids, $error, $manifest_context, $details);
if ($ok) {
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'installed', null, false, true);
scm_workshop_record_catalog_items($db, isset($manifest_context['workshop_app_id']) ? (string)$manifest_context['workshop_app_id'] : '', $item_ids, $home_info, isset($manifest_context['item_details']) ? $manifest_context['item_details'] : array(), true);
scm_workshop_log_action($db, $home_id, $user_id, "update_all ids=".implode(',', $item_ids)." addon_id=".$addon_id." status=success");
$is_error = false;
$message = 'All saved Workshop item(s) updated successfully. Log: ' . scm_h(isset($details['log_path']) ? $details['log_path'] : '');
return true;
}
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'failed', $error, false, false);
scm_workshop_log_action($db, $home_id, $user_id, "update_all ids=".implode(',', $item_ids)." addon_id=".$addon_id." status=failed error=".$error);
$message = $error;
return false;
}
$message = 'Invalid workshop action.';
return false;
}

View file

@ -0,0 +1,475 @@
<?php
/*
*
* GSP - Workshop Content actions (Phase 1)
*
*/
require_once("includes/lib_remote.php");
require_once("modules/config_games/server_config_parser.php");
require_once(dirname(__FILE__) . '/server_content_helpers.php');
function scm_workshop_log_action($db, $home_id, $user_id, $message)
{
$db->logger("server_content_workshop home_id=".(int)$home_id." user_id=".(int)$user_id." ".$message);
}
function scm_workshop_update_rows_state($db, $home_id, array $item_ids, $state, $error = null, $mark_install = false, $mark_update = false)
{
if (empty($item_ids)) {
return true;
}
$escaped_ids = array();
foreach ($item_ids as $item_id) {
$escaped_ids[] = "'" . $db->realEscapeSingle((string)$item_id) . "'";
}
$set = array(
"install_state='" . $db->realEscapeSingle($state) . "'",
"updated_at=NOW()",
);
if ($mark_install) {
$set[] = "last_installed_at=NOW()";
}
if ($mark_update) {
$set[] = "last_updated_at=NOW()";
}
if ($error === null) {
$set[] = "last_error=NULL";
} else {
$set[] = "last_error='" . $db->realEscapeSingle($error) . "'";
}
$query = "UPDATE `".OGP_DB_PREFIX."server_content_workshop`
SET ".implode(", ", $set)."
WHERE home_id=".(int)$home_id." AND workshop_item_id IN (".implode(",", $escaped_ids).")";
return (bool)$db->query($query);
}
function scm_workshop_filter_existing_ids($db, $home_id, array $item_ids)
{
if (empty($item_ids)) {
return array();
}
$escaped_ids = array();
foreach ($item_ids as $item_id) {
$escaped_ids[] = "'" . $db->realEscapeSingle((string)$item_id) . "'";
}
$rows = $db->resultQuery(
"SELECT workshop_item_id FROM `".OGP_DB_PREFIX."server_content_workshop`
WHERE home_id=".(int)$home_id." AND workshop_item_id IN (".implode(",", $escaped_ids).")"
);
$allowed = array();
if (is_array($rows)) {
foreach ((array)$rows as $row) {
$allowed[(string)$row['workshop_item_id']] = (string)$row['workshop_item_id'];
}
}
return array_values($allowed);
}
function scm_workshop_get_content_template($db, $addon_id)
{
$addon_id = (int)$addon_id;
if ($addon_id <= 0) {
return array();
}
scm_ensure_phase2_schema($db);
$rows = $db->resultQuery(
"SELECT addon_id, name, content_version, description
FROM `" . OGP_DB_PREFIX . "addons`
WHERE addon_id=" . $addon_id . " AND install_method='steam_workshop'
LIMIT 1"
);
return (is_array($rows) && !empty($rows)) ? $rows[0] : array();
}
function scm_workshop_build_manifest_context($db, array $home_info, $server_xml, array $item_ids, array $template = array())
{
$install_strategy = scm_detect_workshop_install_strategy($home_info, $server_xml, $template);
$copy_keys = scm_workshop_should_copy_keys($server_xml, $install_strategy);
$xml_install_path = scm_extract_workshop_install_path($server_xml);
$keys_target_path = scm_workshop_keys_target_path($server_xml, $home_info);
$item_details = array();
$resolved_app_id = '';
$steam_app_id = '';
foreach ($item_ids as $item_id) {
$payload = array(
'workshop_item_id' => (string)$item_id,
'install_strategy' => $install_strategy,
);
$message = '';
$runtime = scm_build_workshop_runtime_context($db, $home_info, $server_xml, $payload, $message);
if ($runtime === false) {
$runtime = array();
}
$item_app_id = isset($runtime['workshop_app_id']) ? (string)$runtime['workshop_app_id'] : '';
if ($resolved_app_id === '' && $item_app_id !== '') {
$resolved_app_id = $item_app_id;
}
if ($steam_app_id === '' && !empty($runtime['steam_app_id'])) {
$steam_app_id = (string)$runtime['steam_app_id'];
}
$item_details[(string)$item_id] = array(
'workshop_item_id' => (string)$item_id,
'title' => '',
'folder_name' => isset($runtime['folder_name']) && $runtime['folder_name'] !== '' ? (string)$runtime['folder_name'] : '@' . $item_id,
'target_path_template' => isset($runtime['target_path_template']) ? (string)$runtime['target_path_template'] : scm_get_default_workshop_target_template($install_strategy),
'target_path_resolved' => isset($runtime['target_path_resolved']) ? (string)$runtime['target_path_resolved'] : '',
'install_strategy' => $install_strategy,
'copy_keys' => $copy_keys ? 1 : 0,
'keys_target_path' => $keys_target_path,
);
}
if ($resolved_app_id === '') {
$resolved_app_id = scm_extract_workshop_app_id($server_xml);
}
if ($steam_app_id === '') {
$steam_app_id = scm_extract_workshop_steam_app_id($server_xml);
}
return array(
'workshop_app_id' => $resolved_app_id,
'steam_app_id' => $steam_app_id,
'server_root' => rtrim((string)$home_info['home_path'], '/'),
'install_strategy' => $install_strategy,
'copy_keys' => $copy_keys ? 1 : 0,
'target_path_template' => $xml_install_path !== '' ? $xml_install_path : scm_get_default_workshop_target_template($install_strategy),
'keys_target_path' => $keys_target_path,
'post_install_script' => scm_workshop_post_install_action($server_xml),
'launch_param_additions' => isset($server_xml->workshop_support->startup_param_format) ? trim((string)$server_xml->workshop_support->startup_param_format) : '',
'content_template_id' => isset($template['addon_id']) ? (int)$template['addon_id'] : 0,
'content_template_name' => isset($template['name']) ? (string)$template['name'] : '',
'item_details' => $item_details,
);
}
function scm_workshop_write_manifest_and_run($db, array $home_info, $server_xml, $action, array $item_ids, &$error = '', array $extra_manifest = array(), &$result_details = array())
{
$error = '';
$result_details = array();
if (empty($item_ids)) {
$error = 'No Workshop IDs were selected for this action.';
return false;
}
$manifest_path = scm_get_workshop_manifest_path($home_info);
if ($manifest_path === false) {
$error = 'Manifest path validation failed for this server home.';
return false;
}
$home_path = rtrim(clean_path((string)$home_info['home_path']), '/');
if (!scm_path_is_under_home($home_path, $manifest_path)) {
$error = 'Manifest path is outside of the server home.';
return false;
}
$manifest_dir = dirname($manifest_path);
$manifest = array(
'manifest_version' => 1,
'action' => (string)$action,
'home_id' => (int)$home_info['home_id'],
'home_cfg_id' => (int)$home_info['home_cfg_id'],
'game_path' => $home_path,
'server_path' => $home_path,
'workshop_app_id' => (!empty($extra_manifest['workshop_app_id']) ? (string)$extra_manifest['workshop_app_id'] : scm_extract_workshop_app_id($server_xml)),
'steam_app_id' => !empty($extra_manifest['steam_app_id']) ? (string)$extra_manifest['steam_app_id'] : '',
'items' => array_values($item_ids),
'item_details' => !empty($extra_manifest['item_details']) && is_array($extra_manifest['item_details']) ? $extra_manifest['item_details'] : array(),
'install_strategy' => !empty($extra_manifest['install_strategy']) ? (string)$extra_manifest['install_strategy'] : '',
'target_path' => !empty($extra_manifest['target_path_template']) ? (string)$extra_manifest['target_path_template'] : scm_get_default_workshop_target_template(!empty($extra_manifest['install_strategy']) ? (string)$extra_manifest['install_strategy'] : ''),
'generated_at' => date('Y-m-d H:i:s'),
);
if (!empty($extra_manifest)) {
$manifest['extra'] = $extra_manifest;
}
$manifest_json = json_encode($manifest);
if ($manifest_json === false) {
$error = 'Failed to encode workshop manifest JSON.';
return false;
}
$remote = new OGPRemoteLibrary(
$home_info['agent_ip'],
$home_info['agent_port'],
$home_info['encryption_key'],
$home_info['timeout']
);
$remote->exec("mkdir -p " . escapeshellarg($manifest_dir));
if ((int)$remote->remote_writefile($manifest_path, $manifest_json) !== 1) {
$error = 'Failed to write workshop manifest to remote server.';
return false;
}
$script_path = scm_prepare_workshop_script_for_agent($remote, $home_info, $server_xml, $error);
if ($script_path === false) {
return false;
}
$command = "bash " . escapeshellarg($script_path) . " " . escapeshellarg($manifest_path) . " ; echo __GSP_WORKSHOP_EXIT:$?";
$output = $remote->exec($command);
if (!is_string($output) || $output === '') {
$error = 'Workshop script did not return an execution status.';
return false;
}
if (!preg_match('/__GSP_WORKSHOP_EXIT:(\d+)/', $output, $matches)) {
$error = 'Workshop script exit marker not found in output.';
return false;
}
$result_details = array(
'manifest_path' => $manifest_path,
'script_path' => $script_path,
'log_path' => clean_path($manifest_dir . (scm_is_windows_home($home_info) ? '/workshop_install_windows.log' : '/workshop_install.log')),
'output' => trim(preg_replace('/__GSP_WORKSHOP_EXIT:\d+/', '', $output)),
);
$exit_code = (int)$matches[1];
if ($exit_code !== 0) {
$error = 'Workshop script failed (exit '.$exit_code.'): '.trim($output);
return false;
}
return true;
}
function scm_workshop_handle_action($db, array $home_info, $user_id, $action, $raw_ids, array $selected_ids, &$message, &$is_error, $addon_id = 0, array $options = array())
{
$message = '';
$is_error = true;
if (!scm_ensure_workshop_schema($db)) {
$message = 'Workshop schema migration failed.';
return false;
}
scm_ensure_phase2_schema($db);
$home_id = (int)$home_info['home_id'];
$user_id = (int)$user_id;
$addon_id = (int)$addon_id;
$server_xml = read_server_config(SERVER_CONFIG_LOCATION . "/" . $home_info['home_cfg_file']);
if ($server_xml === false) {
$message = 'Unable to read server configuration for workshop action.';
return false;
}
if (!scm_workshop_is_supported($server_xml)) {
$message = 'This game XML does not enable Steam Workshop support. Add a valid workshop_support block before installing Workshop items.';
return false;
}
$template = scm_workshop_get_content_template($db, $addon_id);
if ($action === 'install_new') {
$invalid = array();
$item_ids = scm_parse_workshop_ids($raw_ids, $invalid);
if (!empty($invalid)) {
$message = 'Invalid Workshop item entries. Use a numeric Workshop ID or Steam Workshop URL: ' . implode(', ', $invalid);
return false;
}
if (empty($item_ids)) {
$message = 'Enter at least one Steam Workshop ID or Workshop URL.';
return false;
}
$manifest_context = scm_workshop_build_manifest_context($db, $home_info, $server_xml, $item_ids, $template);
$resolved_app_id = isset($manifest_context['workshop_app_id']) ? (string)$manifest_context['workshop_app_id'] : '';
if ($resolved_app_id === '') {
$message = 'Workshop App ID is missing from the game XML workshop_support block.';
return false;
}
// Check whether the content_id column exists (added in db_version 6).
$has_content_id_col = (bool)$db->resultQuery(
"SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = '" . $db->realEscapeSingle(OGP_DB_PREFIX . 'server_content_workshop') . "'
AND COLUMN_NAME = 'content_id'"
);
$next_order_rows = $db->resultQuery(
"SELECT COALESCE(MAX(load_order), 0) AS max_order FROM `".OGP_DB_PREFIX."server_content_workshop`
WHERE home_id=".$home_id
);
$next_order = (is_array($next_order_rows) && isset($next_order_rows[0]['max_order'])) ? (int)$next_order_rows[0]['max_order'] : 0;
foreach ($item_ids as $item_id) {
$item_detail = isset($manifest_context['item_details'][(string)$item_id]) ? $manifest_context['item_details'][(string)$item_id] : array();
$install_path = isset($item_detail['target_path_resolved']) ? (string)$item_detail['target_path_resolved'] : '';
$install_strategy = isset($item_detail['install_strategy']) ? (string)$item_detail['install_strategy'] : (string)$manifest_context['install_strategy'];
$next_order++;
$content_id_col = $has_content_id_col && $addon_id > 0 ? ", content_id" : '';
$content_id_val = $has_content_id_col && $addon_id > 0 ? ", " . $addon_id : '';
$content_id_upd = $has_content_id_col && $addon_id > 0 ? ", content_id=VALUES(content_id)" : '';
$query = "INSERT INTO `".OGP_DB_PREFIX."server_content_workshop`
(home_id, home_cfg_id, remote_server_id, workshop_app_id, workshop_item_id, install_path, install_strategy, enabled, load_order, install_state, created_by, created_at, updated_at" . $content_id_col . ")
VALUES (
".$home_id.",
".(int)$home_info['home_cfg_id'].",
".(int)$home_info['remote_server_id'].",
'".$db->realEscapeSingle($resolved_app_id)."',
'".$db->realEscapeSingle($item_id)."',
'".$db->realEscapeSingle($install_path)."',
'".$db->realEscapeSingle($install_strategy)."',
1,
".$next_order.",
'queued',
".$user_id.",
NOW(),
NOW()
" . $content_id_val . "
)
ON DUPLICATE KEY UPDATE
home_cfg_id=VALUES(home_cfg_id),
remote_server_id=VALUES(remote_server_id),
workshop_app_id=VALUES(workshop_app_id),
install_path=VALUES(install_path),
install_strategy=VALUES(install_strategy),
enabled=1,
install_state='queued',
last_error=NULL,
updated_at=NOW()" . $content_id_upd;
$db->query($query);
}
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'installing', null, false, false);
$error = '';
$details = array();
$ok = scm_workshop_write_manifest_and_run($db, $home_info, $server_xml, 'install', $item_ids, $error, $manifest_context, $details);
if ($ok) {
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'installed', null, true, true);
scm_workshop_record_catalog_items($db, $resolved_app_id, $item_ids, $home_info, isset($manifest_context['item_details']) ? $manifest_context['item_details'] : array(), true);
scm_workshop_log_action($db, $home_id, $user_id, "install_new ids=".implode(',', $item_ids)." addon_id=".$addon_id." status=success");
$is_error = false;
$message = 'Workshop item(s) installed successfully. Manifest: '.scm_h(isset($details['manifest_path']) ? $details['manifest_path'] : '').' Log: '.scm_h(isset($details['log_path']) ? $details['log_path'] : '');
return true;
}
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'failed', $error, false, false);
scm_workshop_log_action($db, $home_id, $user_id, "install_new ids=".implode(',', $item_ids)." addon_id=".$addon_id." status=failed error=".$error);
$message = $error;
return false;
}
if ($action === 'enable_selected' || $action === 'disable_selected' || $action === 'save_update_policy') {
$item_ids = scm_workshop_filter_existing_ids($db, $home_id, scm_parse_selected_workshop_ids($selected_ids));
if (empty($item_ids)) {
$message = 'Select one or more saved Workshop IDs.';
return false;
}
if ($action === 'save_update_policy') {
$policy = isset($options['update_policy']) ? (string)$options['update_policy'] : 'manual';
if (!scm_workshop_set_update_policy($db, $home_id, $item_ids, $policy)) {
$message = 'Failed to save Workshop update policy.';
return false;
}
$is_error = false;
$message = 'Workshop update policy saved for selected item(s).';
return true;
}
$enabled = ($action === 'enable_selected') ? 1 : 0;
if (!scm_workshop_set_enabled($db, $home_id, $item_ids, $enabled)) {
$message = 'Failed to update Workshop enabled state.';
return false;
}
$is_error = false;
$message = $enabled ? 'Selected Workshop item(s) enabled.' : 'Selected Workshop item(s) disabled.';
return true;
}
if ($action === 'remove_selected') {
$item_ids = scm_workshop_filter_existing_ids($db, $home_id, scm_parse_selected_workshop_ids($selected_ids));
if (empty($item_ids)) {
$message = 'Select one or more saved Workshop IDs to remove.';
return false;
}
$escaped_ids = array();
foreach ($item_ids as $item_id) {
$escaped_ids[] = "'" . $db->realEscapeSingle((string)$item_id) . "'";
}
$db->query(
"DELETE FROM `" . OGP_DB_PREFIX . "server_content_workshop`
WHERE home_id=" . (int)$home_id . "
AND workshop_item_id IN (" . implode(",", $escaped_ids) . ")"
);
scm_workshop_log_action($db, $home_id, $user_id, "remove_selected ids=" . implode(',', $item_ids) . " status=db_removed");
$is_error = false;
$message = 'Selected Workshop item(s) removed from this server list. Installed files, if any, can be cleaned up separately.';
return true;
}
if ($action === 'update_selected' || $action === 'download_selected') {
$item_ids = scm_workshop_filter_existing_ids($db, $home_id, scm_parse_selected_workshop_ids($selected_ids));
if (empty($item_ids)) {
$message = 'Select one or more saved Workshop IDs.';
return false;
}
$target_action = ($action === 'remove_selected') ? 'remove' : (($action === 'download_selected') ? 'download_only' : 'update');
$manifest_context = scm_workshop_build_manifest_context($db, $home_info, $server_xml, $item_ids, $template);
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'installing', null, false, false);
$error = '';
$details = array();
$ok = scm_workshop_write_manifest_and_run($db, $home_info, $server_xml, $target_action, $item_ids, $error, $manifest_context, $details);
if ($ok) {
if ($target_action === 'remove') {
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'removed', null, false, true);
} elseif ($target_action === 'download_only') {
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'downloaded', null, false, true);
scm_workshop_set_update_policy($db, $home_id, $item_ids, 'install_on_restart');
scm_workshop_record_catalog_items($db, isset($manifest_context['workshop_app_id']) ? (string)$manifest_context['workshop_app_id'] : '', $item_ids, $home_info, isset($manifest_context['item_details']) ? $manifest_context['item_details'] : array(), true);
} else {
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'installed', null, false, true);
scm_workshop_record_catalog_items($db, isset($manifest_context['workshop_app_id']) ? (string)$manifest_context['workshop_app_id'] : '', $item_ids, $home_info, isset($manifest_context['item_details']) ? $manifest_context['item_details'] : array(), true);
}
scm_workshop_log_action($db, $home_id, $user_id, $action." ids=".implode(',', $item_ids)." addon_id=".$addon_id." status=success");
$is_error = false;
if ($target_action === 'remove') {
$message = 'Selected Workshop item(s) removed.';
} elseif ($target_action === 'download_only') {
$message = 'Selected Workshop item(s) downloaded and marked for install on next restart.';
} else {
$message = 'Selected Workshop item(s) updated successfully.';
}
$message .= ' Log: ' . scm_h(isset($details['log_path']) ? $details['log_path'] : '');
return true;
}
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'failed', $error, false, false);
scm_workshop_log_action($db, $home_id, $user_id, $action." ids=".implode(',', $item_ids)." addon_id=".$addon_id." status=failed error=".$error);
$message = $error;
return false;
}
if ($action === 'update_all') {
$rows = $db->resultQuery(
"SELECT workshop_item_id FROM `".OGP_DB_PREFIX."server_content_workshop`
WHERE home_id=".$home_id." AND install_state<>'removed'"
);
$item_ids = array();
if (is_array($rows)) {
foreach ((array)$rows as $row) {
$item_ids[] = (string)$row['workshop_item_id'];
}
}
$item_ids = scm_parse_selected_workshop_ids($item_ids);
if (empty($item_ids)) {
$message = 'No Workshop IDs are currently saved for this server.';
return false;
}
$manifest_context = scm_workshop_build_manifest_context($db, $home_info, $server_xml, $item_ids, $template);
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'installing', null, false, false);
$error = '';
$details = array();
$ok = scm_workshop_write_manifest_and_run($db, $home_info, $server_xml, 'update', $item_ids, $error, $manifest_context, $details);
if ($ok) {
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'installed', null, false, true);
scm_workshop_record_catalog_items($db, isset($manifest_context['workshop_app_id']) ? (string)$manifest_context['workshop_app_id'] : '', $item_ids, $home_info, isset($manifest_context['item_details']) ? $manifest_context['item_details'] : array(), true);
scm_workshop_log_action($db, $home_id, $user_id, "update_all ids=".implode(',', $item_ids)." addon_id=".$addon_id." status=success");
$is_error = false;
$message = 'All saved Workshop item(s) updated successfully. Log: ' . scm_h(isset($details['log_path']) ? $details['log_path'] : '');
return true;
}
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'failed', $error, false, false);
scm_workshop_log_action($db, $home_id, $user_id, "update_all ids=".implode(',', $item_ids)." addon_id=".$addon_id." status=failed error=".$error);
$message = $error;
return false;
}
$message = 'Invalid workshop action.';
return false;
}

View file

@ -157,22 +157,11 @@ function exec_ogp_module() {
</form>
<?php if ($catalog_query !== '' || $catalog_tag !== ''): ?>
<h3>Workshop-Enabled Games</h3>
<table class='center'>
<tr><th>Game</th><th>Config</th><th>Workshop App ID</th><th>Strategy</th></tr>
<?php if (empty($game_search_rows)): ?>
<tr><td colspan='4' class='info'>No Workshop-enabled game XML files matched this search.</td></tr>
<?php else: ?>
<?php foreach ((array)$game_search_rows as $game_row): ?>
<tr>
<td><?php echo scm_h($game_row['game_name']); ?></td>
<td><?php echo scm_h($game_row['config_file']); ?></td>
<td><?php echo scm_h($game_row['workshop_app_id']); ?></td>
<td><?php echo scm_h($game_row['install_strategy']); ?></td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</table>
<h3>Workshop Search Results</h3>
<p class='info'>
GSP searched the local catalog for this selected game's Workshop App ID.
If nothing appears below, use the Steam Workshop search link above, then paste the Workshop URL or numeric ID into the install box.
</p>
<?php endif; ?>
<form method='post' action=''>

View file

@ -0,0 +1,319 @@
<?php
/*
*
* GSP - Server Content Workshop page
*
* Users enter Steam Workshop IDs or URLs to install on their server.
* Game-specific Workshop behavior comes from the game XML workshop_support block.
*
*/
require_once(dirname(__FILE__) . '/server_content_helpers.php');
require_once(dirname(__FILE__) . '/workshop_action.php');
function exec_ogp_module() {
global $db;
$user_id = isset($_SESSION['user_id']) ? (int)$_SESSION['user_id'] : 0;
$home_id = isset($_REQUEST['home_id']) ? (int)$_REQUEST['home_id'] : 0;
$mod_id = isset($_REQUEST['mod_id']) ? (int)$_REQUEST['mod_id'] : 0;
$ip = isset($_REQUEST['ip']) ? (string)$_REQUEST['ip'] : '';
$port = isset($_REQUEST['port']) ? (string)$_REQUEST['port'] : '';
$addon_id = isset($_REQUEST['addon_id']) ? (int)$_REQUEST['addon_id'] : 0;
if ($home_id <= 0 || $user_id <= 0) {
print_failure(get_lang('no_rights'));
echo create_back_button("addonsmanager","user_addons");
return;
}
$home_info = scm_get_home_for_user($db, $home_id, $user_id);
if ($home_info === false) {
print_failure(get_lang('no_rights'));
echo create_back_button("addonsmanager","user_addons");
return;
}
if (!scm_ensure_workshop_schema($db)) {
print_failure('Failed to initialize Workshop Content storage.');
return;
}
scm_ensure_phase2_schema($db);
$server_xml = read_server_config(SERVER_CONFIG_LOCATION . "/" . $home_info['home_cfg_file']);
if ($server_xml === false || !scm_workshop_is_supported($server_xml)) {
print_failure('Steam Workshop is not enabled for this game XML. Add a valid workshop_support block before using Workshop management.');
echo create_back_button("addonsmanager","user_addons");
return;
}
// Template records are optional and used only as labels/history anchors.
$addon_template = null;
if ($addon_id > 0) {
$template_rows = $db->resultQuery(
"SELECT addon_id, name, description
FROM `" . OGP_DB_PREFIX . "addons`
WHERE addon_id=" . $addon_id . " AND install_method='steam_workshop'"
);
if (is_array($template_rows) && !empty($template_rows)) {
$addon_template = $template_rows[0];
}
}
$message = '';
$is_error = false;
$entered_ids = '';
$catalog_sort = isset($_REQUEST['catalog_sort']) ? (string)$_REQUEST['catalog_sort'] : 'last_installed';
$catalog_query = isset($_REQUEST['workshop_search']) ? trim((string)$_REQUEST['workshop_search']) : '';
$catalog_tag = isset($_REQUEST['workshop_tag']) ? trim((string)$_REQUEST['workshop_tag']) : '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$posted_home_id = isset($_POST['home_id']) ? (int)$_POST['home_id'] : 0;
$csrf_token = isset($_POST['workshop_csrf']) ? (string)$_POST['workshop_csrf'] : '';
$entered_ids = isset($_POST['workshop_ids']) ? (string)$_POST['workshop_ids'] : '';
$selected_ids = isset($_POST['selected_ids']) ? $_POST['selected_ids'] : array();
$action = isset($_POST['workshop_action']) ? (string)$_POST['workshop_action'] : '';
$posted_addon_id = isset($_POST['addon_id']) ? (int)$_POST['addon_id'] : 0;
$options = array(
'update_policy' => isset($_POST['update_policy']) ? (string)$_POST['update_policy'] : 'manual',
);
if ($posted_home_id !== $home_id) {
$is_error = true;
$message = 'Invalid server context for workshop action.';
}
elseif (!scm_validate_csrf_token($csrf_token)) {
$is_error = true;
$message = 'Invalid CSRF token for workshop action.';
}
else {
scm_workshop_handle_action($db, $home_info, $user_id, $action, $entered_ids, (array)$selected_ids, $message, $is_error, $posted_addon_id > 0 ? $posted_addon_id : $addon_id, $options);
}
}
$catalog_app_id = ($server_xml !== false) ? scm_extract_workshop_app_id($server_xml) : '';
$steam_app_id = ($server_xml !== false) ? scm_extract_workshop_steam_app_id($server_xml) : '';
$install_strategy = ($server_xml !== false) ? scm_detect_workshop_install_strategy($home_info, $server_xml) : '';
$rows = scm_get_workshop_rows($db, $home_id);
$catalog_rows = scm_get_workshop_catalog_rows($db, $catalog_app_id, $catalog_sort, 50, $catalog_query, $catalog_tag);
$game_search_rows = scm_get_workshop_enabled_games($catalog_query, $catalog_tag);
$csrf_token = scm_get_csrf_token();
$base_query = 'm=addonsmanager&p=workshop_content&home_id=' . (int)$home_id .
'&mod_id=' . (int)$mod_id . '&ip=' . urlencode($ip) . '&port=' . urlencode($port) .
'&addon_id=' . (int)$addon_id .
'&workshop_search=' . urlencode($catalog_query) . '&workshop_tag=' . urlencode($catalog_tag);
echo "<h2>Workshop Mods: " . scm_h($home_info['home_name']) . "</h2>";
if ($addon_template !== null) {
echo "<p class='info'>Content template: <strong>" . scm_h($addon_template['name']) . "</strong>";
if (!empty($addon_template['description'])) {
echo " " . scm_h($addon_template['description']);
}
echo "</p>";
}
echo "<p class='info'>Enter a Steam Workshop URL or numeric item ID. GSP stores only the numeric Workshop ID. App IDs, install paths, mod folder strategy, key-copy behavior, and launch parameter format come from this game's XML.</p>";
if ($message !== '') {
if ($is_error) {
print_failure($message);
} else {
print_success($message);
}
}
?>
<table class='center'>
<tr><td align='right'><strong>Server Name:</strong></td><td align='left'><?php echo scm_h($home_info['home_name']); ?></td></tr>
<tr><td align='right'><strong>Game Name:</strong></td><td align='left'><?php echo scm_h($home_info['game_name']); ?></td></tr>
<tr><td align='right'><strong>Workshop App ID:</strong></td><td align='left'><?php echo scm_h($catalog_app_id); ?></td></tr>
<tr><td align='right'><strong>Steam App ID:</strong></td><td align='left'><?php echo scm_h($steam_app_id); ?></td></tr>
<tr><td align='right'><strong>Install Strategy:</strong></td><td align='left'><?php echo scm_h($install_strategy); ?></td></tr>
</table>
<h3>Search Workshop</h3>
<form method='get' action=''>
<input type='hidden' name='m' value='addonsmanager' />
<input type='hidden' name='p' value='workshop_content' />
<input type='hidden' name='home_id' value='<?php echo (int)$home_id; ?>' />
<input type='hidden' name='mod_id' value='<?php echo (int)$mod_id; ?>' />
<input type='hidden' name='ip' value='<?php echo scm_h($ip); ?>' />
<input type='hidden' name='port' value='<?php echo scm_h($port); ?>' />
<input type='hidden' name='addon_id' value='<?php echo (int)$addon_id; ?>' />
<table class='center'>
<tr>
<td align='right'><strong>Keyword / ID / URL</strong></td>
<td align='left'><input type='text' name='workshop_search' size='42' value='<?php echo scm_h($catalog_query); ?>' placeholder='ACE, CBA, Zombies, Maps, or Workshop ID' /></td>
<td align='right'><strong>Tag</strong></td>
<td align='left'><input type='text' name='workshop_tag' size='24' value='<?php echo scm_h($catalog_tag); ?>' placeholder='Weapons, Missions, Maps' /></td>
<td><button type='submit'>Search</button></td>
</tr>
<?php if ($catalog_app_id !== '' && ($catalog_query !== '' || $catalog_tag !== '')): ?>
<tr>
<td></td>
<td colspan='4' class='info'>
<a target='_blank' rel='noopener' href='https://steamcommunity.com/workshop/browse/?appid=<?php echo scm_h($catalog_app_id); ?>&amp;searchtext=<?php echo scm_h(urlencode(trim($catalog_query . ' ' . $catalog_tag))); ?>'>Open matching Steam Workshop search</a>
</td>
</tr>
<?php endif; ?>
</table>
</form>
<?php if ($catalog_query !== '' || $catalog_tag !== ''): ?>
<h3>Workshop-Enabled Games</h3>
<table class='center'>
<tr><th>Game</th><th>Config</th><th>Workshop App ID</th><th>Strategy</th></tr>
<?php if (empty($game_search_rows)): ?>
<tr><td colspan='4' class='info'>No Workshop-enabled game XML files matched this search.</td></tr>
<?php else: ?>
<?php foreach ((array)$game_search_rows as $game_row): ?>
<tr>
<td><?php echo scm_h($game_row['game_name']); ?></td>
<td><?php echo scm_h($game_row['config_file']); ?></td>
<td><?php echo scm_h($game_row['workshop_app_id']); ?></td>
<td><?php echo scm_h($game_row['install_strategy']); ?></td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</table>
<?php endif; ?>
<form method='post' action=''>
<input type='hidden' name='m' value='addonsmanager' />
<input type='hidden' name='p' value='workshop_content' />
<input type='hidden' name='home_id' value='<?php echo (int)$home_id; ?>' />
<input type='hidden' name='mod_id' value='<?php echo (int)$mod_id; ?>' />
<input type='hidden' name='ip' value='<?php echo scm_h($ip); ?>' />
<input type='hidden' name='port' value='<?php echo scm_h($port); ?>' />
<input type='hidden' name='addon_id' value='<?php echo (int)$addon_id; ?>' />
<input type='hidden' name='workshop_csrf' value='<?php echo scm_h($csrf_token); ?>' />
<table class='center'>
<tr>
<td align='right'><strong>Workshop URLs / IDs</strong></td>
<td align='left'>
<textarea name='workshop_ids' rows='4' cols='72' placeholder='https://steamcommunity.com/sharedfiles/filedetails/?id=450814997&#10;463939057'><?php echo scm_h($entered_ids); ?></textarea>
<br><small style="color:#666;">Enter one or more Steam Workshop URLs or numeric IDs, one per line, comma-separated, or space-separated.<br>Example for Arma 3 CBA_A3: <code>https://steamcommunity.com/sharedfiles/filedetails/?id=450814997</code></small>
</td>
<td align='left' style='vertical-align:top;padding-top:4px;'>
<button type='submit' name='workshop_action' value='install_new'>Install / Queue</button>
</td>
</tr>
</table>
<br>
<table class='center'>
<tr>
<th></th>
<th>Workshop ID</th>
<th>Title</th>
<th>Enabled</th>
<th>Order</th>
<th>Update Policy</th>
<th>State</th>
<th>Install Path</th>
<th>Last Installed</th>
<th>Last Updated</th>
<th>Last Error</th>
</tr>
<?php if (empty($rows)): ?>
<tr><td colspan='11' class='info'>No Workshop items saved for this server yet.</td></tr>
<?php else: ?>
<?php foreach ((array)$rows as $row): ?>
<tr>
<td><input type='checkbox' name='selected_ids[]' value='<?php echo scm_h($row['workshop_item_id']); ?>'></td>
<td><?php echo scm_h($row['workshop_item_id']); ?></td>
<td><?php echo scm_h($row['title']); ?></td>
<td><?php echo !empty($row['enabled']) ? 'Yes' : 'No'; ?></td>
<td><?php echo scm_h(isset($row['load_order']) ? $row['load_order'] : ''); ?></td>
<td><?php echo scm_h(isset($row['update_policy']) ? $row['update_policy'] : 'manual'); ?></td>
<td><?php echo scm_h($row['install_state']); ?></td>
<td><small><?php echo scm_h(isset($row['install_path']) ? $row['install_path'] : ''); ?></small></td>
<td><?php echo scm_h($row['last_installed_at']); ?></td>
<td><?php echo scm_h($row['last_updated_at']); ?></td>
<td><?php echo scm_h($row['last_error']); ?></td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</table>
<br>
<table class='center'>
<tr>
<td>
<select name='update_policy'>
<?php foreach (scm_get_workshop_update_policies() as $policy_key => $policy_label): ?>
<option value='<?php echo scm_h($policy_key); ?>'><?php echo scm_h($policy_label); ?></option>
<?php endforeach; ?>
</select>
</td>
<td><button type='submit' name='workshop_action' value='save_update_policy'>Save Policy</button></td>
<td><button type='submit' name='workshop_action' value='update_selected'>Update Selected</button></td>
<td><button type='submit' name='workshop_action' value='download_selected'>Download Selected</button></td>
<td><button type='submit' name='workshop_action' value='remove_selected'>Remove Selected</button></td>
<td><button type='submit' name='workshop_action' value='enable_selected'>Enable Selected</button></td>
<td><button type='submit' name='workshop_action' value='disable_selected'>Disable Selected</button></td>
<td><button type='submit' name='workshop_action' value='update_all'>Update All</button></td>
</tr>
</table>
</form>
<h3>Known Workshop Items</h3>
<p class='info'>These are Workshop items previously installed through Server Content Manager. The catalog grows automatically from real installs. Metadata is optional; direct ID or URL install remains available even when Steam metadata has not been fetched yet.</p>
<table class='center'>
<tr>
<th>Workshop ID</th>
<th><a href='?<?php echo scm_h($base_query); ?>&amp;catalog_sort=name'>Name</a></th>
<th>Author</th>
<th>Thumbnail</th>
<th><a href='?<?php echo scm_h($base_query); ?>&amp;catalog_sort=install_count'>Install Count</a></th>
<th><a href='?<?php echo scm_h($base_query); ?>&amp;catalog_sort=published_date'>Published</a></th>
<th><a href='?<?php echo scm_h($base_query); ?>&amp;catalog_sort=last_updated'>Last Updated</a></th>
<th><a href='?<?php echo scm_h($base_query); ?>&amp;catalog_sort=last_installed'>Last Installed</a></th>
<th><a href='?<?php echo scm_h($base_query); ?>&amp;catalog_sort=workshop_id'>Sort ID</a></th>
<th>Action</th>
</tr>
<?php if (empty($catalog_rows)): ?>
<tr><td colspan='10' class='info'>No known Workshop items have been installed for this app yet.</td></tr>
<?php else: ?>
<?php foreach ((array)$catalog_rows as $catalog_row): ?>
<tr>
<td><?php echo scm_h($catalog_row['workshop_id']); ?></td>
<td><?php echo scm_h($catalog_row['title']); ?></td>
<td><?php echo scm_h(isset($catalog_row['author']) ? $catalog_row['author'] : ''); ?></td>
<td>
<?php if (!empty($catalog_row['thumbnail_url'])): ?>
<img src='<?php echo scm_h($catalog_row['thumbnail_url']); ?>' alt='' style='max-width:72px;max-height:48px;' />
<?php endif; ?>
</td>
<td><?php echo scm_h($catalog_row['install_count']); ?></td>
<td><?php echo scm_h($catalog_row['published_date']); ?></td>
<td><?php echo scm_h($catalog_row['last_updated']); ?></td>
<td><?php echo scm_h($catalog_row['last_installed']); ?></td>
<td><?php echo scm_h($catalog_row['workshop_id']); ?></td>
<td>
<form method='post' action='' style='margin:0;'>
<input type='hidden' name='m' value='addonsmanager' />
<input type='hidden' name='p' value='workshop_content' />
<input type='hidden' name='home_id' value='<?php echo (int)$home_id; ?>' />
<input type='hidden' name='mod_id' value='<?php echo (int)$mod_id; ?>' />
<input type='hidden' name='ip' value='<?php echo scm_h($ip); ?>' />
<input type='hidden' name='port' value='<?php echo scm_h($port); ?>' />
<input type='hidden' name='addon_id' value='<?php echo (int)$addon_id; ?>' />
<input type='hidden' name='workshop_csrf' value='<?php echo scm_h($csrf_token); ?>' />
<input type='hidden' name='workshop_ids' value='<?php echo scm_h($catalog_row['workshop_id']); ?>' />
<button type='submit' name='workshop_action' value='install_new'>Install</button>
</form>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</table>
<form method='get' action=''>
<input type='hidden' name='m' value='addonsmanager' />
<input type='hidden' name='p' value='user_addons' />
<input type='hidden' name='home_id' value='<?php echo (int)$home_id; ?>' />
<input type='hidden' name='mod_id' value='<?php echo (int)$mod_id; ?>' />
<input type='hidden' name='ip' value='<?php echo scm_h($ip); ?>' />
<input type='hidden' name='port' value='<?php echo scm_h($port); ?>' />
<input type='submit' value='Back to Server Content' />
</form>
<?php
}

View file

@ -0,0 +1,319 @@
<?php
/*
*
* GSP - Server Content Workshop page
*
* Users enter Steam Workshop IDs or URLs to install on their server.
* Game-specific Workshop behavior comes from the game XML workshop_support block.
*
*/
require_once(dirname(__FILE__) . '/server_content_helpers.php');
require_once(dirname(__FILE__) . '/workshop_action.php');
function exec_ogp_module() {
global $db;
$user_id = isset($_SESSION['user_id']) ? (int)$_SESSION['user_id'] : 0;
$home_id = isset($_REQUEST['home_id']) ? (int)$_REQUEST['home_id'] : 0;
$mod_id = isset($_REQUEST['mod_id']) ? (int)$_REQUEST['mod_id'] : 0;
$ip = isset($_REQUEST['ip']) ? (string)$_REQUEST['ip'] : '';
$port = isset($_REQUEST['port']) ? (string)$_REQUEST['port'] : '';
$addon_id = isset($_REQUEST['addon_id']) ? (int)$_REQUEST['addon_id'] : 0;
if ($home_id <= 0 || $user_id <= 0) {
print_failure(get_lang('no_rights'));
echo create_back_button("addonsmanager","user_addons");
return;
}
$home_info = scm_get_home_for_user($db, $home_id, $user_id);
if ($home_info === false) {
print_failure(get_lang('no_rights'));
echo create_back_button("addonsmanager","user_addons");
return;
}
if (!scm_ensure_workshop_schema($db)) {
print_failure('Failed to initialize Workshop Content storage.');
return;
}
scm_ensure_phase2_schema($db);
$server_xml = read_server_config(SERVER_CONFIG_LOCATION . "/" . $home_info['home_cfg_file']);
if ($server_xml === false || !scm_workshop_is_supported($server_xml)) {
print_failure('Steam Workshop is not enabled for this game XML. Add a valid workshop_support block before using Workshop management.');
echo create_back_button("addonsmanager","user_addons");
return;
}
// Template records are optional and used only as labels/history anchors.
$addon_template = null;
if ($addon_id > 0) {
$template_rows = $db->resultQuery(
"SELECT addon_id, name, description
FROM `" . OGP_DB_PREFIX . "addons`
WHERE addon_id=" . $addon_id . " AND install_method='steam_workshop'"
);
if (is_array($template_rows) && !empty($template_rows)) {
$addon_template = $template_rows[0];
}
}
$message = '';
$is_error = false;
$entered_ids = '';
$catalog_sort = isset($_REQUEST['catalog_sort']) ? (string)$_REQUEST['catalog_sort'] : 'last_installed';
$catalog_query = isset($_REQUEST['workshop_search']) ? trim((string)$_REQUEST['workshop_search']) : '';
$catalog_tag = isset($_REQUEST['workshop_tag']) ? trim((string)$_REQUEST['workshop_tag']) : '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$posted_home_id = isset($_POST['home_id']) ? (int)$_POST['home_id'] : 0;
$csrf_token = isset($_POST['workshop_csrf']) ? (string)$_POST['workshop_csrf'] : '';
$entered_ids = isset($_POST['workshop_ids']) ? (string)$_POST['workshop_ids'] : '';
$selected_ids = isset($_POST['selected_ids']) ? $_POST['selected_ids'] : array();
$action = isset($_POST['workshop_action']) ? (string)$_POST['workshop_action'] : '';
$posted_addon_id = isset($_POST['addon_id']) ? (int)$_POST['addon_id'] : 0;
$options = array(
'update_policy' => isset($_POST['update_policy']) ? (string)$_POST['update_policy'] : 'manual',
);
if ($posted_home_id !== $home_id) {
$is_error = true;
$message = 'Invalid server context for workshop action.';
}
elseif (!scm_validate_csrf_token($csrf_token)) {
$is_error = true;
$message = 'Invalid CSRF token for workshop action.';
}
else {
scm_workshop_handle_action($db, $home_info, $user_id, $action, $entered_ids, (array)$selected_ids, $message, $is_error, $posted_addon_id > 0 ? $posted_addon_id : $addon_id, $options);
}
}
$catalog_app_id = ($server_xml !== false) ? scm_extract_workshop_app_id($server_xml) : '';
$steam_app_id = ($server_xml !== false) ? scm_extract_workshop_steam_app_id($server_xml) : '';
$install_strategy = ($server_xml !== false) ? scm_detect_workshop_install_strategy($home_info, $server_xml) : '';
$rows = scm_get_workshop_rows($db, $home_id);
$catalog_rows = scm_get_workshop_catalog_rows($db, $catalog_app_id, $catalog_sort, 50, $catalog_query, $catalog_tag);
$game_search_rows = scm_get_workshop_enabled_games($catalog_query, $catalog_tag);
$csrf_token = scm_get_csrf_token();
$base_query = 'm=addonsmanager&p=workshop_content&home_id=' . (int)$home_id .
'&mod_id=' . (int)$mod_id . '&ip=' . urlencode($ip) . '&port=' . urlencode($port) .
'&addon_id=' . (int)$addon_id .
'&workshop_search=' . urlencode($catalog_query) . '&workshop_tag=' . urlencode($catalog_tag);
echo "<h2>Workshop Mods: " . scm_h($home_info['home_name']) . "</h2>";
if ($addon_template !== null) {
echo "<p class='info'>Content template: <strong>" . scm_h($addon_template['name']) . "</strong>";
if (!empty($addon_template['description'])) {
echo " " . scm_h($addon_template['description']);
}
echo "</p>";
}
echo "<p class='info'>Enter a Steam Workshop URL or numeric item ID. GSP stores only the numeric Workshop ID. App IDs, install paths, mod folder strategy, key-copy behavior, and launch parameter format come from this game's XML.</p>";
if ($message !== '') {
if ($is_error) {
print_failure($message);
} else {
print_success($message);
}
}
?>
<table class='center'>
<tr><td align='right'><strong>Server Name:</strong></td><td align='left'><?php echo scm_h($home_info['home_name']); ?></td></tr>
<tr><td align='right'><strong>Game Name:</strong></td><td align='left'><?php echo scm_h($home_info['game_name']); ?></td></tr>
<tr><td align='right'><strong>Workshop App ID:</strong></td><td align='left'><?php echo scm_h($catalog_app_id); ?></td></tr>
<tr><td align='right'><strong>Steam App ID:</strong></td><td align='left'><?php echo scm_h($steam_app_id); ?></td></tr>
<tr><td align='right'><strong>Install Strategy:</strong></td><td align='left'><?php echo scm_h($install_strategy); ?></td></tr>
</table>
<h3>Search Workshop</h3>
<form method='get' action=''>
<input type='hidden' name='m' value='addonsmanager' />
<input type='hidden' name='p' value='workshop_content' />
<input type='hidden' name='home_id' value='<?php echo (int)$home_id; ?>' />
<input type='hidden' name='mod_id' value='<?php echo (int)$mod_id; ?>' />
<input type='hidden' name='ip' value='<?php echo scm_h($ip); ?>' />
<input type='hidden' name='port' value='<?php echo scm_h($port); ?>' />
<input type='hidden' name='addon_id' value='<?php echo (int)$addon_id; ?>' />
<table class='center'>
<tr>
<td align='right'><strong>Keyword / ID / URL</strong></td>
<td align='left'><input type='text' name='workshop_search' size='42' value='<?php echo scm_h($catalog_query); ?>' placeholder='ACE, CBA, Zombies, Maps, or Workshop ID' /></td>
<td align='right'><strong>Tag</strong></td>
<td align='left'><input type='text' name='workshop_tag' size='24' value='<?php echo scm_h($catalog_tag); ?>' placeholder='Weapons, Missions, Maps' /></td>
<td><button type='submit'>Search</button></td>
</tr>
<?php if ($catalog_app_id !== '' && ($catalog_query !== '' || $catalog_tag !== '')): ?>
<tr>
<td></td>
<td colspan='4' class='info'>
<a target='_blank' rel='noopener' href='https://steamcommunity.com/workshop/browse/?appid=<?php echo scm_h($catalog_app_id); ?>&amp;searchtext=<?php echo scm_h(urlencode(trim($catalog_query . ' ' . $catalog_tag))); ?>'>Open matching Steam Workshop search</a>
</td>
</tr>
<?php endif; ?>
</table>
</form>
<?php if ($catalog_query !== '' || $catalog_tag !== ''): ?>
<h3>Workshop-Enabled Games</h3>
<table class='center'>
<tr><th>Game</th><th>Config</th><th>Workshop App ID</th><th>Strategy</th></tr>
<?php if (empty($game_search_rows)): ?>
<tr><td colspan='4' class='info'>No Workshop-enabled game XML files matched this search.</td></tr>
<?php else: ?>
<?php foreach ((array)$game_search_rows as $game_row): ?>
<tr>
<td><?php echo scm_h($game_row['game_name']); ?></td>
<td><?php echo scm_h($game_row['config_file']); ?></td>
<td><?php echo scm_h($game_row['workshop_app_id']); ?></td>
<td><?php echo scm_h($game_row['install_strategy']); ?></td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</table>
<?php endif; ?>
<form method='post' action=''>
<input type='hidden' name='m' value='addonsmanager' />
<input type='hidden' name='p' value='workshop_content' />
<input type='hidden' name='home_id' value='<?php echo (int)$home_id; ?>' />
<input type='hidden' name='mod_id' value='<?php echo (int)$mod_id; ?>' />
<input type='hidden' name='ip' value='<?php echo scm_h($ip); ?>' />
<input type='hidden' name='port' value='<?php echo scm_h($port); ?>' />
<input type='hidden' name='addon_id' value='<?php echo (int)$addon_id; ?>' />
<input type='hidden' name='workshop_csrf' value='<?php echo scm_h($csrf_token); ?>' />
<table class='center'>
<tr>
<td align='right'><strong>Workshop URLs / IDs</strong></td>
<td align='left'>
<textarea name='workshop_ids' rows='4' cols='72' placeholder='https://steamcommunity.com/sharedfiles/filedetails/?id=450814997&#10;463939057'><?php echo scm_h($entered_ids); ?></textarea>
<br><small style="color:#666;">Enter one or more Steam Workshop URLs or numeric IDs, one per line, comma-separated, or space-separated.<br>Example for Arma 3 CBA_A3: <code>https://steamcommunity.com/sharedfiles/filedetails/?id=450814997</code></small>
</td>
<td align='left' style='vertical-align:top;padding-top:4px;'>
<button type='submit' name='workshop_action' value='install_new'>Install / Queue</button>
</td>
</tr>
</table>
<br>
<table class='center'>
<tr>
<th></th>
<th>Workshop ID</th>
<th>Title</th>
<th>Enabled</th>
<th>Order</th>
<th>Update Policy</th>
<th>State</th>
<th>Install Path</th>
<th>Last Installed</th>
<th>Last Updated</th>
<th>Last Error</th>
</tr>
<?php if (empty($rows)): ?>
<tr><td colspan='11' class='info'>No Workshop items saved for this server yet.</td></tr>
<?php else: ?>
<?php foreach ((array)$rows as $row): ?>
<tr>
<td><input type='checkbox' name='selected_ids[]' value='<?php echo scm_h($row['workshop_item_id']); ?>'></td>
<td><?php echo scm_h($row['workshop_item_id']); ?></td>
<td><?php echo scm_h($row['title']); ?></td>
<td><?php echo !empty($row['enabled']) ? 'Yes' : 'No'; ?></td>
<td><?php echo scm_h(isset($row['load_order']) ? $row['load_order'] : ''); ?></td>
<td><?php echo scm_h(isset($row['update_policy']) ? $row['update_policy'] : 'manual'); ?></td>
<td><?php echo scm_h($row['install_state']); ?></td>
<td><small><?php echo scm_h(isset($row['install_path']) ? $row['install_path'] : ''); ?></small></td>
<td><?php echo scm_h($row['last_installed_at']); ?></td>
<td><?php echo scm_h($row['last_updated_at']); ?></td>
<td><?php echo scm_h($row['last_error']); ?></td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</table>
<br>
<table class='center'>
<tr>
<td>
<select name='update_policy'>
<?php foreach (scm_get_workshop_update_policies() as $policy_key => $policy_label): ?>
<option value='<?php echo scm_h($policy_key); ?>'><?php echo scm_h($policy_label); ?></option>
<?php endforeach; ?>
</select>
</td>
<td><button type='submit' name='workshop_action' value='save_update_policy'>Save Policy</button></td>
<td><button type='submit' name='workshop_action' value='update_selected'>Update Selected</button></td>
<td><button type='submit' name='workshop_action' value='download_selected'>Download Selected</button></td>
<td><button type='submit' name='workshop_action' value='remove_selected'>Remove Selected</button></td>
<td><button type='submit' name='workshop_action' value='enable_selected'>Enable Selected</button></td>
<td><button type='submit' name='workshop_action' value='disable_selected'>Disable Selected</button></td>
<td><button type='submit' name='workshop_action' value='update_all'>Update All</button></td>
</tr>
</table>
</form>
<h3>Known Workshop Items</h3>
<p class='info'>These are Workshop items previously installed through Server Content Manager. The catalog grows automatically from real installs. Metadata is optional; direct ID or URL install remains available even when Steam metadata has not been fetched yet.</p>
<table class='center'>
<tr>
<th>Workshop ID</th>
<th><a href='?<?php echo scm_h($base_query); ?>&amp;catalog_sort=name'>Name</a></th>
<th>Author</th>
<th>Thumbnail</th>
<th><a href='?<?php echo scm_h($base_query); ?>&amp;catalog_sort=install_count'>Install Count</a></th>
<th><a href='?<?php echo scm_h($base_query); ?>&amp;catalog_sort=published_date'>Published</a></th>
<th><a href='?<?php echo scm_h($base_query); ?>&amp;catalog_sort=last_updated'>Last Updated</a></th>
<th><a href='?<?php echo scm_h($base_query); ?>&amp;catalog_sort=last_installed'>Last Installed</a></th>
<th><a href='?<?php echo scm_h($base_query); ?>&amp;catalog_sort=workshop_id'>Sort ID</a></th>
<th>Action</th>
</tr>
<?php if (empty($catalog_rows)): ?>
<tr><td colspan='10' class='info'>No known Workshop items have been installed for this app yet.</td></tr>
<?php else: ?>
<?php foreach ((array)$catalog_rows as $catalog_row): ?>
<tr>
<td><?php echo scm_h($catalog_row['workshop_id']); ?></td>
<td><?php echo scm_h($catalog_row['title']); ?></td>
<td><?php echo scm_h(isset($catalog_row['author']) ? $catalog_row['author'] : ''); ?></td>
<td>
<?php if (!empty($catalog_row['thumbnail_url'])): ?>
<img src='<?php echo scm_h($catalog_row['thumbnail_url']); ?>' alt='' style='max-width:72px;max-height:48px;' />
<?php endif; ?>
</td>
<td><?php echo scm_h($catalog_row['install_count']); ?></td>
<td><?php echo scm_h($catalog_row['published_date']); ?></td>
<td><?php echo scm_h($catalog_row['last_updated']); ?></td>
<td><?php echo scm_h($catalog_row['last_installed']); ?></td>
<td><?php echo scm_h($catalog_row['workshop_id']); ?></td>
<td>
<form method='post' action='' style='margin:0;'>
<input type='hidden' name='m' value='addonsmanager' />
<input type='hidden' name='p' value='workshop_content' />
<input type='hidden' name='home_id' value='<?php echo (int)$home_id; ?>' />
<input type='hidden' name='mod_id' value='<?php echo (int)$mod_id; ?>' />
<input type='hidden' name='ip' value='<?php echo scm_h($ip); ?>' />
<input type='hidden' name='port' value='<?php echo scm_h($port); ?>' />
<input type='hidden' name='addon_id' value='<?php echo (int)$addon_id; ?>' />
<input type='hidden' name='workshop_csrf' value='<?php echo scm_h($csrf_token); ?>' />
<input type='hidden' name='workshop_ids' value='<?php echo scm_h($catalog_row['workshop_id']); ?>' />
<button type='submit' name='workshop_action' value='install_new'>Install</button>
</form>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</table>
<form method='get' action=''>
<input type='hidden' name='m' value='addonsmanager' />
<input type='hidden' name='p' value='user_addons' />
<input type='hidden' name='home_id' value='<?php echo (int)$home_id; ?>' />
<input type='hidden' name='mod_id' value='<?php echo (int)$mod_id; ?>' />
<input type='hidden' name='ip' value='<?php echo scm_h($ip); ?>' />
<input type='hidden' name='port' value='<?php echo scm_h($port); ?>' />
<input type='submit' value='Back to Server Content' />
</form>
<?php
}

View file

@ -0,0 +1,319 @@
<?php
/*
*
* GSP - Server Content Workshop page
*
* Users enter Steam Workshop IDs or URLs to install on their server.
* Game-specific Workshop behavior comes from the game XML workshop_support block.
*
*/
require_once(dirname(__FILE__) . '/server_content_helpers.php');
require_once(dirname(__FILE__) . '/workshop_action.php');
function exec_ogp_module() {
global $db;
$user_id = isset($_SESSION['user_id']) ? (int)$_SESSION['user_id'] : 0;
$home_id = isset($_REQUEST['home_id']) ? (int)$_REQUEST['home_id'] : 0;
$mod_id = isset($_REQUEST['mod_id']) ? (int)$_REQUEST['mod_id'] : 0;
$ip = isset($_REQUEST['ip']) ? (string)$_REQUEST['ip'] : '';
$port = isset($_REQUEST['port']) ? (string)$_REQUEST['port'] : '';
$addon_id = isset($_REQUEST['addon_id']) ? (int)$_REQUEST['addon_id'] : 0;
if ($home_id <= 0 || $user_id <= 0) {
print_failure(get_lang('no_rights'));
echo create_back_button("addonsmanager","user_addons");
return;
}
$home_info = scm_get_home_for_user($db, $home_id, $user_id);
if ($home_info === false) {
print_failure(get_lang('no_rights'));
echo create_back_button("addonsmanager","user_addons");
return;
}
if (!scm_ensure_workshop_schema($db)) {
print_failure('Failed to initialize Workshop Content storage.');
return;
}
scm_ensure_phase2_schema($db);
$server_xml = read_server_config(SERVER_CONFIG_LOCATION . "/" . $home_info['home_cfg_file']);
if ($server_xml === false || !scm_workshop_is_supported($server_xml)) {
print_failure('Steam Workshop is not enabled for this game XML. Add a valid workshop_support block before using Workshop management.');
echo create_back_button("addonsmanager","user_addons");
return;
}
// Template records are optional and used only as labels/history anchors.
$addon_template = null;
if ($addon_id > 0) {
$template_rows = $db->resultQuery(
"SELECT addon_id, name, description
FROM `" . OGP_DB_PREFIX . "addons`
WHERE addon_id=" . $addon_id . " AND install_method='steam_workshop'"
);
if (is_array($template_rows) && !empty($template_rows)) {
$addon_template = $template_rows[0];
}
}
$message = '';
$is_error = false;
$entered_ids = '';
$catalog_sort = isset($_REQUEST['catalog_sort']) ? (string)$_REQUEST['catalog_sort'] : 'last_installed';
$catalog_query = isset($_REQUEST['workshop_search']) ? trim((string)$_REQUEST['workshop_search']) : '';
$catalog_tag = isset($_REQUEST['workshop_tag']) ? trim((string)$_REQUEST['workshop_tag']) : '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$posted_home_id = isset($_POST['home_id']) ? (int)$_POST['home_id'] : 0;
$csrf_token = isset($_POST['workshop_csrf']) ? (string)$_POST['workshop_csrf'] : '';
$entered_ids = isset($_POST['workshop_ids']) ? (string)$_POST['workshop_ids'] : '';
$selected_ids = isset($_POST['selected_ids']) ? $_POST['selected_ids'] : array();
$action = isset($_POST['workshop_action']) ? (string)$_POST['workshop_action'] : '';
$posted_addon_id = isset($_POST['addon_id']) ? (int)$_POST['addon_id'] : 0;
$options = array(
'update_policy' => isset($_POST['update_policy']) ? (string)$_POST['update_policy'] : 'manual',
);
if ($posted_home_id !== $home_id) {
$is_error = true;
$message = 'Invalid server context for workshop action.';
}
elseif (!scm_validate_csrf_token($csrf_token)) {
$is_error = true;
$message = 'Invalid CSRF token for workshop action.';
}
else {
scm_workshop_handle_action($db, $home_info, $user_id, $action, $entered_ids, (array)$selected_ids, $message, $is_error, $posted_addon_id > 0 ? $posted_addon_id : $addon_id, $options);
}
}
$catalog_app_id = ($server_xml !== false) ? scm_extract_workshop_app_id($server_xml) : '';
$steam_app_id = ($server_xml !== false) ? scm_extract_workshop_steam_app_id($server_xml) : '';
$install_strategy = ($server_xml !== false) ? scm_detect_workshop_install_strategy($home_info, $server_xml) : '';
$rows = scm_get_workshop_rows($db, $home_id);
$catalog_rows = scm_get_workshop_catalog_rows($db, $catalog_app_id, $catalog_sort, 50, $catalog_query, $catalog_tag);
$game_search_rows = scm_get_workshop_enabled_games($catalog_query, $catalog_tag);
$csrf_token = scm_get_csrf_token();
$base_query = 'm=addonsmanager&p=workshop_content&home_id=' . (int)$home_id .
'&mod_id=' . (int)$mod_id . '&ip=' . urlencode($ip) . '&port=' . urlencode($port) .
'&addon_id=' . (int)$addon_id .
'&workshop_search=' . urlencode($catalog_query) . '&workshop_tag=' . urlencode($catalog_tag);
echo "<h2>Workshop Mods: " . scm_h($home_info['home_name']) . "</h2>";
if ($addon_template !== null) {
echo "<p class='info'>Content template: <strong>" . scm_h($addon_template['name']) . "</strong>";
if (!empty($addon_template['description'])) {
echo " " . scm_h($addon_template['description']);
}
echo "</p>";
}
echo "<p class='info'>Enter a Steam Workshop URL or numeric item ID. GSP stores only the numeric Workshop ID. App IDs, install paths, mod folder strategy, key-copy behavior, and launch parameter format come from this game's XML.</p>";
if ($message !== '') {
if ($is_error) {
print_failure($message);
} else {
print_success($message);
}
}
?>
<table class='center'>
<tr><td align='right'><strong>Server Name:</strong></td><td align='left'><?php echo scm_h($home_info['home_name']); ?></td></tr>
<tr><td align='right'><strong>Game Name:</strong></td><td align='left'><?php echo scm_h($home_info['game_name']); ?></td></tr>
<tr><td align='right'><strong>Workshop App ID:</strong></td><td align='left'><?php echo scm_h($catalog_app_id); ?></td></tr>
<tr><td align='right'><strong>Steam App ID:</strong></td><td align='left'><?php echo scm_h($steam_app_id); ?></td></tr>
<tr><td align='right'><strong>Install Strategy:</strong></td><td align='left'><?php echo scm_h($install_strategy); ?></td></tr>
</table>
<h3>Search Workshop</h3>
<form method='get' action=''>
<input type='hidden' name='m' value='addonsmanager' />
<input type='hidden' name='p' value='workshop_content' />
<input type='hidden' name='home_id' value='<?php echo (int)$home_id; ?>' />
<input type='hidden' name='mod_id' value='<?php echo (int)$mod_id; ?>' />
<input type='hidden' name='ip' value='<?php echo scm_h($ip); ?>' />
<input type='hidden' name='port' value='<?php echo scm_h($port); ?>' />
<input type='hidden' name='addon_id' value='<?php echo (int)$addon_id; ?>' />
<table class='center'>
<tr>
<td align='right'><strong>Keyword / ID / URL</strong></td>
<td align='left'><input type='text' name='workshop_search' size='42' value='<?php echo scm_h($catalog_query); ?>' placeholder='ACE, CBA, Zombies, Maps, or Workshop ID' /></td>
<td align='right'><strong>Tag</strong></td>
<td align='left'><input type='text' name='workshop_tag' size='24' value='<?php echo scm_h($catalog_tag); ?>' placeholder='Weapons, Missions, Maps' /></td>
<td><button type='submit'>Search</button></td>
</tr>
<?php if ($catalog_app_id !== '' && ($catalog_query !== '' || $catalog_tag !== '')): ?>
<tr>
<td></td>
<td colspan='4' class='info'>
<a target='_blank' rel='noopener' href='https://steamcommunity.com/workshop/browse/?appid=<?php echo scm_h($catalog_app_id); ?>&amp;searchtext=<?php echo scm_h(urlencode(trim($catalog_query . ' ' . $catalog_tag))); ?>'>Open matching Steam Workshop search</a>
</td>
</tr>
<?php endif; ?>
</table>
</form>
<?php if ($catalog_query !== '' || $catalog_tag !== ''): ?>
<h3>Workshop-Enabled Games</h3>
<table class='center'>
<tr><th>Game</th><th>Config</th><th>Workshop App ID</th><th>Strategy</th></tr>
<?php if (empty($game_search_rows)): ?>
<tr><td colspan='4' class='info'>No Workshop-enabled game XML files matched this search.</td></tr>
<?php else: ?>
<?php foreach ((array)$game_search_rows as $game_row): ?>
<tr>
<td><?php echo scm_h($game_row['game_name']); ?></td>
<td><?php echo scm_h($game_row['config_file']); ?></td>
<td><?php echo scm_h($game_row['workshop_app_id']); ?></td>
<td><?php echo scm_h($game_row['install_strategy']); ?></td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</table>
<?php endif; ?>
<form method='post' action=''>
<input type='hidden' name='m' value='addonsmanager' />
<input type='hidden' name='p' value='workshop_content' />
<input type='hidden' name='home_id' value='<?php echo (int)$home_id; ?>' />
<input type='hidden' name='mod_id' value='<?php echo (int)$mod_id; ?>' />
<input type='hidden' name='ip' value='<?php echo scm_h($ip); ?>' />
<input type='hidden' name='port' value='<?php echo scm_h($port); ?>' />
<input type='hidden' name='addon_id' value='<?php echo (int)$addon_id; ?>' />
<input type='hidden' name='workshop_csrf' value='<?php echo scm_h($csrf_token); ?>' />
<table class='center'>
<tr>
<td align='right'><strong>Workshop URLs / IDs</strong></td>
<td align='left'>
<textarea name='workshop_ids' rows='4' cols='72' placeholder='https://steamcommunity.com/sharedfiles/filedetails/?id=450814997&#10;463939057'><?php echo scm_h($entered_ids); ?></textarea>
<br><small style="color:#666;">Enter one or more Steam Workshop URLs or numeric IDs, one per line, comma-separated, or space-separated.<br>Example for Arma 3 CBA_A3: <code>https://steamcommunity.com/sharedfiles/filedetails/?id=450814997</code></small>
</td>
<td align='left' style='vertical-align:top;padding-top:4px;'>
<button type='submit' name='workshop_action' value='install_new'>Install / Queue</button>
</td>
</tr>
</table>
<br>
<table class='center'>
<tr>
<th></th>
<th>Workshop ID</th>
<th>Title</th>
<th>Enabled</th>
<th>Order</th>
<th>Update Policy</th>
<th>State</th>
<th>Install Path</th>
<th>Last Installed</th>
<th>Last Updated</th>
<th>Last Error</th>
</tr>
<?php if (empty($rows)): ?>
<tr><td colspan='11' class='info'>No Workshop items saved for this server yet.</td></tr>
<?php else: ?>
<?php foreach ((array)$rows as $row): ?>
<tr>
<td><input type='checkbox' name='selected_ids[]' value='<?php echo scm_h($row['workshop_item_id']); ?>'></td>
<td><?php echo scm_h($row['workshop_item_id']); ?></td>
<td><?php echo scm_h($row['title']); ?></td>
<td><?php echo !empty($row['enabled']) ? 'Yes' : 'No'; ?></td>
<td><?php echo scm_h(isset($row['load_order']) ? $row['load_order'] : ''); ?></td>
<td><?php echo scm_h(isset($row['update_policy']) ? $row['update_policy'] : 'manual'); ?></td>
<td><?php echo scm_h($row['install_state']); ?></td>
<td><small><?php echo scm_h(isset($row['install_path']) ? $row['install_path'] : ''); ?></small></td>
<td><?php echo scm_h($row['last_installed_at']); ?></td>
<td><?php echo scm_h($row['last_updated_at']); ?></td>
<td><?php echo scm_h($row['last_error']); ?></td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</table>
<br>
<table class='center'>
<tr>
<td>
<select name='update_policy'>
<?php foreach (scm_get_workshop_update_policies() as $policy_key => $policy_label): ?>
<option value='<?php echo scm_h($policy_key); ?>'><?php echo scm_h($policy_label); ?></option>
<?php endforeach; ?>
</select>
</td>
<td><button type='submit' name='workshop_action' value='save_update_policy'>Save Policy</button></td>
<td><button type='submit' name='workshop_action' value='update_selected'>Update Selected</button></td>
<td><button type='submit' name='workshop_action' value='download_selected'>Download Selected</button></td>
<td><button type='submit' name='workshop_action' value='remove_selected'>Remove Selected</button></td>
<td><button type='submit' name='workshop_action' value='enable_selected'>Enable Selected</button></td>
<td><button type='submit' name='workshop_action' value='disable_selected'>Disable Selected</button></td>
<td><button type='submit' name='workshop_action' value='update_all'>Update All</button></td>
</tr>
</table>
</form>
<h3>Known Workshop Items</h3>
<p class='info'>These are Workshop items previously installed through Server Content Manager. The catalog grows automatically from real installs. Metadata is optional; direct ID or URL install remains available even when Steam metadata has not been fetched yet.</p>
<table class='center'>
<tr>
<th>Workshop ID</th>
<th><a href='?<?php echo scm_h($base_query); ?>&amp;catalog_sort=name'>Name</a></th>
<th>Author</th>
<th>Thumbnail</th>
<th><a href='?<?php echo scm_h($base_query); ?>&amp;catalog_sort=install_count'>Install Count</a></th>
<th><a href='?<?php echo scm_h($base_query); ?>&amp;catalog_sort=published_date'>Published</a></th>
<th><a href='?<?php echo scm_h($base_query); ?>&amp;catalog_sort=last_updated'>Last Updated</a></th>
<th><a href='?<?php echo scm_h($base_query); ?>&amp;catalog_sort=last_installed'>Last Installed</a></th>
<th><a href='?<?php echo scm_h($base_query); ?>&amp;catalog_sort=workshop_id'>Sort ID</a></th>
<th>Action</th>
</tr>
<?php if (empty($catalog_rows)): ?>
<tr><td colspan='10' class='info'>No known Workshop items have been installed for this app yet.</td></tr>
<?php else: ?>
<?php foreach ((array)$catalog_rows as $catalog_row): ?>
<tr>
<td><?php echo scm_h($catalog_row['workshop_id']); ?></td>
<td><?php echo scm_h($catalog_row['title']); ?></td>
<td><?php echo scm_h(isset($catalog_row['author']) ? $catalog_row['author'] : ''); ?></td>
<td>
<?php if (!empty($catalog_row['thumbnail_url'])): ?>
<img src='<?php echo scm_h($catalog_row['thumbnail_url']); ?>' alt='' style='max-width:72px;max-height:48px;' />
<?php endif; ?>
</td>
<td><?php echo scm_h($catalog_row['install_count']); ?></td>
<td><?php echo scm_h($catalog_row['published_date']); ?></td>
<td><?php echo scm_h($catalog_row['last_updated']); ?></td>
<td><?php echo scm_h($catalog_row['last_installed']); ?></td>
<td><?php echo scm_h($catalog_row['workshop_id']); ?></td>
<td>
<form method='post' action='' style='margin:0;'>
<input type='hidden' name='m' value='addonsmanager' />
<input type='hidden' name='p' value='workshop_content' />
<input type='hidden' name='home_id' value='<?php echo (int)$home_id; ?>' />
<input type='hidden' name='mod_id' value='<?php echo (int)$mod_id; ?>' />
<input type='hidden' name='ip' value='<?php echo scm_h($ip); ?>' />
<input type='hidden' name='port' value='<?php echo scm_h($port); ?>' />
<input type='hidden' name='addon_id' value='<?php echo (int)$addon_id; ?>' />
<input type='hidden' name='workshop_csrf' value='<?php echo scm_h($csrf_token); ?>' />
<input type='hidden' name='workshop_ids' value='<?php echo scm_h($catalog_row['workshop_id']); ?>' />
<button type='submit' name='workshop_action' value='install_new'>Install</button>
</form>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</table>
<form method='get' action=''>
<input type='hidden' name='m' value='addonsmanager' />
<input type='hidden' name='p' value='user_addons' />
<input type='hidden' name='home_id' value='<?php echo (int)$home_id; ?>' />
<input type='hidden' name='mod_id' value='<?php echo (int)$mod_id; ?>' />
<input type='hidden' name='ip' value='<?php echo scm_h($ip); ?>' />
<input type='hidden' name='port' value='<?php echo scm_h($port); ?>' />
<input type='submit' value='Back to Server Content' />
</form>
<?php
}

View file

@ -0,0 +1,319 @@
<?php
/*
*
* GSP - Server Content Workshop page
*
* Users enter Steam Workshop IDs or URLs to install on their server.
* Game-specific Workshop behavior comes from the game XML workshop_support block.
*
*/
require_once(dirname(__FILE__) . '/server_content_helpers.php');
require_once(dirname(__FILE__) . '/workshop_action.php');
function exec_ogp_module() {
global $db;
$user_id = isset($_SESSION['user_id']) ? (int)$_SESSION['user_id'] : 0;
$home_id = isset($_REQUEST['home_id']) ? (int)$_REQUEST['home_id'] : 0;
$mod_id = isset($_REQUEST['mod_id']) ? (int)$_REQUEST['mod_id'] : 0;
$ip = isset($_REQUEST['ip']) ? (string)$_REQUEST['ip'] : '';
$port = isset($_REQUEST['port']) ? (string)$_REQUEST['port'] : '';
$addon_id = isset($_REQUEST['addon_id']) ? (int)$_REQUEST['addon_id'] : 0;
if ($home_id <= 0 || $user_id <= 0) {
print_failure(get_lang('no_rights'));
echo create_back_button("addonsmanager","user_addons");
return;
}
$home_info = scm_get_home_for_user($db, $home_id, $user_id);
if ($home_info === false) {
print_failure(get_lang('no_rights'));
echo create_back_button("addonsmanager","user_addons");
return;
}
if (!scm_ensure_workshop_schema($db)) {
print_failure('Failed to initialize Workshop Content storage.');
return;
}
scm_ensure_phase2_schema($db);
$server_xml = read_server_config(SERVER_CONFIG_LOCATION . "/" . $home_info['home_cfg_file']);
if ($server_xml === false || !scm_workshop_is_supported($server_xml)) {
print_failure('Steam Workshop is not enabled for this game XML. Add a valid workshop_support block before using Workshop management.');
echo create_back_button("addonsmanager","user_addons");
return;
}
// Template records are optional and used only as labels/history anchors.
$addon_template = null;
if ($addon_id > 0) {
$template_rows = $db->resultQuery(
"SELECT addon_id, name, description
FROM `" . OGP_DB_PREFIX . "addons`
WHERE addon_id=" . $addon_id . " AND install_method='steam_workshop'"
);
if (is_array($template_rows) && !empty($template_rows)) {
$addon_template = $template_rows[0];
}
}
$message = '';
$is_error = false;
$entered_ids = '';
$catalog_sort = isset($_REQUEST['catalog_sort']) ? (string)$_REQUEST['catalog_sort'] : 'last_installed';
$catalog_query = isset($_REQUEST['workshop_search']) ? trim((string)$_REQUEST['workshop_search']) : '';
$catalog_tag = isset($_REQUEST['workshop_tag']) ? trim((string)$_REQUEST['workshop_tag']) : '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$posted_home_id = isset($_POST['home_id']) ? (int)$_POST['home_id'] : 0;
$csrf_token = isset($_POST['workshop_csrf']) ? (string)$_POST['workshop_csrf'] : '';
$entered_ids = isset($_POST['workshop_ids']) ? (string)$_POST['workshop_ids'] : '';
$selected_ids = isset($_POST['selected_ids']) ? $_POST['selected_ids'] : array();
$action = isset($_POST['workshop_action']) ? (string)$_POST['workshop_action'] : '';
$posted_addon_id = isset($_POST['addon_id']) ? (int)$_POST['addon_id'] : 0;
$options = array(
'update_policy' => isset($_POST['update_policy']) ? (string)$_POST['update_policy'] : 'manual',
);
if ($posted_home_id !== $home_id) {
$is_error = true;
$message = 'Invalid server context for workshop action.';
}
elseif (!scm_validate_csrf_token($csrf_token)) {
$is_error = true;
$message = 'Invalid CSRF token for workshop action.';
}
else {
scm_workshop_handle_action($db, $home_info, $user_id, $action, $entered_ids, (array)$selected_ids, $message, $is_error, $posted_addon_id > 0 ? $posted_addon_id : $addon_id, $options);
}
}
$catalog_app_id = ($server_xml !== false) ? scm_extract_workshop_app_id($server_xml) : '';
$steam_app_id = ($server_xml !== false) ? scm_extract_workshop_steam_app_id($server_xml) : '';
$install_strategy = ($server_xml !== false) ? scm_detect_workshop_install_strategy($home_info, $server_xml) : '';
$rows = scm_get_workshop_rows($db, $home_id);
$catalog_rows = scm_get_workshop_catalog_rows($db, $catalog_app_id, $catalog_sort, 50, $catalog_query, $catalog_tag);
$game_search_rows = scm_get_workshop_enabled_games($catalog_query, $catalog_tag);
$csrf_token = scm_get_csrf_token();
$base_query = 'm=addonsmanager&p=workshop_content&home_id=' . (int)$home_id .
'&mod_id=' . (int)$mod_id . '&ip=' . urlencode($ip) . '&port=' . urlencode($port) .
'&addon_id=' . (int)$addon_id .
'&workshop_search=' . urlencode($catalog_query) . '&workshop_tag=' . urlencode($catalog_tag);
echo "<h2>Workshop Mods: " . scm_h($home_info['home_name']) . "</h2>";
if ($addon_template !== null) {
echo "<p class='info'>Content template: <strong>" . scm_h($addon_template['name']) . "</strong>";
if (!empty($addon_template['description'])) {
echo " " . scm_h($addon_template['description']);
}
echo "</p>";
}
echo "<p class='info'>Enter a Steam Workshop URL or numeric item ID. GSP stores only the numeric Workshop ID. App IDs, install paths, mod folder strategy, key-copy behavior, and launch parameter format come from this game's XML.</p>";
if ($message !== '') {
if ($is_error) {
print_failure($message);
} else {
print_success($message);
}
}
?>
<table class='center'>
<tr><td align='right'><strong>Server Name:</strong></td><td align='left'><?php echo scm_h($home_info['home_name']); ?></td></tr>
<tr><td align='right'><strong>Game Name:</strong></td><td align='left'><?php echo scm_h($home_info['game_name']); ?></td></tr>
<tr><td align='right'><strong>Workshop App ID:</strong></td><td align='left'><?php echo scm_h($catalog_app_id); ?></td></tr>
<tr><td align='right'><strong>Steam App ID:</strong></td><td align='left'><?php echo scm_h($steam_app_id); ?></td></tr>
<tr><td align='right'><strong>Install Strategy:</strong></td><td align='left'><?php echo scm_h($install_strategy); ?></td></tr>
</table>
<h3>Search Workshop</h3>
<form method='get' action=''>
<input type='hidden' name='m' value='addonsmanager' />
<input type='hidden' name='p' value='workshop_content' />
<input type='hidden' name='home_id' value='<?php echo (int)$home_id; ?>' />
<input type='hidden' name='mod_id' value='<?php echo (int)$mod_id; ?>' />
<input type='hidden' name='ip' value='<?php echo scm_h($ip); ?>' />
<input type='hidden' name='port' value='<?php echo scm_h($port); ?>' />
<input type='hidden' name='addon_id' value='<?php echo (int)$addon_id; ?>' />
<table class='center'>
<tr>
<td align='right'><strong>Keyword / ID / URL</strong></td>
<td align='left'><input type='text' name='workshop_search' size='42' value='<?php echo scm_h($catalog_query); ?>' placeholder='ACE, CBA, Zombies, Maps, or Workshop ID' /></td>
<td align='right'><strong>Tag</strong></td>
<td align='left'><input type='text' name='workshop_tag' size='24' value='<?php echo scm_h($catalog_tag); ?>' placeholder='Weapons, Missions, Maps' /></td>
<td><button type='submit'>Search</button></td>
</tr>
<?php if ($catalog_app_id !== '' && ($catalog_query !== '' || $catalog_tag !== '')): ?>
<tr>
<td></td>
<td colspan='4' class='info'>
<a target='_blank' rel='noopener' href='https://steamcommunity.com/workshop/browse/?appid=<?php echo scm_h($catalog_app_id); ?>&amp;searchtext=<?php echo scm_h(urlencode(trim($catalog_query . ' ' . $catalog_tag))); ?>'>Open matching Steam Workshop search</a>
</td>
</tr>
<?php endif; ?>
</table>
</form>
<?php if ($catalog_query !== '' || $catalog_tag !== ''): ?>
<h3>Workshop-Enabled Games</h3>
<table class='center'>
<tr><th>Game</th><th>Config</th><th>Workshop App ID</th><th>Strategy</th></tr>
<?php if (empty($game_search_rows)): ?>
<tr><td colspan='4' class='info'>No Workshop-enabled game XML files matched this search.</td></tr>
<?php else: ?>
<?php foreach ((array)$game_search_rows as $game_row): ?>
<tr>
<td><?php echo scm_h($game_row['game_name']); ?></td>
<td><?php echo scm_h($game_row['config_file']); ?></td>
<td><?php echo scm_h($game_row['workshop_app_id']); ?></td>
<td><?php echo scm_h($game_row['install_strategy']); ?></td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</table>
<?php endif; ?>
<form method='post' action=''>
<input type='hidden' name='m' value='addonsmanager' />
<input type='hidden' name='p' value='workshop_content' />
<input type='hidden' name='home_id' value='<?php echo (int)$home_id; ?>' />
<input type='hidden' name='mod_id' value='<?php echo (int)$mod_id; ?>' />
<input type='hidden' name='ip' value='<?php echo scm_h($ip); ?>' />
<input type='hidden' name='port' value='<?php echo scm_h($port); ?>' />
<input type='hidden' name='addon_id' value='<?php echo (int)$addon_id; ?>' />
<input type='hidden' name='workshop_csrf' value='<?php echo scm_h($csrf_token); ?>' />
<table class='center'>
<tr>
<td align='right'><strong>Workshop URLs / IDs</strong></td>
<td align='left'>
<textarea name='workshop_ids' rows='4' cols='72' placeholder='https://steamcommunity.com/sharedfiles/filedetails/?id=450814997&#10;463939057'><?php echo scm_h($entered_ids); ?></textarea>
<br><small style="color:#666;">Enter one or more Steam Workshop URLs or numeric IDs, one per line, comma-separated, or space-separated.<br>Example for Arma 3 CBA_A3: <code>https://steamcommunity.com/sharedfiles/filedetails/?id=450814997</code></small>
</td>
<td align='left' style='vertical-align:top;padding-top:4px;'>
<button type='submit' name='workshop_action' value='install_new'>Install / Queue</button>
</td>
</tr>
</table>
<br>
<table class='center'>
<tr>
<th></th>
<th>Workshop ID</th>
<th>Title</th>
<th>Enabled</th>
<th>Order</th>
<th>Update Policy</th>
<th>State</th>
<th>Install Path</th>
<th>Last Installed</th>
<th>Last Updated</th>
<th>Last Error</th>
</tr>
<?php if (empty($rows)): ?>
<tr><td colspan='11' class='info'>No Workshop items saved for this server yet.</td></tr>
<?php else: ?>
<?php foreach ((array)$rows as $row): ?>
<tr>
<td><input type='checkbox' name='selected_ids[]' value='<?php echo scm_h($row['workshop_item_id']); ?>'></td>
<td><?php echo scm_h($row['workshop_item_id']); ?></td>
<td><?php echo scm_h($row['title']); ?></td>
<td><?php echo !empty($row['enabled']) ? 'Yes' : 'No'; ?></td>
<td><?php echo scm_h(isset($row['load_order']) ? $row['load_order'] : ''); ?></td>
<td><?php echo scm_h(isset($row['update_policy']) ? $row['update_policy'] : 'manual'); ?></td>
<td><?php echo scm_h($row['install_state']); ?></td>
<td><small><?php echo scm_h(isset($row['install_path']) ? $row['install_path'] : ''); ?></small></td>
<td><?php echo scm_h($row['last_installed_at']); ?></td>
<td><?php echo scm_h($row['last_updated_at']); ?></td>
<td><?php echo scm_h($row['last_error']); ?></td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</table>
<br>
<table class='center'>
<tr>
<td>
<select name='update_policy'>
<?php foreach (scm_get_workshop_update_policies() as $policy_key => $policy_label): ?>
<option value='<?php echo scm_h($policy_key); ?>'><?php echo scm_h($policy_label); ?></option>
<?php endforeach; ?>
</select>
</td>
<td><button type='submit' name='workshop_action' value='save_update_policy'>Save Policy</button></td>
<td><button type='submit' name='workshop_action' value='update_selected'>Update Selected</button></td>
<td><button type='submit' name='workshop_action' value='download_selected'>Download Selected</button></td>
<td><button type='submit' name='workshop_action' value='remove_selected'>Remove Selected</button></td>
<td><button type='submit' name='workshop_action' value='enable_selected'>Enable Selected</button></td>
<td><button type='submit' name='workshop_action' value='disable_selected'>Disable Selected</button></td>
<td><button type='submit' name='workshop_action' value='update_all'>Update All</button></td>
</tr>
</table>
</form>
<h3>Known Workshop Items</h3>
<p class='info'>These are Workshop items previously installed through Server Content Manager. The catalog grows automatically from real installs. Metadata is optional; direct ID or URL install remains available even when Steam metadata has not been fetched yet.</p>
<table class='center'>
<tr>
<th>Workshop ID</th>
<th><a href='?<?php echo scm_h($base_query); ?>&amp;catalog_sort=name'>Name</a></th>
<th>Author</th>
<th>Thumbnail</th>
<th><a href='?<?php echo scm_h($base_query); ?>&amp;catalog_sort=install_count'>Install Count</a></th>
<th><a href='?<?php echo scm_h($base_query); ?>&amp;catalog_sort=published_date'>Published</a></th>
<th><a href='?<?php echo scm_h($base_query); ?>&amp;catalog_sort=last_updated'>Last Updated</a></th>
<th><a href='?<?php echo scm_h($base_query); ?>&amp;catalog_sort=last_installed'>Last Installed</a></th>
<th><a href='?<?php echo scm_h($base_query); ?>&amp;catalog_sort=workshop_id'>Sort ID</a></th>
<th>Action</th>
</tr>
<?php if (empty($catalog_rows)): ?>
<tr><td colspan='10' class='info'>No known Workshop items have been installed for this app yet.</td></tr>
<?php else: ?>
<?php foreach ((array)$catalog_rows as $catalog_row): ?>
<tr>
<td><?php echo scm_h($catalog_row['workshop_id']); ?></td>
<td><?php echo scm_h($catalog_row['title']); ?></td>
<td><?php echo scm_h(isset($catalog_row['author']) ? $catalog_row['author'] : ''); ?></td>
<td>
<?php if (!empty($catalog_row['thumbnail_url'])): ?>
<img src='<?php echo scm_h($catalog_row['thumbnail_url']); ?>' alt='' style='max-width:72px;max-height:48px;' />
<?php endif; ?>
</td>
<td><?php echo scm_h($catalog_row['install_count']); ?></td>
<td><?php echo scm_h($catalog_row['published_date']); ?></td>
<td><?php echo scm_h($catalog_row['last_updated']); ?></td>
<td><?php echo scm_h($catalog_row['last_installed']); ?></td>
<td><?php echo scm_h($catalog_row['workshop_id']); ?></td>
<td>
<form method='post' action='' style='margin:0;'>
<input type='hidden' name='m' value='addonsmanager' />
<input type='hidden' name='p' value='workshop_content' />
<input type='hidden' name='home_id' value='<?php echo (int)$home_id; ?>' />
<input type='hidden' name='mod_id' value='<?php echo (int)$mod_id; ?>' />
<input type='hidden' name='ip' value='<?php echo scm_h($ip); ?>' />
<input type='hidden' name='port' value='<?php echo scm_h($port); ?>' />
<input type='hidden' name='addon_id' value='<?php echo (int)$addon_id; ?>' />
<input type='hidden' name='workshop_csrf' value='<?php echo scm_h($csrf_token); ?>' />
<input type='hidden' name='workshop_ids' value='<?php echo scm_h($catalog_row['workshop_id']); ?>' />
<button type='submit' name='workshop_action' value='install_new'>Install</button>
</form>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</table>
<form method='get' action=''>
<input type='hidden' name='m' value='addonsmanager' />
<input type='hidden' name='p' value='user_addons' />
<input type='hidden' name='home_id' value='<?php echo (int)$home_id; ?>' />
<input type='hidden' name='mod_id' value='<?php echo (int)$mod_id; ?>' />
<input type='hidden' name='ip' value='<?php echo scm_h($ip); ?>' />
<input type='hidden' name='port' value='<?php echo scm_h($port); ?>' />
<input type='submit' value='Back to Server Content' />
</form>
<?php
}

View file

@ -0,0 +1,308 @@
<?php
/*
*
* GSP - Server Content Workshop page
*
* Users enter Steam Workshop IDs or URLs to install on their server.
* Game-specific Workshop behavior comes from the game XML workshop_support block.
*
*/
require_once(dirname(__FILE__) . '/server_content_helpers.php');
require_once(dirname(__FILE__) . '/workshop_action.php');
function exec_ogp_module() {
global $db;
$user_id = isset($_SESSION['user_id']) ? (int)$_SESSION['user_id'] : 0;
$home_id = isset($_REQUEST['home_id']) ? (int)$_REQUEST['home_id'] : 0;
$mod_id = isset($_REQUEST['mod_id']) ? (int)$_REQUEST['mod_id'] : 0;
$ip = isset($_REQUEST['ip']) ? (string)$_REQUEST['ip'] : '';
$port = isset($_REQUEST['port']) ? (string)$_REQUEST['port'] : '';
$addon_id = isset($_REQUEST['addon_id']) ? (int)$_REQUEST['addon_id'] : 0;
if ($home_id <= 0 || $user_id <= 0) {
print_failure(get_lang('no_rights'));
echo create_back_button("addonsmanager","user_addons");
return;
}
$home_info = scm_get_home_for_user($db, $home_id, $user_id);
if ($home_info === false) {
print_failure(get_lang('no_rights'));
echo create_back_button("addonsmanager","user_addons");
return;
}
if (!scm_ensure_workshop_schema($db)) {
print_failure('Failed to initialize Workshop Content storage.');
return;
}
scm_ensure_phase2_schema($db);
$server_xml = read_server_config(SERVER_CONFIG_LOCATION . "/" . $home_info['home_cfg_file']);
if ($server_xml === false || !scm_workshop_is_supported($server_xml)) {
print_failure('Steam Workshop is not enabled for this game XML. Add a valid workshop_support block before using Workshop management.');
echo create_back_button("addonsmanager","user_addons");
return;
}
// Template records are optional and used only as labels/history anchors.
$addon_template = null;
if ($addon_id > 0) {
$template_rows = $db->resultQuery(
"SELECT addon_id, name, description
FROM `" . OGP_DB_PREFIX . "addons`
WHERE addon_id=" . $addon_id . " AND install_method='steam_workshop'"
);
if (is_array($template_rows) && !empty($template_rows)) {
$addon_template = $template_rows[0];
}
}
$message = '';
$is_error = false;
$entered_ids = '';
$catalog_sort = isset($_REQUEST['catalog_sort']) ? (string)$_REQUEST['catalog_sort'] : 'last_installed';
$catalog_query = isset($_REQUEST['workshop_search']) ? trim((string)$_REQUEST['workshop_search']) : '';
$catalog_tag = isset($_REQUEST['workshop_tag']) ? trim((string)$_REQUEST['workshop_tag']) : '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$posted_home_id = isset($_POST['home_id']) ? (int)$_POST['home_id'] : 0;
$csrf_token = isset($_POST['workshop_csrf']) ? (string)$_POST['workshop_csrf'] : '';
$entered_ids = isset($_POST['workshop_ids']) ? (string)$_POST['workshop_ids'] : '';
$selected_ids = isset($_POST['selected_ids']) ? $_POST['selected_ids'] : array();
$action = isset($_POST['workshop_action']) ? (string)$_POST['workshop_action'] : '';
$posted_addon_id = isset($_POST['addon_id']) ? (int)$_POST['addon_id'] : 0;
$options = array(
'update_policy' => isset($_POST['update_policy']) ? (string)$_POST['update_policy'] : 'manual',
);
if ($posted_home_id !== $home_id) {
$is_error = true;
$message = 'Invalid server context for workshop action.';
}
elseif (!scm_validate_csrf_token($csrf_token)) {
$is_error = true;
$message = 'Invalid CSRF token for workshop action.';
}
else {
scm_workshop_handle_action($db, $home_info, $user_id, $action, $entered_ids, (array)$selected_ids, $message, $is_error, $posted_addon_id > 0 ? $posted_addon_id : $addon_id, $options);
}
}
$catalog_app_id = ($server_xml !== false) ? scm_extract_workshop_app_id($server_xml) : '';
$steam_app_id = ($server_xml !== false) ? scm_extract_workshop_steam_app_id($server_xml) : '';
$install_strategy = ($server_xml !== false) ? scm_detect_workshop_install_strategy($home_info, $server_xml) : '';
$rows = scm_get_workshop_rows($db, $home_id);
$catalog_rows = scm_get_workshop_catalog_rows($db, $catalog_app_id, $catalog_sort, 50, $catalog_query, $catalog_tag);
$game_search_rows = scm_get_workshop_enabled_games($catalog_query, $catalog_tag);
$csrf_token = scm_get_csrf_token();
$base_query = 'm=addonsmanager&p=workshop_content&home_id=' . (int)$home_id .
'&mod_id=' . (int)$mod_id . '&ip=' . urlencode($ip) . '&port=' . urlencode($port) .
'&addon_id=' . (int)$addon_id .
'&workshop_search=' . urlencode($catalog_query) . '&workshop_tag=' . urlencode($catalog_tag);
echo "<h2>Workshop Mods: " . scm_h($home_info['home_name']) . "</h2>";
if ($addon_template !== null) {
echo "<p class='info'>Content template: <strong>" . scm_h($addon_template['name']) . "</strong>";
if (!empty($addon_template['description'])) {
echo " " . scm_h($addon_template['description']);
}
echo "</p>";
}
echo "<p class='info'>Enter a Steam Workshop URL or numeric item ID. GSP stores only the numeric Workshop ID. App IDs, install paths, mod folder strategy, key-copy behavior, and launch parameter format come from this game's XML.</p>";
if ($message !== '') {
if ($is_error) {
print_failure($message);
} else {
print_success($message);
}
}
?>
<table class='center'>
<tr><td align='right'><strong>Server Name:</strong></td><td align='left'><?php echo scm_h($home_info['home_name']); ?></td></tr>
<tr><td align='right'><strong>Game Name:</strong></td><td align='left'><?php echo scm_h($home_info['game_name']); ?></td></tr>
<tr><td align='right'><strong>Workshop App ID:</strong></td><td align='left'><?php echo scm_h($catalog_app_id); ?></td></tr>
<tr><td align='right'><strong>Steam App ID:</strong></td><td align='left'><?php echo scm_h($steam_app_id); ?></td></tr>
<tr><td align='right'><strong>Install Strategy:</strong></td><td align='left'><?php echo scm_h($install_strategy); ?></td></tr>
</table>
<h3>Search Workshop</h3>
<form method='get' action=''>
<input type='hidden' name='m' value='addonsmanager' />
<input type='hidden' name='p' value='workshop_content' />
<input type='hidden' name='home_id' value='<?php echo (int)$home_id; ?>' />
<input type='hidden' name='mod_id' value='<?php echo (int)$mod_id; ?>' />
<input type='hidden' name='ip' value='<?php echo scm_h($ip); ?>' />
<input type='hidden' name='port' value='<?php echo scm_h($port); ?>' />
<input type='hidden' name='addon_id' value='<?php echo (int)$addon_id; ?>' />
<table class='center'>
<tr>
<td align='right'><strong>Keyword / ID / URL</strong></td>
<td align='left'><input type='text' name='workshop_search' size='42' value='<?php echo scm_h($catalog_query); ?>' placeholder='ACE, CBA, Zombies, Maps, or Workshop ID' /></td>
<td align='right'><strong>Tag</strong></td>
<td align='left'><input type='text' name='workshop_tag' size='24' value='<?php echo scm_h($catalog_tag); ?>' placeholder='Weapons, Missions, Maps' /></td>
<td><button type='submit'>Search</button></td>
</tr>
<?php if ($catalog_app_id !== '' && ($catalog_query !== '' || $catalog_tag !== '')): ?>
<tr>
<td></td>
<td colspan='4' class='info'>
<a target='_blank' rel='noopener' href='https://steamcommunity.com/workshop/browse/?appid=<?php echo scm_h($catalog_app_id); ?>&amp;searchtext=<?php echo scm_h(urlencode(trim($catalog_query . ' ' . $catalog_tag))); ?>'>Open matching Steam Workshop search</a>
</td>
</tr>
<?php endif; ?>
</table>
</form>
<?php if ($catalog_query !== '' || $catalog_tag !== ''): ?>
<h3>Workshop Search Results</h3>
<p class='info'>
GSP searched the local catalog for this selected game's Workshop App ID.
If nothing appears below, use the Steam Workshop search link above, then paste the Workshop URL or numeric ID into the install box.
</p>
<?php endif; ?>
<form method='post' action=''>
<input type='hidden' name='m' value='addonsmanager' />
<input type='hidden' name='p' value='workshop_content' />
<input type='hidden' name='home_id' value='<?php echo (int)$home_id; ?>' />
<input type='hidden' name='mod_id' value='<?php echo (int)$mod_id; ?>' />
<input type='hidden' name='ip' value='<?php echo scm_h($ip); ?>' />
<input type='hidden' name='port' value='<?php echo scm_h($port); ?>' />
<input type='hidden' name='addon_id' value='<?php echo (int)$addon_id; ?>' />
<input type='hidden' name='workshop_csrf' value='<?php echo scm_h($csrf_token); ?>' />
<table class='center'>
<tr>
<td align='right'><strong>Workshop URLs / IDs</strong></td>
<td align='left'>
<textarea name='workshop_ids' rows='4' cols='72' placeholder='https://steamcommunity.com/sharedfiles/filedetails/?id=450814997&#10;463939057'><?php echo scm_h($entered_ids); ?></textarea>
<br><small style="color:#666;">Enter one or more Steam Workshop URLs or numeric IDs, one per line, comma-separated, or space-separated.<br>Example for Arma 3 CBA_A3: <code>https://steamcommunity.com/sharedfiles/filedetails/?id=450814997</code></small>
</td>
<td align='left' style='vertical-align:top;padding-top:4px;'>
<button type='submit' name='workshop_action' value='install_new'>Install / Queue</button>
</td>
</tr>
</table>
<br>
<table class='center'>
<tr>
<th></th>
<th>Workshop ID</th>
<th>Title</th>
<th>Enabled</th>
<th>Order</th>
<th>Update Policy</th>
<th>State</th>
<th>Install Path</th>
<th>Last Installed</th>
<th>Last Updated</th>
<th>Last Error</th>
</tr>
<?php if (empty($rows)): ?>
<tr><td colspan='11' class='info'>No Workshop items saved for this server yet.</td></tr>
<?php else: ?>
<?php foreach ((array)$rows as $row): ?>
<tr>
<td><input type='checkbox' name='selected_ids[]' value='<?php echo scm_h($row['workshop_item_id']); ?>'></td>
<td><?php echo scm_h($row['workshop_item_id']); ?></td>
<td><?php echo scm_h($row['title']); ?></td>
<td><?php echo !empty($row['enabled']) ? 'Yes' : 'No'; ?></td>
<td><?php echo scm_h(isset($row['load_order']) ? $row['load_order'] : ''); ?></td>
<td><?php echo scm_h(isset($row['update_policy']) ? $row['update_policy'] : 'manual'); ?></td>
<td><?php echo scm_h($row['install_state']); ?></td>
<td><small><?php echo scm_h(isset($row['install_path']) ? $row['install_path'] : ''); ?></small></td>
<td><?php echo scm_h($row['last_installed_at']); ?></td>
<td><?php echo scm_h($row['last_updated_at']); ?></td>
<td><?php echo scm_h($row['last_error']); ?></td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</table>
<br>
<table class='center'>
<tr>
<td>
<select name='update_policy'>
<?php foreach (scm_get_workshop_update_policies() as $policy_key => $policy_label): ?>
<option value='<?php echo scm_h($policy_key); ?>'><?php echo scm_h($policy_label); ?></option>
<?php endforeach; ?>
</select>
</td>
<td><button type='submit' name='workshop_action' value='save_update_policy'>Save Policy</button></td>
<td><button type='submit' name='workshop_action' value='update_selected'>Update Selected</button></td>
<td><button type='submit' name='workshop_action' value='download_selected'>Download Selected</button></td>
<td><button type='submit' name='workshop_action' value='remove_selected'>Remove Selected</button></td>
<td><button type='submit' name='workshop_action' value='enable_selected'>Enable Selected</button></td>
<td><button type='submit' name='workshop_action' value='disable_selected'>Disable Selected</button></td>
<td><button type='submit' name='workshop_action' value='update_all'>Update All</button></td>
</tr>
</table>
</form>
<h3>Known Workshop Items</h3>
<p class='info'>These are Workshop items previously installed through Server Content Manager. The catalog grows automatically from real installs. Metadata is optional; direct ID or URL install remains available even when Steam metadata has not been fetched yet.</p>
<table class='center'>
<tr>
<th>Workshop ID</th>
<th><a href='?<?php echo scm_h($base_query); ?>&amp;catalog_sort=name'>Name</a></th>
<th>Author</th>
<th>Thumbnail</th>
<th><a href='?<?php echo scm_h($base_query); ?>&amp;catalog_sort=install_count'>Install Count</a></th>
<th><a href='?<?php echo scm_h($base_query); ?>&amp;catalog_sort=published_date'>Published</a></th>
<th><a href='?<?php echo scm_h($base_query); ?>&amp;catalog_sort=last_updated'>Last Updated</a></th>
<th><a href='?<?php echo scm_h($base_query); ?>&amp;catalog_sort=last_installed'>Last Installed</a></th>
<th><a href='?<?php echo scm_h($base_query); ?>&amp;catalog_sort=workshop_id'>Sort ID</a></th>
<th>Action</th>
</tr>
<?php if (empty($catalog_rows)): ?>
<tr><td colspan='10' class='info'>No known Workshop items have been installed for this app yet.</td></tr>
<?php else: ?>
<?php foreach ((array)$catalog_rows as $catalog_row): ?>
<tr>
<td><?php echo scm_h($catalog_row['workshop_id']); ?></td>
<td><?php echo scm_h($catalog_row['title']); ?></td>
<td><?php echo scm_h(isset($catalog_row['author']) ? $catalog_row['author'] : ''); ?></td>
<td>
<?php if (!empty($catalog_row['thumbnail_url'])): ?>
<img src='<?php echo scm_h($catalog_row['thumbnail_url']); ?>' alt='' style='max-width:72px;max-height:48px;' />
<?php endif; ?>
</td>
<td><?php echo scm_h($catalog_row['install_count']); ?></td>
<td><?php echo scm_h($catalog_row['published_date']); ?></td>
<td><?php echo scm_h($catalog_row['last_updated']); ?></td>
<td><?php echo scm_h($catalog_row['last_installed']); ?></td>
<td><?php echo scm_h($catalog_row['workshop_id']); ?></td>
<td>
<form method='post' action='' style='margin:0;'>
<input type='hidden' name='m' value='addonsmanager' />
<input type='hidden' name='p' value='workshop_content' />
<input type='hidden' name='home_id' value='<?php echo (int)$home_id; ?>' />
<input type='hidden' name='mod_id' value='<?php echo (int)$mod_id; ?>' />
<input type='hidden' name='ip' value='<?php echo scm_h($ip); ?>' />
<input type='hidden' name='port' value='<?php echo scm_h($port); ?>' />
<input type='hidden' name='addon_id' value='<?php echo (int)$addon_id; ?>' />
<input type='hidden' name='workshop_csrf' value='<?php echo scm_h($csrf_token); ?>' />
<input type='hidden' name='workshop_ids' value='<?php echo scm_h($catalog_row['workshop_id']); ?>' />
<button type='submit' name='workshop_action' value='install_new'>Install</button>
</form>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</table>
<form method='get' action=''>
<input type='hidden' name='m' value='addonsmanager' />
<input type='hidden' name='p' value='user_addons' />
<input type='hidden' name='home_id' value='<?php echo (int)$home_id; ?>' />
<input type='hidden' name='mod_id' value='<?php echo (int)$mod_id; ?>' />
<input type='hidden' name='ip' value='<?php echo scm_h($ip); ?>' />
<input type='hidden' name='port' value='<?php echo scm_h($port); ?>' />
<input type='submit' value='Back to Server Content' />
</form>
<?php
}