diff --git a/Panel/modules/addonsmanager/scripts/workshop/panel_generated_steamcmd_job b/Panel/modules/addonsmanager/scripts/workshop/panel_generated_steamcmd_job index daca4928..3a746e08 100755 --- a/Panel/modules/addonsmanager/scripts/workshop/panel_generated_steamcmd_job +++ b/Panel/modules/addonsmanager/scripts/workshop/panel_generated_steamcmd_job @@ -1,133 +1,119 @@ #!/usr/bin/env bash set -u - MANIFEST="${1:-}" - if [ -z "$MANIFEST" ] || [ ! -f "$MANIFEST" ]; then - echo "ERROR: Workshop manifest missing: $MANIFEST" - exit 2 + 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" + 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_value() { + local key="$1" + perl -0777 -ne ' + my $k = shift @ARGV; + if (/"\Q$k\E"\s*:\s*"([^"]*)"/s) { + my $v = $1; + $v =~ s/\\\//\//g; + print $v; + exit; + } + if (/"\Q$k\E"\s*:\s*([0-9]+)/s) { + print $1; + exit; + } + ' "$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 =~ /(?/dev/null 2>&1; then command -v "$c"; return 0; fi - if [ -f "$c" ]; then echo "$c"; return 0; fi - done - return 1 + 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 + 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 + 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" + 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" + 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 + 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 diff --git a/Panel/modules/addonsmanager/scripts/workshop/panel_generated_steamcmd_job.bak.20260609-093249 b/Panel/modules/addonsmanager/scripts/workshop/panel_generated_steamcmd_job.bak.20260609-093249 new file mode 100755 index 00000000..daca4928 --- /dev/null +++ b/Panel/modules/addonsmanager/scripts/workshop/panel_generated_steamcmd_job.bak.20260609-093249 @@ -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 =~ /(?/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 diff --git a/Panel/modules/addonsmanager/workshop_action.php b/Panel/modules/addonsmanager/workshop_action.php index 3c8b3c29..292b9a62 100644 --- a/Panel/modules/addonsmanager/workshop_action.php +++ b/Panel/modules/addonsmanager/workshop_action.php @@ -167,6 +167,18 @@ function scm_workshop_write_manifest_and_run($db, array $home_info, $server_xml, } $manifest_dir = dirname($manifest_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'] : scm_extract_workshop_steam_app_id($server_xml); + if ($workshop_app_id === '' && $steam_app_id !== '') { + $workshop_app_id = $steam_app_id; + } + if ($steam_app_id === '' && $workshop_app_id !== '') { + $steam_app_id = $workshop_app_id; + } + if ($workshop_app_id === '' || !preg_match('/^[0-9]+$/', $workshop_app_id)) { + $error = 'Workshop App ID is missing from the game XML workshop_support block.'; + return false; + } $manifest = array( 'manifest_version' => 1, 'action' => (string)$action, @@ -174,8 +186,8 @@ function scm_workshop_write_manifest_and_run($db, array $home_info, $server_xml, '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'] : '', + 'workshop_app_id' => $workshop_app_id, + 'steam_app_id' => $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'] : '', diff --git a/Panel/modules/addonsmanager/workshop_action.php.bak.20260609-093249 b/Panel/modules/addonsmanager/workshop_action.php.bak.20260609-093249 new file mode 100644 index 00000000..3c8b3c29 --- /dev/null +++ b/Panel/modules/addonsmanager/workshop_action.php.bak.20260609-093249 @@ -0,0 +1,475 @@ +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; +}