From baaa9624549930a7d0082d80860cd1df73af6311 Mon Sep 17 00:00:00 2001 From: Frank Harris Date: Fri, 16 Jan 2026 01:46:33 -0600 Subject: [PATCH] MAJOR updated for steam workshop --- lang/English/modules/steam_workshop.php | 20 + modules/steam_workshop/functions.php | 17 + modules/steam_workshop/lib/Workshop.php | 417 ++++++++++++++++++++ modules/steam_workshop/main.php | 446 ++++++++-------------- modules/steam_workshop/module.php | 2 +- modules/steam_workshop/workshop_admin.php | 316 +++++---------- 6 files changed, 698 insertions(+), 520 deletions(-) create mode 100644 modules/steam_workshop/lib/Workshop.php diff --git a/lang/English/modules/steam_workshop.php b/lang/English/modules/steam_workshop.php index 7ee3ae92..7a60e6e9 100644 --- a/lang/English/modules/steam_workshop.php +++ b/lang/English/modules/steam_workshop.php @@ -71,4 +71,24 @@ define('OGP_LANG_download_method', "Download Method"); define('OGP_LANG_anonymous_login', "Anonymous Login"); define('OGP_LANG_select_at_least_one_mod_or_enter_mod_id', "Select at least one mod or enter a mod ID."); define('OGP_LANG_no_game_servers_assigned', "You don't have any servers assigned to your account."); +define('OGP_LANG_installed_mods', "Workshop items"); +define('OGP_LANG_no_mods_found', "No workshop items configured."); +define('OGP_LANG_name', "Name"); +define('OGP_LANG_status', "Status"); +define('OGP_LANG_remove', "Remove"); +define('OGP_LANG_add_mod', "Add workshop item"); +define('OGP_LANG_add', "Add"); +define('OGP_LANG_collection_id', "Collection ID"); +define('OGP_LANG_actions', "Actions"); +define('OGP_LANG_settings_updated', "Settings updated"); +define('OGP_LANG_invalid_mod_id', "Invalid Workshop ID"); +define('OGP_LANG_pending', "Pending"); +define('OGP_LANG_apply_updates', "Apply updates"); +define('OGP_LANG_deploy_mode', "Deploy mode"); +define('OGP_LANG_deploy_destination', "Deploy destination"); +define('OGP_LANG_staging_path', "Staging path"); +define('OGP_LANG_check_on_start', "Check on start"); +define('OGP_LANG_periodic_check_minutes', "Periodic check (minutes)"); +define('OGP_LANG_steam_username', "Steam username"); +define('OGP_LANG_steam_password', "Steam password"); ?> diff --git a/modules/steam_workshop/functions.php b/modules/steam_workshop/functions.php index 65001899..076e85ed 100644 --- a/modules/steam_workshop/functions.php +++ b/modules/steam_workshop/functions.php @@ -51,6 +51,23 @@ function create_drop_box_from_array_onchange($input_array,$listname,$current_val return $retval; } +function create_drop_box_from_array($input_array,$listname,$current_value = "") +{ + $retval = "\n"; + return $retval; +} + function get_mod_names_list($mods_list, $xml_mods) { $mod_names = ""; diff --git a/modules/steam_workshop/lib/Workshop.php b/modules/steam_workshop/lib/Workshop.php new file mode 100644 index 00000000..fdb08cdd --- /dev/null +++ b/modules/steam_workshop/lib/Workshop.php @@ -0,0 +1,417 @@ +file = $file; + $dir = dirname($file); + if (!is_dir($dir)) { + mkdir($dir, 0775, true); + } + } + + public function all() + { + $data = $this->readFile(); + return is_array($data) ? $data : []; + } + + public function get($homeId) + { + $all = $this->all(); + return isset($all[$homeId]) ? $all[$homeId] : null; + } + + public function put($homeId, array $config) + { + $all = $this->all(); + $all[$homeId] = $config; + $this->writeFile($all); + } + + public function delete($homeId) + { + $all = $this->all(); + if (isset($all[$homeId])) { + unset($all[$homeId]); + $this->writeFile($all); + } + } + + private function readFile() + { + if (!is_file($this->file)) { + return []; + } + $fh = fopen($this->file, 'c+'); + if ($fh === false) { + return []; + } + flock($fh, LOCK_SH); + $raw = stream_get_contents($fh); + flock($fh, LOCK_UN); + fclose($fh); + $data = json_decode($raw, true); + return is_array($data) ? $data : []; + } + + private function writeFile(array $data) + { + $fh = fopen($this->file, 'c+'); + if ($fh === false) { + return; + } + flock($fh, LOCK_EX); + ftruncate($fh, 0); + rewind($fh); + fwrite($fh, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); + fflush($fh); + flock($fh, LOCK_UN); + fclose($fh); + } +} + +class WorkshopStateStore +{ + private $dir; + + public function __construct($dir) + { + $this->dir = rtrim($dir, '/'); + if (!is_dir($this->dir)) { + mkdir($this->dir, 0775, true); + } + } + + public function get($homeId) + { + $path = $this->statePath($homeId); + if (!is_file($path)) { + return ['items' => [], 'last_sync' => null, 'last_status' => null]; + } + $raw = file_get_contents($path); + $data = json_decode($raw, true); + if (!is_array($data)) { + return ['items' => [], 'last_sync' => null, 'last_status' => null]; + } + if (!isset($data['items'])) { + $data['items'] = []; + } + return $data; + } + + public function put($homeId, array $state) + { + $path = $this->statePath($homeId); + $payload = json_encode($state, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); + file_put_contents($path, $payload); + } + + public function updateItem($homeId, $itemId, array $itemState) + { + $state = $this->get($homeId); + $state['items'][$itemId] = $itemState; + $this->put($homeId, $state); + } + + private function statePath($homeId) + { + return $this->dir . '/state_' . $homeId . '.json'; + } +} + +class WorkshopLock +{ + private $fh; + + public function __construct($lockPath) + { + $dir = dirname($lockPath); + if (!is_dir($dir)) { + mkdir($dir, 0775, true); + } + $this->fh = fopen($lockPath, 'c'); + } + + public function acquire() + { + if (!$this->fh) { + return false; + } + return flock($this->fh, LOCK_EX | LOCK_NB); + } + + public function release() + { + if ($this->fh) { + flock($this->fh, LOCK_UN); + } + } +} + +class WorkshopResolver +{ + public function resolveItems(array $config) + { + $items = array_map('trim', isset($config['workshop_item_ids']) ? (array)$config['workshop_item_ids'] : []); + $collections = array_map('trim', isset($config['collection_ids']) ? (array)$config['collection_ids'] : []); + + if (!empty($collections)) { + foreach ($collections as $collectionId) { + $expanded = $this->expandCollection($collectionId); + $items = array_merge($items, $expanded); + } + } + + $items = array_values(array_unique(array_filter($items, 'strlen'))); + return $items; + } + + private function expandCollection($collectionId) + { + $payload = http_build_query([ + 'collectioncount' => 1, + 'publishedfileids[0]' => $collectionId, + ]); + + $context = stream_context_create([ + 'http' => [ + 'method' => 'POST', + 'header' => "Content-Type: application/x-www-form-urlencoded", + 'content' => $payload, + 'timeout' => 10, + ], + ]); + + $json = @file_get_contents('https://api.steampowered.com/ISteamRemoteStorage/GetCollectionDetails/v1/', false, $context); + if ($json === false) { + return []; + } + $data = json_decode($json, true); + if (!isset($data['response']['collectiondetails'][0]['children'])) { + return []; + } + $children = $data['response']['collectiondetails'][0]['children']; + $ids = []; + foreach ($children as $child) { + if (isset($child['publishedfileid'])) { + $ids[] = $child['publishedfileid']; + } + } + return $ids; + } + + public function fetchItemDetails(array $itemIds) + { + if (empty($itemIds)) { + return []; + } + $payload = ['itemcount' => count($itemIds)]; + $index = 0; + foreach ($itemIds as $id) { + $payload['publishedfileids[' . $index . ']'] = $id; + $index++; + } + + $context = stream_context_create([ + 'http' => [ + 'method' => 'POST', + 'header' => "Content-Type: application/x-www-form-urlencoded", + 'content' => http_build_query($payload), + 'timeout' => 10, + ], + ]); + + $json = @file_get_contents('https://api.steampowered.com/ISteamRemoteStorage/GetPublishedFileDetails/v1/', false, $context); + if ($json === false) { + return []; + } + $data = json_decode($json, true); + if (!isset($data['response']['publishedfiledetails'])) { + return []; + } + $details = []; + foreach ($data['response']['publishedfiledetails'] as $item) { + $details[$item['publishedfileid']] = $item; + } + return $details; + } +} + +class WorkshopSyncResult +{ + public $success; + public $message; + public $updatedItems; + public $skippedItems; + + public function __construct($success, $message, array $updatedItems = [], array $skippedItems = []) + { + $this->success = $success; + $this->message = $message; + $this->updatedItems = $updatedItems; + $this->skippedItems = $skippedItems; + } +} + +class WorkshopSyncService +{ + private $remote; + private $homeCfg; + private $configStore; + private $stateStore; + private $resolver; + + public function __construct($remote, array $homeCfg, WorkshopConfigStore $configStore, WorkshopStateStore $stateStore, WorkshopResolver $resolver) + { + $this->remote = $remote; + $this->homeCfg = $homeCfg; + $this->configStore = $configStore; + $this->stateStore = $stateStore; + $this->resolver = $resolver; + } + + public function sync($homeId) + { + $config = $this->configStore->get($homeId); + if (!$config) { + return new WorkshopSyncResult(false, 'No workshop configuration found for this server.'); + } + + $itemIds = $this->resolver->resolveItems($config); + if (empty($itemIds)) { + return new WorkshopSyncResult(true, 'No workshop items configured.', [], []); + } + + $details = $this->resolver->fetchItemDetails($itemIds); + $state = $this->stateStore->get($homeId); + $needsUpdate = []; + $skipped = []; + + foreach ($itemIds as $id) { + $remoteUpdated = isset($details[$id]['time_updated']) ? (int)$details[$id]['time_updated'] : null; + $local = isset($state['items'][$id]) ? $state['items'][$id] : null; + $localSeen = $local && isset($local['last_seen_manifest_id']) ? (int)$local['last_seen_manifest_id'] : null; + if ($remoteUpdated !== null && $localSeen !== null && $remoteUpdated <= $localSeen) { + $skipped[] = $id; + continue; + } + $needsUpdate[] = $id; + } + + if (empty($needsUpdate)) { + $state['last_sync'] = time(); + $state['last_status'] = 'up-to-date'; + $this->stateStore->put($homeId, $state); + return new WorkshopSyncResult(true, 'All workshop items are up-to-date.', [], $skipped); + } + + $modsFullPath = $this->modsPath($config); + $paths = $this->buildPaths($config); + $modNamesList = implode(',', $needsUpdate); + + $result = $this->remote->steam_workshop( + $homeId, + $modsFullPath, + $config['workshop_app_id'], + implode(',', $needsUpdate), + $config['regex'], + (int)$config['mods_backreference_index'], + $config['variable'], + $config['place_after'], + $config['mod_string'], + $config['string_separator'], + $paths['config_file_path'], + $config['post_install'], + $modNamesList, + $config['anonymous_login'] ? '1' : '0', + $config['steam_username'], + $config['steam_password'], + $config['download_method'], + '', + '' + ); + + if ($result !== 1) { + return new WorkshopSyncResult(false, 'Workshop sync failed to start (agent error code ' . $result . ').'); + } + + $now = time(); + foreach ($needsUpdate as $id) { + $state['items'][$id] = [ + 'last_seen_manifest_id' => isset($details[$id]['time_updated']) ? (int)$details[$id]['time_updated'] : $now, + 'last_downloaded_at' => $now, + 'local_path' => $modsFullPath, + 'deploy_path' => $paths['deploy_destination'], + 'last_error' => null, + ]; + } + $state['last_sync'] = $now; + $state['last_status'] = 'started'; + $this->stateStore->put($homeId, $state); + + return new WorkshopSyncResult(true, 'Workshop sync started.', $needsUpdate, $skipped); + } + + private function modsPath(array $config) + { + if (!empty($config['staging_path'])) { + return clean_path($config['staging_path']); + } + return clean_path($this->homeCfg['home_path'] . '/workshop'); + } + + private function buildPaths(array $config) + { + $serverRoot = rtrim($this->homeCfg['home_path'], '/'); + $deployDest = !empty($config['deploy_destination']) ? clean_path($config['deploy_destination']) : ($serverRoot . '/mods'); + $configPath = !empty($config['filepath']) ? clean_path($config['filepath']) : ($serverRoot . '/config.cfg'); + return [ + 'deploy_destination' => $deployDest, + 'config_file_path' => $configPath, + ]; + } +} + +function workshop_build_default_config($homeCfg, $modXml, $settings) +{ + $modsPath = ($modXml && isset($modXml->mods_path)) ? (string)$modXml->mods_path : 'mods'; + $configNode = $modXml && isset($modXml->config) ? $modXml->config : null; + + return [ + 'workshop_app_id' => ($modXml && isset($modXml->workshop_id)) ? (string)$modXml->workshop_id : '', + 'download_method' => ($modXml && isset($modXml->download_method)) ? (string)$modXml->download_method : 'steamcmd', + 'deploy_mode' => 'copy', + 'deploy_destination' => clean_path($homeCfg['home_path'] . '/' . $modsPath), + 'staging_path' => clean_path($homeCfg['home_path'] . '/' . $modsPath), + 'mods_path' => clean_path($homeCfg['home_path'] . '/' . $modsPath), + 'regex' => ($configNode && isset($configNode->regex)) ? (string)$configNode->regex : '', + 'mods_backreference_index' => ($configNode && isset($configNode->mods_backreference_index)) ? (int)$configNode->mods_backreference_index : 1, + 'variable' => ($configNode && isset($configNode->variable)) ? (string)$configNode->variable : '', + 'place_after' => ($configNode && isset($configNode->place_after)) ? (string)$configNode->place_after : '', + 'mod_string' => ($configNode && isset($configNode->mod_string)) ? (string)$configNode->mod_string : '%workshop_mod_id%', + 'string_separator' => ($configNode && isset($configNode->string_separator)) ? (string)$configNode->string_separator : ';', + 'filepath' => ($configNode && isset($configNode->filepath)) ? clean_path($homeCfg['home_path'] . '/' . $configNode->filepath) : '', + 'post_install' => ($modXml && isset($modXml->post_install)) ? (string)$modXml->post_install : '', + 'uninstall' => ($modXml && isset($modXml->uninstall)) ? (string)$modXml->uninstall : '', + 'anonymous_login' => ($modXml && isset($modXml->anonymous_login)) ? ((string)$modXml->anonymous_login === '1') : true, + 'steam_username' => isset($settings['steam_user']) ? $settings['steam_user'] : '', + 'steam_password' => isset($settings['steam_pass']) ? $settings['steam_pass'] : '', + 'workshop_item_ids' => [], + 'collection_ids' => [], + 'check_on_start' => true, + 'periodic_check_minutes' => null, + 'apply_updates' => 'on_start_only', + ]; +} diff --git a/modules/steam_workshop/main.php b/modules/steam_workshop/main.php index 87d9f7c2..9e1d1872 100644 --- a/modules/steam_workshop/main.php +++ b/modules/steam_workshop/main.php @@ -21,312 +21,166 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * */ -require_once("includes/lib_remote.php"); -require_once("modules/config_games/server_config_parser.php"); -require_once("modules/steam_workshop/functions.php"); +require_once('includes/lib_remote.php'); +require_once('modules/config_games/server_config_parser.php'); +require_once('modules/steam_workshop/lib/Workshop.php'); require_once('includes/form_table_class.php'); -echo ''."\n". - ''."\n". - ''; function exec_ogp_module() { - - Global $db,$view,$settings; + global $db, $view, $settings; echo '

Steam Workshop

'; - define('CONFIGS', "modules/steam_workshop/game_configs/"); - - if(isset($_GET['home_id-mod_id-ip-port']) && $_GET['home_id-mod_id-ip-port'] != "") - list($home_id, $mod_id, $ip, $port) = explode("-", $_GET['home_id-mod_id-ip-port']); - else - { + + if (!isset($_GET['home_id-mod_id-ip-port']) || $_GET['home_id-mod_id-ip-port'] === '') { print_failure(get_lang('no_game_servers_assigned')); return; } - - if(!isset($_POST['workshop_mod_id']) and !isset($_GET['show_log']) and !isset($_POST['manual_workshop_mod_id'])) - { - echo ""; - } - - $isAdmin = $db->isAdmin( $_SESSION['user_id'] ); - - if($isAdmin) - $home_cfg = $db->getGameHome($home_id); - else - $home_cfg = $db->getUserGameHome($_SESSION['user_id'],$home_id); - - if($home_cfg) - { - $server_xml = read_server_config(SERVER_CONFIG_LOCATION."/".$home_cfg['home_cfg_file']); - if($server_xml === FALSE) - { - print_failure(get_lang_f('failed_reading_xml_file', SERVER_CONFIG_LOCATION."/".$home_cfg['home_cfg_file'])); - return; - } - - if(!isset($home_cfg['mods'][$mod_id]['mod_key'])) - { - print_failure(get_lang_f('mod_id_does_not_exists_in_home', $mod_id, $home_id)); - return; - } - - $modkey = $home_cfg['mods'][$mod_id]['mod_key']; - $mod_xml = xml_get_mod($server_xml, $modkey); - - if (!$mod_xml) - { - print_failure(get_lang_f('mod_key_not_found_from_xml', $modkey)); - return; - } - - if(preg_match('/(linux|win)(32|64)?/i', $home_cfg['game_key'], $matches)) - { - $os = ""; - if(strtolower($matches[1]) == 'linux') - $os = "Linux"; - elseif(strtolower($matches[1]) == 'win') - $os = "Windows"; - } - else - { - print_failure(get_lang_f('unable_to_get_os_from_game_key', $home_cfg['game_key'])); - return; - } - - $xml_file = CONFIGS.$mod_xml->installer_name."_".$os.".xml"; - - if(!file_exists($xml_file)) - { - print_failure(get_lang('workshop_configuration_not_found')); - return; - } - - $dom = new DOMDocument(); - - if ( @$dom->load($xml_file) === FALSE ) - { - print_failure(get_lang('workshop_configuration_file_has_bad_format')); - return; - } - - $xml = simplexml_load_file($xml_file); - - if($xml !== false) - { - $remote = new OGPRemoteLibrary($home_cfg['agent_ip'],$home_cfg['agent_port'],$home_cfg['encryption_key'], $home_cfg['timeout']); - - if($remote->status_chk() !== 1) - { - print_failure(get_lang('remote_server_offline')); - } - - if(isset($_GET['show_log'])) - { - $update_active = $remote->get_log(OGP_SCREEN_TYPE_UPDATE,$home_id,clean_path($home_cfg['home_path']),$log_txt); - if ( $update_active == 1 ) - { - if(isset($_POST['sgc'])) - { - $remote->send_steam_guard_code($home_id, $_POST['sgc']); - return; - } - echo "

". get_lang("update_in_progress") ."

\n"; - echo "
".$log_txt."
\n\n
\n"; - if(preg_match('/Two-factor code:$/m', $log_txt) and !isset($_GET['get_sgc'])) - { - $view->refresh("?m=steam_workshop&p=main&home_id-mod_id-ip-port=".$_GET['home_id-mod_id-ip-port']."&get_sgc=show&show_log",0); - return; - } - if(isset($_GET['get_sgc']) && $_GET['get_sgc'] == 'show') - return; - - echo "

"; - echo get_lang("refresh_steam_workshop_status") ."

"; - $view->refresh("?m=steam_workshop&p=main&home_id-mod_id-ip-port=".$_GET['home_id-mod_id-ip-port']."&show_log",5); - } - else - { - print_success( get_lang("update_completed") ); - echo "
".$log_txt."
\n"; - echo "
<< ". get_lang("back") ."
"; - } - } - else - { - if(isset($_POST['workshop_mod_id']) OR isset($_POST['manual_workshop_mod_id'])) - { - $failure = false; - if(isset($_POST['manual_workshop_mod_id']) and $_POST['manual_workshop_mod_id'] != "" and preg_match('/^([0-9]+,?)+$/', $_POST['manual_workshop_mod_id'])) - { - $mods_list = $_POST['manual_workshop_mod_id']; - $mod_id_array = explode(',', $mods_list); - foreach($mod_id_array as $workshop_mod_id) - { - $exist = false; - foreach($xml->mods->mod as $mod) - { - if($mod['id'] == $workshop_mod_id) - { - $exist = true; - break; - } - } - - if(belongs_to_workshop($workshop_mod_id, $xml->workshop_id)) - { - if(!$exist) - { - list($mod_title, $mod_description, $mod_image_url, $download_url, $filename, $file_size) = get_mod_info($workshop_mod_id); - //add mods to the xml - $mod = new SimpleXMLElement(''); - $mod->addAttribute('id', $workshop_mod_id); - $mod->addChild('name', $mod_title); - $mod->addChild('description', base64_encode($mod_description)); - $mod->addChild('image_url', $mod_image_url); - $mod->addChild('download_url', $download_url); - $mod->addChild('filename', $filename); - $mod->addChild('file_size', $file_size); - $moddom = dom_import_simplexml($mod)->ownerDocument; - $moddom->formatOutput = true; - $mod_string = $moddom->saveXML($moddom->documentElement); - - $dom = dom_import_simplexml($xml)->ownerDocument; - $dom->formatOutput = true; - - $mods = $dom->getElementsByTagName('mods')->item(0); - - $f = $dom->createDocumentFragment(); - $f->appendXML($mod_string."\n"); - $mods->appendChild($f); - - - file_put_contents($xml_file, $dom->saveXML()); - $xml = simplexml_load_file($xml_file); - } - } - else - { - print_failure(get_lang_f('mod_does_not_belong_to_workshop', $workshop_mod_id)); - $failure = true; - } - } - } - elseif(isset($_POST['workshop_mod_id'])) - { - $mods_list = implode(',',$_POST['workshop_mod_id']); - } - - if(isset($_POST['install']) and !$failure and isset($mods_list) and preg_match('/^([0-9]+,?)+$/', $mods_list)) - { - $config = $xml->config; - $anonymous_login = $xml->anonymous_login; - $download_method = $xml->download_method; - $user = $settings['steam_user']; - $pass = $settings['steam_pass']; - $regex = $config->regex; - $mods_backreference_index = (int)$config->mods_backreference_index; - $variable = $config->variable; - $place_after = $config->place_after; - $mod_string = $config->mod_string; - $string_separator = $config->string_separator; - $config_file_path = clean_path($home_cfg['home_path']."/".$config->filepath); - $post_install = $xml->post_install; - $mod_names_list = get_mod_names_list($mods_list, $xml->mods->mod); - $mods_full_path = clean_path($home_cfg['home_path'].'/'.$xml->mods_path); - $workshop_id = $xml->workshop_id; - - $url_list = ""; - $filename_list = ""; - if($download_method == "steamapi") - { - foreach(explode(',', $mods_list) as $workshop_mod_id) - { - foreach($xml->mods->mod as $mod) - { - if($mod['id'] == $workshop_mod_id) - { - $separator = $url_list == ""?"":","; - $url_list .= $separator.$mod->download_url; - $filename_list .= $separator.$mod->filename; - } - } - } - } - - $steam_out = $remote->steam_workshop($home_id, $mods_full_path, - $workshop_id, $mods_list, - $regex, $mods_backreference_index, - $variable, $place_after, $mod_string, - $string_separator, $config_file_path, - $post_install, $mod_names_list, - $anonymous_login, $user, $pass, - $download_method, $url_list, $filename_list); - if ( $steam_out === 1 ) - { - print_success( get_lang("mod_installation_started") ); - $view->refresh("?m=steam_workshop&p=main&home_id-mod_id-ip-port=".$_GET['home_id-mod_id-ip-port']."&show_log", 2); - } - elseif( $steam_out === 0 ) - { - print_failure( get_lang("failed_to_start_steam_workshop") ); - return; - } - elseif ( $steam_out === -1 ) - { - print_failure( get_lang("connection_error") ); - } - } - - if(isset($_POST['show_info']) and !$failure and isset($mods_list) and preg_match('/^([0-9]+,?)+$/', $mods_list)) - { - $mod_id_array = explode(',', $mods_list); - echo ""; - foreach($xml->mods->mod as $mod) - { - if(in_array($mod['id'],$mod_id_array)) - { - echo ""; - } - } - echo "

".$mod->name."

". - "
". - "
".htmlentities(base64_decode($mod->description))."
".get_lang('back').""; - } - } - else - { - $ft = new FormTable(); - $ft->start_form("?m=steam_workshop&p=main&home_id-mod_id-ip-port=".$_GET['home_id-mod_id-ip-port'], "post", "onsubmit='return isValidForm(this)' data-form-error='".get_lang('select_at_least_one_mod_or_enter_mod_id')."'"); - $ft->start_table(); - if(count($xml->mods->mod) > 0) - { - echo '
'; - foreach($xml->mods->mod as $mod) - echo "
"; - echo '
'; - } - $ft->add_field('string', 'manual_workshop_mod_id',''); - $ft->end_table(); - $ft->add_button("submit", "install", get_lang('install_mod')); - $ft->add_button("submit", "show_info", get_lang('show_mod_info')); - $ft->end_form(); - } - } - } - else - { - print_failure(get_lang('workshop_configuration_file_has_bad_format')); - return; - } - } - else - { + + list($homeId, $modId) = explode('-', $_GET['home_id-mod_id-ip-port']); + + $isAdmin = $db->isAdmin($_SESSION['user_id']); + $homeCfg = $isAdmin ? $db->getGameHome($homeId) : $db->getUserGameHome($_SESSION['user_id'], $homeId); + if (!$homeCfg) { print_failure(get_lang('game_home_not_found')); return; } + + $serverXml = read_server_config(SERVER_CONFIG_LOCATION . '/' . $homeCfg['home_cfg_file']); + if ($serverXml === false) { + print_failure(get_lang_f('failed_reading_xml_file', SERVER_CONFIG_LOCATION . '/' . $homeCfg['home_cfg_file'])); + return; + } + + if (!isset($homeCfg['mods'][$modId]['mod_key'])) { + print_failure(get_lang_f('mod_id_does_not_exists_in_home', $modId, $homeId)); + return; + } + + $modKey = $homeCfg['mods'][$modId]['mod_key']; + $modXml = xml_get_mod($serverXml, $modKey); + if (!$modXml) { + print_failure(get_lang_f('mod_key_not_found_from_xml', $modKey)); + return; + } + + $configStore = new WorkshopConfigStore('modules/steam_workshop/data/configs.json'); + $stateStore = new WorkshopStateStore('modules/steam_workshop/data'); + $resolver = new WorkshopResolver(); + + $config = $configStore->get($homeId); + if ($config === null) { + $config = workshop_build_default_config($homeCfg, $modXml, $settings); + $configStore->put($homeId, $config); + } + + $remote = new OGPRemoteLibrary($homeCfg['agent_ip'], $homeCfg['agent_port'], $homeCfg['encryption_key'], $homeCfg['timeout']); + if ($remote->status_chk() !== 1) { + print_failure(get_lang('remote_server_offline')); + return; + } + + $syncService = new WorkshopSyncService($remote, $homeCfg, $configStore, $stateStore, $resolver); + $state = $stateStore->get($homeId); + $items = $resolver->resolveItems($config); + $details = $resolver->fetchItemDetails($items); + + $message = ''; + if (isset($_POST['action'])) { + switch ($_POST['action']) { + case 'add_item': + if (isset($_POST['item_id']) && preg_match('/^[0-9]+$/', $_POST['item_id'])) { + $config['workshop_item_ids'][] = $_POST['item_id']; + $config['workshop_item_ids'] = array_values(array_unique(array_filter($config['workshop_item_ids'], 'strlen'))); + $configStore->put($homeId, $config); + $items = $resolver->resolveItems($config); + $details = $resolver->fetchItemDetails($items); + $message = get_lang('mod_installation_started'); + } else { + print_failure(get_lang('invalid_mod_id')); + } + break; + case 'add_collection': + if (isset($_POST['collection_id']) && preg_match('/^[0-9]+$/', $_POST['collection_id'])) { + $config['collection_ids'][] = $_POST['collection_id']; + $config['collection_ids'] = array_values(array_unique(array_filter($config['collection_ids'], 'strlen'))); + $configStore->put($homeId, $config); + $items = $resolver->resolveItems($config); + $details = $resolver->fetchItemDetails($items); + $message = get_lang('settings_updated'); + } else { + print_failure(get_lang('invalid_mod_id')); + } + break; + case 'remove_item': + if (isset($_POST['item_id'])) { + $config['workshop_item_ids'] = array_values(array_filter($config['workshop_item_ids'], function ($id) { + return isset($_POST['item_id']) ? ($id !== $_POST['item_id']) : true; + })); + $configStore->put($homeId, $config); + $items = $resolver->resolveItems($config); + $details = $resolver->fetchItemDetails($items); + $message = get_lang('settings_updated'); + } + break; + case 'sync_now': + $result = $syncService->sync($homeId); + if ($result->success) { + print_success($result->message); + } else { + print_failure($result->message); + } + $state = $stateStore->get($homeId); + break; + } + } + + if ($message !== '') { + print_success($message); + } + + echo '
'; + echo '

' . get_lang('installed_mods') . '

'; + if (empty($items)) { + echo '

' . get_lang('no_mods_found') . '

'; + } else { + echo ''; + echo ''; + foreach ($items as $id) { + $name = isset($details[$id]['title']) ? htmlentities($details[$id]['title']) : $id; + $status = isset($state['items'][$id]) ? get_lang('installed') : get_lang('pending'); + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + } + echo '
ID' . get_lang('name') . '' . get_lang('status') . '
' . $id . '' . $name . '' . $status . ''; + echo '
'; + echo ''; + echo ''; + echo '
'; + echo '
'; + } + echo '
'; + + echo '
'; + echo '

' . get_lang('add_mod') . '

'; + echo '
'; + echo ' '; + echo ''; + echo '
'; + echo '
'; + echo ' '; + echo ''; + echo '
'; + echo '
'; + + echo '
'; + echo '

' . get_lang('actions') . '

'; + echo '
'; + echo ''; + echo '
'; + echo '
'; } ?> diff --git a/modules/steam_workshop/module.php b/modules/steam_workshop/module.php index b8faf67e..15ad2dfc 100644 --- a/modules/steam_workshop/module.php +++ b/modules/steam_workshop/module.php @@ -23,7 +23,7 @@ */ // Module general information $module_title = "Steam Workshop"; -$module_version = "1.1"; +$module_version = "2.0"; $db_version = 0; $module_required = TRUE; $module_menus = array(array( 'subpage' => 'workshop_admin', 'name'=>'Steam Workshop', 'group'=>'admin' )); diff --git a/modules/steam_workshop/workshop_admin.php b/modules/steam_workshop/workshop_admin.php index 7e808168..a45dbc62 100644 --- a/modules/steam_workshop/workshop_admin.php +++ b/modules/steam_workshop/workshop_admin.php @@ -22,234 +22,104 @@ * */ require_once('includes/form_table_class.php'); -require_once("modules/steam_workshop/functions.php"); +require_once('modules/config_games/server_config_parser.php'); +require_once('modules/steam_workshop/functions.php'); +require_once('modules/steam_workshop/lib/Workshop.php'); + function exec_ogp_module() { - - Global $db,$view; - echo '

Steam Workshop

'; - define('CONFIGS', "modules/steam_workshop/game_configs/"); - - if(isset($_REQUEST['home_cfg_id-mod_cfg_id-os'])) - list($home_cfg_id, $mod_cfg_id, $os) = explode('-', $_REQUEST['home_cfg_id-mod_cfg_id-os']); - - if(isset($home_cfg_id) and isset($mod_cfg_id)) - { - $gameCfg = $db->getGameCfg($home_cfg_id); - $cfgMods = $db->getCfgMods($home_cfg_id); - - foreach($cfgMods as $cfgMod) - { - if($cfgMod['mod_cfg_id'] == $mod_cfg_id) - $modkey = $cfgMod['mod_key']; - } - - $server_xml = read_server_config(SERVER_CONFIG_LOCATION."/".$gameCfg['home_cfg_file']); - - $mod_xml = xml_get_mod($server_xml, $modkey); - - if (!$mod_xml) - { - print_failure(get_lang_f('mod_key_not_found_from_xml',$modkey)); - return; - } - $xml_file = CONFIGS.$mod_xml->installer_name."_".$os.".xml"; - - if(isset($_POST['save_config'])) - { - $xml = new SimpleXMLElement(''); - $xml->addChild('workshop_id', $_POST['workshop_id']); - $xml->addChild('download_method', $_POST['download_method']); - $xml->addChild('anonymous_login', $_POST['anonymous_login']); - $xml->addChild('mods_path', $_POST['mods_path']); - $mods = $xml->addChild('mods'); - if(file_exists($xml_file)) - { - $file_xml = simplexml_load_file($xml_file); - foreach($file_xml->mods->mod as $xml_mod) - { - $mod = $mods->addChild('mod'); - $mod->addAttribute('id', $xml_mod['id']); - $mod->addChild('name', $xml_mod->name); - $mod->addChild('description', $xml_mod->description); - $mod->addChild('image_url', $xml_mod->image_url); - $mod->addChild('download_url', $xml_mod->download_url); - $mod->addChild('filename', $xml_mod->filename); - $mod->addChild('file_size', $xml_mod->file_size); - } - } - - $config = $xml->addChild('config'); - $config->addChild('regex', $_POST['regex']); - $config->addChild('mods_backreference_index', $_POST['mods_backreference_index']); - $config->addChild('variable', $_POST['variable']); - $config->addChild('place_after', $_POST['place_after']); - $config->addChild('mod_string', $_POST['mod_string']); - $config->addChild('string_separator', $_POST['string_separator']); - $config->addChild('filepath', $_POST['filepath']); - $xml->addChild('post_install', str_replace('&','&',$_POST['post_install'])); - $xml->addChild('uninstall', str_replace('&','&',$_POST['uninstall'])); - - $dom = dom_import_simplexml($xml)->ownerDocument; - $dom->formatOutput = true; - file_put_contents($xml_file, $dom->saveXML()); - } - - if(isset($_POST['remove_mods'])) - { - $xml = new SimpleXMLElement(''); - $xml->addChild('workshop_id', $_POST['workshop_id']); - $xml->addChild('download_method', $_POST['download_method']); - $xml->addChild('anonymous_login', $_POST['anonymous_login']); - $xml->addChild('mods_path', $_POST['mods_path']); - $mods = $xml->addChild('mods'); - if(file_exists($xml_file)) - { - $file_xml = simplexml_load_file($xml_file); - foreach($file_xml->mods->mod as $xml_mod) - { - if(in_array($xml_mod['id'],$_POST['workshop_mod_id'])) - continue; - $mod = $mods->addChild('mod'); - $mod->addAttribute('id', $xml_mod['id']); - $mod->addChild('name', $xml_mod->name); - $mod->addChild('description', $xml_mod->description); - $mod->addChild('image_url', $xml_mod->image_url); - $mod->addChild('download_url', $xml_mod->download_url); - $mod->addChild('filename', $xml_mod->filename); - $mod->addChild('file_size', $xml_mod->file_size); - } - } - $config = $xml->addChild('config'); - $config->addChild('regex', $_POST['regex']); - $config->addChild('mods_backreference_index', $_POST['mods_backreference_index']); - $config->addChild('variable', $_POST['variable']); - $config->addChild('place_after', $_POST['place_after']); - $config->addChild('mod_string', $_POST['mod_string']); - $config->addChild('string_separator', $_POST['string_separator']); - $config->addChild('filepath', $_POST['filepath']); - $xml->addChild('post_install', $_POST['post_install']); - $xml->addChild('uninstall', $_POST['uninstall']); - - $dom = dom_import_simplexml($xml)->ownerDocument; - $dom->formatOutput = true; - file_put_contents($xml_file, $dom->saveXML()); - } - - if(file_exists($xml_file)) - { - $xml = simplexml_load_file($xml_file); - - $workshop_id = $xml->workshop_id; - $download_method = $xml->download_method; - $anonymous_login = $xml->anonymous_login; - $mods_path = $xml->mods_path; - $regex = $xml->config->regex; - $mods_backreference_index = $xml->config->mods_backreference_index; - $variable = $xml->config->variable; - $place_after = $xml->config->place_after; - $mod_string = $xml->config->mod_string; - $string_separator = $xml->config->string_separator; - $filepath = $xml->config->filepath; - $post_install = $xml->post_install; - $uninstall = $xml->uninstall; - } - - - } - - $game_cfgs = $db->getGameCfgs(); - $games[0] = get_lang('select_game'); - foreach($game_cfgs as $game_cfg) - { - $server_xml = read_server_config(SERVER_CONFIG_LOCATION."/".$game_cfg['home_cfg_file']); - if(isset($server_xml->installer) and $server_xml->installer == "steamcmd") - { - $cfgMods = $db->getCfgMods($game_cfg['home_cfg_id']); - foreach($cfgMods as $cfgMod) - { - $mod_xml = xml_get_mod($server_xml, $cfgMod['mod_key']); - if(isset($mod_xml->installer_name) and !in_array((string)$mod_xml->installer_name, get_blacklist())) - { - preg_match('/(linux|win)(32|64)?/i', $game_cfg['game_key'], $matches); - - if(strtolower($matches[1]) == 'linux') - $os = "Linux"; - elseif(strtolower($matches[1]) == 'win') - $os = "Windows"; - if(isset($matches[2]) and strtolower($matches[2]) == '64') - $arch = "64"; - else - $arch = "32"; - - $modname = strtolower($cfgMod['mod_name']) == "none"? "":" [MOD:" . $cfgMod['mod_name']."]"; - - $games[$game_cfg['home_cfg_id'].'-'.$cfgMod['mod_cfg_id'].'-'.$os] = $game_cfg['game_name'] . " (" . $os . " " . $arch . "bits)$modname"; - } - } + global $db; + echo '

Steam Workshop – Admin

'; + + $homes = $db->getGameHomes(); + $eligibleHomes = []; + foreach ($homes as $home) { + $serverXml = read_server_config(SERVER_CONFIG_LOCATION . '/' . $home['home_cfg_file']); + if ($serverXml !== false && isset($serverXml->installer) && strtolower((string)$serverXml->installer) === 'steamcmd') { + $eligibleHomes[$home['home_id']] = $home; } } - - $download_methods = array("steamcmd", "steamapi"); - + + if (empty($eligibleHomes)) { + print_failure(get_lang('no_game_servers_assigned')); + return; + } + + $selectedHomeId = isset($_REQUEST['home_id']) ? $_REQUEST['home_id'] : array_key_first($eligibleHomes); + if (!isset($eligibleHomes[$selectedHomeId])) { + $selectedHomeId = array_key_first($eligibleHomes); + } + + $homeCfg = $db->getGameHome($selectedHomeId); + $serverXml = read_server_config(SERVER_CONFIG_LOCATION . '/' . $homeCfg['home_cfg_file']); + $mods = isset($homeCfg['mods']) ? $homeCfg['mods'] : []; + $firstMod = reset($mods); + $modKey = isset($firstMod['mod_key']) ? $firstMod['mod_key'] : null; + $modXml = $modKey ? xml_get_mod($serverXml, $modKey) : null; + + $configStore = new WorkshopConfigStore('modules/steam_workshop/data/configs.json'); + $config = $configStore->get($selectedHomeId); + if ($config === null) { + $config = workshop_build_default_config($homeCfg, $modXml, []); + $configStore->put($selectedHomeId, $config); + } + + if (isset($_POST['save_config'])) { + $config['workshop_app_id'] = trim($_POST['workshop_app_id']); + $config['download_method'] = $_POST['download_method']; + $config['deploy_mode'] = $_POST['deploy_mode']; + $config['deploy_destination'] = clean_path($_POST['deploy_destination']); + $config['staging_path'] = clean_path($_POST['staging_path']); + $config['regex'] = $_POST['regex']; + $config['mods_backreference_index'] = (int)$_POST['mods_backreference_index']; + $config['variable'] = $_POST['variable']; + $config['place_after'] = $_POST['place_after']; + $config['mod_string'] = $_POST['mod_string']; + $config['string_separator'] = $_POST['string_separator']; + $config['filepath'] = clean_path($_POST['filepath']); + $config['anonymous_login'] = isset($_POST['anonymous_login']) && $_POST['anonymous_login'] === '1'; + $config['steam_username'] = $_POST['steam_username']; + $config['steam_password'] = $_POST['steam_password']; + $config['check_on_start'] = isset($_POST['check_on_start']) && $_POST['check_on_start'] === '1'; + $config['periodic_check_minutes'] = $_POST['periodic_check_minutes'] === '' ? null : (int)$_POST['periodic_check_minutes']; + $config['apply_updates'] = $_POST['apply_updates']; + $configStore->put($selectedHomeId, $config); + print_success(get_lang('settings_updated')); + } + + $downloadMethods = array('steamcmd' => 'steamcmd'); + $deployModes = array('copy' => 'copy', 'symlink' => 'symlink'); + $applyModes = array('on_start_only' => 'on_start_only', 'download_now_apply_on_next_restart' => 'download_now_apply_on_next_restart'); + + $homeOptions = []; + foreach ($eligibleHomes as $id => $home) { + $homeOptions[$id] = $home['home_name']; + } + $ft = new FormTable(); - $ft->start_form("?m=steam_workshop&p=workshop_admin", "post", "autocomplete=\"off\""); + $ft->start_form('?m=steam_workshop&p=workshop_admin', 'post', 'autocomplete="off"'); $ft->start_table(); - $ft->add_custom_field('game', create_drop_box_from_array_onchange($games, "home_cfg_id-mod_cfg_id-os", @$_REQUEST['home_cfg_id-mod_cfg_id-os'])); - if(isset($home_cfg_id) and isset($mod_cfg_id)) - { - $ft->add_field('string','workshop_id',@$workshop_id); - $ft->add_custom_field('download_method',create_drop_box_from_array($download_methods, "download_method", @$download_method)); - $ft->add_field('on_off','anonymous_login',@$anonymous_login); - $ft->add_field('string','mods_path',@$mods_path); - $ft->add_field('string','regex',@$regex); - $ft->add_field('string','mods_backreference_index',@$mods_backreference_index); - $ft->add_field('string','variable',@$variable); - $ft->add_field('string','place_after',@$place_after); - $ft->add_field('string','mod_string',@$mod_string); - $ft->add_field('string','string_separator',@$string_separator); - $ft->add_field('string','filepath',@$filepath); - $ft->add_field('text','post_install',@$post_install); - $ft->add_field('text','uninstall',@$uninstall); - - - $ft->end_table(); - $ft->add_button("submit","save_config",get_lang('save_config')); - $ft->end_form(); - } - else - { - $ft->end_table(); - $ft->end_form(); - } - - if(isset($xml) and count($xml->mods->mod) > 0) - { - $ft = new FormTable(); - $ft->start_form("?m=steam_workshop&p=workshop_admin&home_cfg_id-mod_cfg_id-os=".$_REQUEST['home_cfg_id-mod_cfg_id-os'], "post", "autocomplete=\"off\""); - $ft->start_table(); - echo ""; - $ft->add_field_hidden('workshop_id',$workshop_id); - $ft->add_field_hidden('download_method',$download_method); - $ft->add_field_hidden('anonymous_login',$anonymous_login); - $ft->add_field_hidden('mods_path',$mods_path); - $ft->add_field_hidden('regex',$regex); - $ft->add_field_hidden('mods_backreference_index',$mods_backreference_index); - $ft->add_field_hidden('variable',$variable); - $ft->add_field_hidden('place_after',$place_after); - $ft->add_field_hidden('mod_string',$mod_string); - $ft->add_field_hidden('string_separator',$string_separator); - $ft->add_field_hidden('filepath',$filepath); - $ft->add_field_hidden('post_install',$post_install); - $ft->add_field_hidden('uninstall',$uninstall); - echo "". - '
'; - foreach($xml->mods->mod as $mod) - echo "
"; - echo '
'; - $ft->end_table(); - $ft->add_button("submit","remove_mods",get_lang('remove_mods')); - $ft->end_form(); - } + $ft->add_custom_field('home_id', create_drop_box_from_array_onchange($homeOptions, 'home_id', $selectedHomeId)); + $ft->add_field('string', 'workshop_app_id', $config['workshop_app_id']); + $ft->add_custom_field('download_method', create_drop_box_from_array($downloadMethods, 'download_method', $config['download_method'])); + $ft->add_custom_field('deploy_mode', create_drop_box_from_array($deployModes, 'deploy_mode', $config['deploy_mode'])); + $ft->add_field('string', 'deploy_destination', $config['deploy_destination']); + $ft->add_field('string', 'staging_path', $config['staging_path']); + $ft->add_field('string', 'regex', $config['regex']); + $ft->add_field('string', 'mods_backreference_index', $config['mods_backreference_index']); + $ft->add_field('string', 'variable', $config['variable']); + $ft->add_field('string', 'place_after', $config['place_after']); + $ft->add_field('string', 'mod_string', $config['mod_string']); + $ft->add_field('string', 'string_separator', $config['string_separator']); + $ft->add_field('string', 'filepath', $config['filepath']); + $ft->add_field('on_off', 'anonymous_login', $config['anonymous_login'] ? '1' : '0'); + $ft->add_field('string', 'steam_username', $config['steam_username']); + $ft->add_field('password', 'steam_password', $config['steam_password']); + $ft->add_field('on_off', 'check_on_start', $config['check_on_start'] ? '1' : '0'); + $ft->add_field('string', 'periodic_check_minutes', $config['periodic_check_minutes']); + $ft->add_custom_field('apply_updates', create_drop_box_from_array($applyModes, 'apply_updates', $config['apply_updates'])); + $ft->end_table(); + $ft->add_button('submit', 'save_config', get_lang('save_config')); + $ft->end_form(); } ?> \ No newline at end of file