Panel/Panel/modules/addonsmanager/workshop_action.php
copilot-swe-agent[bot] 7e183d77a0
feat: add type-aware server content workflows and workshop metadata support
Agent-Logs-Url: https://github.com/GameServerPanel/GSP/sessions/211fb593-b55a-42ad-b657-a3a4ca4764ff

Co-authored-by: iaretechnician <2749183+iaretechnician@users.noreply.github.com>
2026-05-19 19:13:13 +00:00

279 lines
9.6 KiB
PHP

<?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_write_manifest_and_run($db, array $home_info, $server_xml, $action, array $item_ids, &$error = '', array $extra_manifest = array())
{
$error = '';
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;
}
$script_path = scm_get_workshop_script_path($home_info, $server_xml);
$script_path = trim((string)$script_path);
if ($script_path === '' || !preg_match('/^[^\\r\\n\\0]+$/', $script_path)) {
$error = 'Workshop script path is invalid.';
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(
'action' => (string)$action,
'home_id' => (int)$home_info['home_id'],
'home_cfg_id' => (int)$home_info['home_cfg_id'],
'workshop_app_id' => scm_extract_workshop_app_id($server_xml),
'items' => array_values($item_ids),
);
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;
}
if ((int)$remote->rfile_exists($script_path) !== 1) {
$error = 'Configured workshop script not found on agent host: ' . $script_path;
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;
}
$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)
{
$message = '';
$is_error = true;
if (!scm_ensure_workshop_schema($db)) {
$message = 'Workshop schema migration failed.';
return false;
}
$home_id = (int)$home_info['home_id'];
$user_id = (int)$user_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 ($action === 'install_new') {
$invalid = array();
$item_ids = scm_parse_workshop_ids($raw_ids, $invalid);
if (!empty($invalid)) {
$message = 'Invalid Workshop IDs: ' . implode(', ', $invalid);
return false;
}
if (empty($item_ids)) {
$message = 'Enter at least one numeric Workshop ID.';
return false;
}
foreach ($item_ids as $item_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_state, created_by, created_at, updated_at)
VALUES (
".$home_id.",
".(int)$home_info['home_cfg_id'].",
".(int)$home_info['remote_server_id'].",
'".$db->realEscapeSingle(scm_extract_workshop_app_id($server_xml))."',
'".$db->realEscapeSingle($item_id)."',
'selected',
".$user_id.",
NOW(),
NOW()
)
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_state='selected',
last_error=NULL,
updated_at=NOW()";
$db->query($query);
}
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'installing', null, false, false);
$error = '';
$ok = scm_workshop_write_manifest_and_run($db, $home_info, $server_xml, 'install', $item_ids, $error);
if ($ok) {
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'installed', null, true, true);
scm_workshop_log_action($db, $home_id, $user_id, "install_new ids=".implode(',', $item_ids)." status=success");
$is_error = false;
$message = 'Workshop IDs installed successfully.';
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)." status=failed error=".$error);
$message = $error;
return false;
}
if ($action === 'update_selected' || $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.';
return false;
}
$target_action = ($action === 'remove_selected') ? 'remove' : 'update';
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'installing', null, false, false);
$error = '';
$ok = scm_workshop_write_manifest_and_run($db, $home_info, $server_xml, $target_action, $item_ids, $error);
if ($ok) {
if ($target_action === 'remove') {
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'removed', null, false, true);
} else {
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'installed', null, false, true);
}
scm_workshop_log_action($db, $home_id, $user_id, $action." ids=".implode(',', $item_ids)." status=success");
$is_error = false;
$message = ($target_action === 'remove') ? 'Selected Workshop IDs marked removed.' : 'Selected Workshop IDs updated successfully.';
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)." 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;
}
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'installing', null, false, false);
$error = '';
$ok = scm_workshop_write_manifest_and_run($db, $home_info, $server_xml, 'update', $item_ids, $error);
if ($ok) {
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'installed', null, false, true);
scm_workshop_log_action($db, $home_id, $user_id, "update_all ids=".implode(',', $item_ids)." status=success");
$is_error = false;
$message = 'All saved Workshop IDs updated successfully.';
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)." status=failed error=".$error);
$message = $error;
return false;
}
$message = 'Invalid workshop action.';
return false;
}