gpt woekshop edit
This commit is contained in:
parent
26c92ef6a2
commit
c8287596b5
16 changed files with 6549 additions and 17 deletions
133
Panel/modules/addonsmanager/scripts/workshop/panel_generated_steamcmd_job
Executable file
133
Panel/modules/addonsmanager/scripts/workshop/panel_generated_steamcmd_job
Executable 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
|
||||
|
|
@ -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
|
|
@ -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.';
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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=''>
|
||||
|
|
|
|||
|
|
@ -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); ?>&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 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); ?>&catalog_sort=name'>Name</a></th>
|
||||
<th>Author</th>
|
||||
<th>Thumbnail</th>
|
||||
<th><a href='?<?php echo scm_h($base_query); ?>&catalog_sort=install_count'>Install Count</a></th>
|
||||
<th><a href='?<?php echo scm_h($base_query); ?>&catalog_sort=published_date'>Published</a></th>
|
||||
<th><a href='?<?php echo scm_h($base_query); ?>&catalog_sort=last_updated'>Last Updated</a></th>
|
||||
<th><a href='?<?php echo scm_h($base_query); ?>&catalog_sort=last_installed'>Last Installed</a></th>
|
||||
<th><a href='?<?php echo scm_h($base_query); ?>&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
|
||||
}
|
||||
|
|
@ -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); ?>&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 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); ?>&catalog_sort=name'>Name</a></th>
|
||||
<th>Author</th>
|
||||
<th>Thumbnail</th>
|
||||
<th><a href='?<?php echo scm_h($base_query); ?>&catalog_sort=install_count'>Install Count</a></th>
|
||||
<th><a href='?<?php echo scm_h($base_query); ?>&catalog_sort=published_date'>Published</a></th>
|
||||
<th><a href='?<?php echo scm_h($base_query); ?>&catalog_sort=last_updated'>Last Updated</a></th>
|
||||
<th><a href='?<?php echo scm_h($base_query); ?>&catalog_sort=last_installed'>Last Installed</a></th>
|
||||
<th><a href='?<?php echo scm_h($base_query); ?>&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
|
||||
}
|
||||
|
|
@ -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); ?>&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 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); ?>&catalog_sort=name'>Name</a></th>
|
||||
<th>Author</th>
|
||||
<th>Thumbnail</th>
|
||||
<th><a href='?<?php echo scm_h($base_query); ?>&catalog_sort=install_count'>Install Count</a></th>
|
||||
<th><a href='?<?php echo scm_h($base_query); ?>&catalog_sort=published_date'>Published</a></th>
|
||||
<th><a href='?<?php echo scm_h($base_query); ?>&catalog_sort=last_updated'>Last Updated</a></th>
|
||||
<th><a href='?<?php echo scm_h($base_query); ?>&catalog_sort=last_installed'>Last Installed</a></th>
|
||||
<th><a href='?<?php echo scm_h($base_query); ?>&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
|
||||
}
|
||||
|
|
@ -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); ?>&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 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); ?>&catalog_sort=name'>Name</a></th>
|
||||
<th>Author</th>
|
||||
<th>Thumbnail</th>
|
||||
<th><a href='?<?php echo scm_h($base_query); ?>&catalog_sort=install_count'>Install Count</a></th>
|
||||
<th><a href='?<?php echo scm_h($base_query); ?>&catalog_sort=published_date'>Published</a></th>
|
||||
<th><a href='?<?php echo scm_h($base_query); ?>&catalog_sort=last_updated'>Last Updated</a></th>
|
||||
<th><a href='?<?php echo scm_h($base_query); ?>&catalog_sort=last_installed'>Last Installed</a></th>
|
||||
<th><a href='?<?php echo scm_h($base_query); ?>&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
|
||||
}
|
||||
|
|
@ -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); ?>&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 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); ?>&catalog_sort=name'>Name</a></th>
|
||||
<th>Author</th>
|
||||
<th>Thumbnail</th>
|
||||
<th><a href='?<?php echo scm_h($base_query); ?>&catalog_sort=install_count'>Install Count</a></th>
|
||||
<th><a href='?<?php echo scm_h($base_query); ?>&catalog_sort=published_date'>Published</a></th>
|
||||
<th><a href='?<?php echo scm_h($base_query); ?>&catalog_sort=last_updated'>Last Updated</a></th>
|
||||
<th><a href='?<?php echo scm_h($base_query); ?>&catalog_sort=last_installed'>Last Installed</a></th>
|
||||
<th><a href='?<?php echo scm_h($base_query); ?>&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
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue