Steam workshop UPDATE
This commit is contained in:
parent
49600d1cfd
commit
fcc1b18e4c
23 changed files with 1290 additions and 335 deletions
|
|
@ -0,0 +1,13 @@
|
|||
1565508334,@SepticFalconMerch
|
||||
1559317235,@Weapon Redux Pack
|
||||
1567720365,@DayZ Plus
|
||||
1572541337,@InventoryPlus
|
||||
1560819773,@[MOV] Unlimited Stamina
|
||||
1599353287,@bT_Map
|
||||
1589849870,@bT_Items
|
||||
1617874376,@OP_BaseItems
|
||||
1574054508,@BuildAnywhere
|
||||
1590841260,@Trader
|
||||
1559212036,@RPCFramework
|
||||
1578227776,@Permissions-Framework
|
||||
1564026768,@Community-Online-Tools
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
@echo off
|
||||
TITLE DayZ Server 1 - Normal Modded Server
|
||||
COLOR 0B
|
||||
:: Parameters::
|
||||
::DayZ Parameters
|
||||
set DAYZ-SA_SERVER_LOCATION="C:\FILEPATH\Server"
|
||||
set DAYZ-SAL_NAME=DZSALModServer.exe
|
||||
set LOG_LOCATION=C:\FILEPATH\ServerLogs\Server
|
||||
set PORT_NUM=2302
|
||||
::
|
||||
::Battleye Parameters
|
||||
set BE_FOLDER="C:\FILEPATH\Server\Battleye"
|
||||
set BEC_LOCATION="C:\FILEPATH\Server\Battleye\Bec"
|
||||
::
|
||||
::ModCheck Parameters
|
||||
set MOD_LIST=(C:\FILEPATH\Modlist.txt)
|
||||
set STEAM_WORKSHOP=C:\FILEPATH\steamcmd\steamapps\workshop\content\221100
|
||||
set STEAMCMD_LOCATION=C:\FILEPATH\steamcmd
|
||||
set STEAM_USER=your steam username
|
||||
set STEAMCMD_DEL=5
|
||||
setlocal EnableDelayedExpansion
|
||||
::::::::::::::
|
||||
|
||||
echo Agusanz
|
||||
goto checksv
|
||||
pause
|
||||
|
||||
:checksv
|
||||
tasklist /FI "IMAGENAME eq %DAYZ-SAL_NAME%" 2>NUL | find /I /N "%DAYZ-SAL_NAME%">NUL
|
||||
if "%ERRORLEVEL%"=="0" goto checkbec
|
||||
cls
|
||||
echo Server is not running, taking care of it..
|
||||
goto killsv
|
||||
|
||||
:checkbec
|
||||
tasklist /FI "IMAGENAME eq Bec.exe" 2>NUL | find /I /N "Bec.exe">NUL
|
||||
if "%ERRORLEVEL%"=="0" goto loopsv
|
||||
cls
|
||||
echo Bec is not running, taking care of it..
|
||||
goto startbec
|
||||
|
||||
:loopsv
|
||||
FOR /L %%s IN (30,-1,0) DO (
|
||||
cls
|
||||
echo Server is running. Checking again in %%s seconds..
|
||||
timeout 1 >nul
|
||||
)
|
||||
goto checksv
|
||||
|
||||
:killsv
|
||||
taskkill /f /im %DAYZ-SAL_NAME%
|
||||
goto checkmods
|
||||
|
||||
:startsv
|
||||
cls
|
||||
echo Starting DayZ SA Server.
|
||||
timeout 1 >nul
|
||||
cls
|
||||
echo Starting DayZ SA Server..
|
||||
timeout 1 >nul
|
||||
cls
|
||||
echo Starting DayZ SA Server...
|
||||
cd "%DAYZ-SA_SERVER_LOCATION%"
|
||||
start %DAYZ-SAL_NAME% -config=serverDZ.cfg -port=%PORT_NUM% -dologs -adminlog -netlog -freezecheck -BEpath=%BE_FOLDER% -profiles=%LOG_LOCATION% "-mod=!MODS_TO_LOAD!%" "-scrAllowFileWrite"
|
||||
FOR /L %%s IN (30,-1,0) DO (
|
||||
cls
|
||||
echo Initializing server, wait %%s seconds to initialize Bec..
|
||||
timeout 1 >nul
|
||||
)
|
||||
goto startbec
|
||||
|
||||
:startbec
|
||||
cls
|
||||
echo Starting Bec.
|
||||
timeout 1 >nul
|
||||
cls
|
||||
echo Starting Bec..
|
||||
timeout 1 >nul
|
||||
cls
|
||||
echo Starting Bec...
|
||||
timeout 1 >nul
|
||||
cd "%BEC_LOCATION%"
|
||||
start Bec.exe -f Config.cfg
|
||||
goto checksv
|
||||
|
||||
:checkmods
|
||||
cls
|
||||
FOR /L %%s IN (%STEAMCMD_DEL%,-1,0) DO (
|
||||
cls
|
||||
echo Checking for mod updates in %%s seconds..
|
||||
timeout 1 >nul
|
||||
)
|
||||
echo Reading in configurations/variables set in this batch and MOD_LIST. Updating Steam Workbench mods...
|
||||
@ timeout 1 >nul
|
||||
cd %STEAMCMD_LOCATION%
|
||||
for /f "tokens=1,2 delims=," %%g in %MOD_LIST% do steamcmd.exe +login %STEAM_USER% +workshop_download_item 221100 "%%g" +quit
|
||||
cls
|
||||
echo Steam Workshop files up to date! Syncing Workbench source with server destination...
|
||||
@ timeout 2 >nul
|
||||
cls
|
||||
@ for /f "tokens=1,2 delims=," %%g in %MOD_LIST% do robocopy "%STEAM_WORKSHOP%\%%g" "%DAYZ-SA_SERVER_LOCATION%\%%h" *.* /mir
|
||||
@ for /f "tokens=1,2 delims=," %%g in %MOD_LIST% do forfiles /p "%DAYZ-SA_SERVER_LOCATION%\%%h" /m *.bikey /s /c "cmd /c copy @path %DAYZ-SA_SERVER_LOCATION%\keys"
|
||||
cls
|
||||
echo Sync complete! If sync not completed correctly, verify configuration file.
|
||||
@ timeout 3 >nul
|
||||
cls
|
||||
set "MODS_TO_LOAD="
|
||||
for /f "tokens=1,2 delims=," %%g in %MOD_LIST% do (
|
||||
set "MODS_TO_LOAD=!MODS_TO_LOAD!%%h;"
|
||||
)
|
||||
set "MODS_TO_LOAD=!MODS_TO_LOAD:~0,-1!"
|
||||
ECHO Will start DayZ with the following mods: !MODS_TO_LOAD!%
|
||||
@ timeout 3 >nul
|
||||
goto startsv
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
@echo off
|
||||
TITLE DayZ Server
|
||||
COLOR 0A
|
||||
:: Variables::
|
||||
::DZSALModServer.exe path
|
||||
set DAYZ-SA_SERVER_LOCATION="C:\SERVERFILEPATH\server"
|
||||
::Bec.exe path
|
||||
set BEC_LOCATION="C:\SERVERFILEPATH\server\Battleye\Bec"
|
||||
::
|
||||
::ModCheck ; Enter location of Mod List and where your steam workshop files download to (set for default)
|
||||
set MOD_LIST=(C:\FILEPATH\Modlist.txt)
|
||||
set STEAM_WORKSHOP=C:\FILEPATH\steamcmd\steamapps\workshop\content\221100
|
||||
set STEAMCMD_LOCATION=C:\FILEPATH\steamcmd
|
||||
set STEAM_USER=USERNAME
|
||||
set STEAMCMD_DEL=10
|
||||
::::::::::::::
|
||||
|
||||
echo Agusanz
|
||||
goto checksv
|
||||
pause
|
||||
|
||||
:checksv
|
||||
tasklist /FI "IMAGENAME eq DZSALModserver.exe" 2>NUL | find /I /N "DZSALModserver.exe">NUL
|
||||
if "%ERRORLEVEL%"=="0" goto checkbec
|
||||
cls
|
||||
echo Server is not running, taking care of it..
|
||||
goto killsv
|
||||
|
||||
:checkbec
|
||||
tasklist /FI "IMAGENAME eq Bec.exe" 2>NUL | find /I /N "Bec.exe">NUL
|
||||
if "%ERRORLEVEL%"=="0" goto loopsv
|
||||
cls
|
||||
echo Bec is not running, taking care of it..
|
||||
goto startbec
|
||||
|
||||
:loopsv
|
||||
FOR /L %%s IN (30,-1,0) DO (
|
||||
cls
|
||||
echo Server is running. Checking again in %%s seconds..
|
||||
timeout 1 >nul
|
||||
)
|
||||
goto checksv
|
||||
|
||||
:killsv
|
||||
taskkill /f /im Bec.exe
|
||||
taskkill /f /im DZSALModserver.exe
|
||||
goto checkmods
|
||||
|
||||
:startsv
|
||||
cls
|
||||
echo Starting DayZ SA Server.
|
||||
timeout 1 >nul
|
||||
cls
|
||||
echo Starting DayZ SA Server..
|
||||
timeout 1 >nul
|
||||
cls
|
||||
echo Starting DayZ SA Server...
|
||||
cd "%DAYZ-SA_SERVER_LOCATION%"
|
||||
start DZSALModserver.exe -config=serverDZ.cfg -port=2302 -dologs -adminlog -netlog -freezecheck -BEpath=C:\SERVERFILEPATH\server\battleye -profiles=C:\FILEPATH\ServerLogs\server "-mod=@yourmods;@gohere"
|
||||
FOR /L %%s IN (30,-1,0) DO (
|
||||
cls
|
||||
echo Initializing server, wait %%s seconds to initialize Bec..
|
||||
timeout 1 >nul
|
||||
)
|
||||
goto startbec
|
||||
|
||||
:startbec
|
||||
cls
|
||||
echo Starting Bec.
|
||||
timeout 1 >nul
|
||||
cls
|
||||
echo Starting Bec..
|
||||
timeout 1 >nul
|
||||
cls
|
||||
echo Starting Bec...
|
||||
timeout 1 >nul
|
||||
cd "%BEC_LOCATION%"
|
||||
start Bec.exe -f Config.cfg
|
||||
goto checksv
|
||||
|
||||
:checkmods
|
||||
cls
|
||||
FOR /L %%s IN (90,-1,0) DO (
|
||||
cls
|
||||
echo Checking for mod updates in %%s seconds..
|
||||
timeout 1 >nul
|
||||
)
|
||||
echo Reading in configurations/variables set in this batch and MOD_LIST. Updating Steam Workbench mods...
|
||||
@ timeout 1 >nul
|
||||
cd %STEAMCMD_LOCATION%
|
||||
for /f "tokens=1,2 delims=," %%g in %MOD_LIST% do steamcmd.exe +login %STEAM_USER% +workshop_download_item 221100 "%%g" +quit +cls
|
||||
cls
|
||||
echo Steam Workshop files up to date! Syncing Workbench source with server destination...
|
||||
@ timeout 2 >nul
|
||||
@ for /f "tokens=1,2 delims=," %%g in %MOD_LIST% do robocopy "%STEAM_WORKSHOP%\%%g" "%DAYZ-SA_SERVER_LOCATION%\%%h" *.* /mir
|
||||
cls
|
||||
echo Sync complete! If sync not completed correctly, verify configuration file.
|
||||
@ timeout 3 >nul
|
||||
cls
|
||||
goto startsv
|
||||
26
modules/steam_workshop/README.md
Normal file
26
modules/steam_workshop/README.md
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# Steam Workshop Automation (WIP)
|
||||
|
||||
This folder now hosts the rewritten Steam Workshop tooling for the GSP panel. The previous DayZ-only batch scripts are left untouched under `DayZ Workshop Mod Auto Update/` for historical reference, but the new MVC layer introduces adapters, XML-backed configuration, and an eventual agent scheduler.
|
||||
|
||||
## Milestone 1 summary
|
||||
|
||||
- **Controllers** – `controllers/SteamWorkshopController.php` routes the module entrypoint through a thin MVC wrapper.
|
||||
- **Service layer** – `lib/SteamWorkshopService.php` loads/saves per-home XML configs under `data/configs/<home_id>.xml`, parses Modlist-style imports, and exposes adapter metadata.
|
||||
- **Adapters** – `lib/GameAdapters/*.xml` define canonical behaviors for DayZ, Arma 3, ARK, Garry's Mod, and CS2. They are validated against `schema.xsd`.
|
||||
- **Views** – `views/*` render the server list, edit form, and parsed mod table using localized strings from `lang/en_US.php`.
|
||||
- **Data directory** – `data/configs/` stores the serialized workshop configuration for each game home.
|
||||
|
||||
## Editing workflow
|
||||
|
||||
1. Visit `home.php?m=steam_workshop&p=main` to see the list of homes you can access. Click **Configure** on any home to edit its Workshop setup.
|
||||
2. Paste a Modlist.txt style payload (e.g., `1565508334,@MyMod`) into the Workshop IDs textarea.
|
||||
3. Choose the adapter, interval, install strategy, and on-update action, then click **Save settings**. The controller serializes this into XML so the agent can consume it later.
|
||||
4. Config files live under `modules/steam_workshop/data/configs/`. Delete a file to reset a home to defaults.
|
||||
|
||||
## Roadmap
|
||||
|
||||
- **Milestone 2** will flesh out the adapter runtime helpers and validation against the schema.
|
||||
- **Milestone 3** wires the Linux/Windows agents via a new `workshop_update` RPC and scheduler, using the serialized XML from this module.
|
||||
- Later milestones add dry-run/apply actions, activation writers, and safe apply hooks.
|
||||
|
||||
> GSP is a heavily customized fork of OGP maintained by WDS. Keep all Steam Workshop code inside this module tree so storefront, agents, and future docs stay decoupled.
|
||||
135
modules/steam_workshop/controllers/SteamWorkshopController.php
Normal file
135
modules/steam_workshop/controllers/SteamWorkshopController.php
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../lib/SteamWorkshopService.php';
|
||||
|
||||
class SteamWorkshopController
|
||||
{
|
||||
private SteamWorkshopService $service;
|
||||
private array $lang;
|
||||
|
||||
public function __construct(OGPDatabase $db)
|
||||
{
|
||||
$this->service = new SteamWorkshopService($db);
|
||||
$this->lang = $this->loadLang();
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
global $db;
|
||||
|
||||
$userId = (int)($_SESSION['user_id'] ?? 0);
|
||||
$isAdmin = $db->isAdmin($userId);
|
||||
$action = $_GET['action'] ?? 'index';
|
||||
|
||||
echo '<link rel="stylesheet" type="text/css" href="modules/steam_workshop/steam_workshop.css" />';
|
||||
|
||||
if ($action === 'save' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$this->handleSave($userId, $isAdmin);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($action === 'edit') {
|
||||
$this->handleEdit($userId, $isAdmin);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->renderIndex($userId, $isAdmin);
|
||||
}
|
||||
|
||||
private function handleSave(int $userId, bool $isAdmin): void
|
||||
{
|
||||
$homeId = isset($_POST['home_id']) ? (int)$_POST['home_id'] : 0;
|
||||
if ($homeId <= 0) {
|
||||
print_failure($this->lang['error_missing_home'] ?? 'Home ID missing.');
|
||||
$this->renderIndex($userId, $isAdmin);
|
||||
return;
|
||||
}
|
||||
|
||||
$home = $this->service->getHome($homeId, $userId, $isAdmin);
|
||||
if ($home === null) {
|
||||
print_failure($this->lang['error_home_not_found'] ?? 'Home not found.');
|
||||
$this->renderIndex($userId, $isAdmin);
|
||||
return;
|
||||
}
|
||||
|
||||
$config = $this->service->buildConfigFromRequest($_POST);
|
||||
$this->service->saveConfig($homeId, $config);
|
||||
print_success($this->lang['message_config_saved'] ?? 'Workshop configuration saved.');
|
||||
|
||||
$this->renderEdit($home, $config, $isAdmin);
|
||||
}
|
||||
|
||||
private function handleEdit(int $userId, bool $isAdmin): void
|
||||
{
|
||||
$homeId = isset($_GET['home_id']) ? (int)$_GET['home_id'] : 0;
|
||||
if ($homeId <= 0) {
|
||||
print_failure($this->lang['error_missing_home'] ?? 'Home ID missing.');
|
||||
$this->renderIndex($userId, $isAdmin);
|
||||
return;
|
||||
}
|
||||
|
||||
$home = $this->service->getHome($homeId, $userId, $isAdmin);
|
||||
if ($home === null) {
|
||||
print_failure($this->lang['error_home_not_found'] ?? 'Home not found.');
|
||||
$this->renderIndex($userId, $isAdmin);
|
||||
return;
|
||||
}
|
||||
|
||||
$config = $this->service->loadConfig($homeId);
|
||||
$this->renderEdit($home, $config, $isAdmin);
|
||||
}
|
||||
|
||||
private function renderIndex(int $userId, bool $isAdmin): void
|
||||
{
|
||||
$records = [];
|
||||
$homes = $this->service->listHomesForUser($userId, $isAdmin);
|
||||
foreach ($homes as $home) {
|
||||
$config = $this->service->loadConfig((int)$home['home_id']);
|
||||
$adapter = $this->service->getAdapterByKey($config['adapter_key']);
|
||||
$records[] = [
|
||||
'home' => $home,
|
||||
'config' => $config,
|
||||
'adapter' => $adapter,
|
||||
];
|
||||
}
|
||||
|
||||
$this->render('index', [
|
||||
'lang' => $this->lang,
|
||||
'records' => $records,
|
||||
'isAdmin' => $isAdmin,
|
||||
'adapterOptions' => $this->service->getAdapterOptions(),
|
||||
]);
|
||||
}
|
||||
|
||||
private function renderEdit(array $home, array $config, bool $isAdmin): void
|
||||
{
|
||||
$this->render('edit', [
|
||||
'lang' => $this->lang,
|
||||
'home' => $home,
|
||||
'config' => $config,
|
||||
'isAdmin' => $isAdmin,
|
||||
'adapterOptions' => $this->service->getAdapterOptions(),
|
||||
]);
|
||||
}
|
||||
|
||||
private function render(string $view, array $data = []): void
|
||||
{
|
||||
extract($data);
|
||||
$lang = $this->lang;
|
||||
require __DIR__ . '/../views/' . $view . '.php';
|
||||
}
|
||||
|
||||
private function loadLang(): array
|
||||
{
|
||||
$langFile = __DIR__ . '/../lang/en_US.php';
|
||||
if (is_file($langFile)) {
|
||||
$strings = require $langFile;
|
||||
if (is_array($strings)) {
|
||||
return $strings;
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
45
modules/steam_workshop/lang/en_US.php
Normal file
45
modules/steam_workshop/lang/en_US.php
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
return [
|
||||
'heading_overview' => 'Steam Workshop automation (preview)',
|
||||
'heading_edit_home' => 'Edit Workshop settings for %s',
|
||||
'button_edit' => 'Configure',
|
||||
'button_create_config' => 'Create config',
|
||||
'button_save' => 'Save settings',
|
||||
'button_cancel' => 'Back to list',
|
||||
'label_feature_flag' => 'Enable scheduled Workshop updates for this server',
|
||||
'label_adapter' => 'Game adapter',
|
||||
'label_interval' => 'Update interval (minutes)',
|
||||
'label_interval_hint' => 'Runs on the agent scheduler. Allowed range: 15–360 minutes.',
|
||||
'label_staging_dir' => 'Staging directory (optional)',
|
||||
'label_install_strategy' => 'Install strategy',
|
||||
'label_on_update_action' => 'On update action',
|
||||
'label_post_install_script' => 'Post-install script (absolute path)',
|
||||
'label_mod_import' => 'Workshop IDs list (one "id,@ModName" per line)',
|
||||
'hint_mod_import' => 'Paste from Modlist.txt or import from a collection. IDs are sanitized automatically.',
|
||||
'status_enabled' => 'Enabled',
|
||||
'status_disabled' => 'Disabled',
|
||||
'status_hot_reload' => 'Hot reload ready',
|
||||
'status_restart_required' => 'Restart required',
|
||||
'empty_state_admin' => 'No game homes are assigned yet. Use "Assign Game Homes" to continue.',
|
||||
'empty_state_user' => 'No servers available. Ask an administrator to assign a game home.',
|
||||
'message_config_saved' => 'Workshop settings saved.',
|
||||
'error_missing_home' => 'Select a server before editing Workshop settings.',
|
||||
'error_home_not_found' => 'Unable to locate that server or you do not have permission.',
|
||||
'mods_table_heading' => 'Parsed Workshop entries',
|
||||
'mods_table_empty' => 'No Workshop IDs have been added yet.',
|
||||
'mods_header_id' => 'Workshop ID',
|
||||
'mods_header_label' => 'Label',
|
||||
'mods_header_source' => 'Source',
|
||||
'mods_header_enabled' => 'Enabled',
|
||||
'install_copy' => 'Copy files into the server directory',
|
||||
'install_symlink' => 'Symlink from Steam workshop content',
|
||||
'install_staging' => 'Download to staging only (manual apply)',
|
||||
'action_queue_for_restart' => 'Queue for restart',
|
||||
'action_hot_reload_if_supported' => 'Hot reload if the adapter allows it',
|
||||
'summary_adapter' => 'Adapter',
|
||||
'summary_interval' => 'Interval',
|
||||
'summary_mods' => 'Mods',
|
||||
'summary_last_saved' => 'Last saved',
|
||||
'summary_hot_reload' => 'Hot reload',
|
||||
'raw_definition_label' => 'Raw Workshop list',
|
||||
];
|
||||
16
modules/steam_workshop/lib/GameAdapters/ark.xml
Normal file
16
modules/steam_workshop/lib/GameAdapters/ark.xml
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<adapter xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="schema.xsd"
|
||||
key="ark"
|
||||
name="ARK: Survival Ascended"
|
||||
supportsHotReload="false">
|
||||
<steamAppId>346110</steamAppId>
|
||||
<modsDir>%SERVER_ROOT%/ShooterGame/Content/Mods</modsDir>
|
||||
<activation type="config_writer">
|
||||
<template>GameUserSettings.ini:[ServerSettings].ActiveMods={modlist(',')}</template>
|
||||
</activation>
|
||||
<postCopyRules>
|
||||
<copyPattern source="*.mod" destination="%SERVER_ROOT%/ShooterGame/Content/Mods" />
|
||||
</postCopyRules>
|
||||
<notes>Copies .mod stubs alongside mod folders and rewrites ActiveMods inside GameUserSettings.ini.</notes>
|
||||
</adapter>
|
||||
17
modules/steam_workshop/lib/GameAdapters/arma3.xml
Normal file
17
modules/steam_workshop/lib/GameAdapters/arma3.xml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<adapter xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="schema.xsd"
|
||||
key="arma3"
|
||||
name="Arma 3"
|
||||
supportsHotReload="false">
|
||||
<steamAppId>107410</steamAppId>
|
||||
<modsDir>%SERVER_ROOT%/arma3server/mods</modsDir>
|
||||
<keysDir>%SERVER_ROOT%/keys</keysDir>
|
||||
<activation type="launch_parameter">
|
||||
<template>-mod=@{modlist(';')}</template>
|
||||
</activation>
|
||||
<postCopyRules>
|
||||
<copyPattern source="*.bikey" destination="%SERVER_ROOT%/keys" />
|
||||
</postCopyRules>
|
||||
<notes>Matches Bohemia best practices: copy .bikey files and keep @Mod folders under the server root.</notes>
|
||||
</adapter>
|
||||
14
modules/steam_workshop/lib/GameAdapters/cs2.xml
Normal file
14
modules/steam_workshop/lib/GameAdapters/cs2.xml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<adapter xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="schema.xsd"
|
||||
key="cs2"
|
||||
name="Counter-Strike 2"
|
||||
supportsHotReload="true">
|
||||
<steamAppId>730</steamAppId>
|
||||
<modsDir>%SERVER_ROOT%/game/csgo/workshop</modsDir>
|
||||
<activation type="console_command">
|
||||
<template>host_workshop_map {id}</template>
|
||||
</activation>
|
||||
<hotReloadCommand>host_workshop_map {id}</hotReloadCommand>
|
||||
<notes>Focuses on map workshop IDs and can hot-reload via `host_workshop_map` when the server allows it.</notes>
|
||||
</adapter>
|
||||
17
modules/steam_workshop/lib/GameAdapters/dayz.xml
Normal file
17
modules/steam_workshop/lib/GameAdapters/dayz.xml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<adapter xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="schema.xsd"
|
||||
key="dayz"
|
||||
name="DayZ"
|
||||
supportsHotReload="false">
|
||||
<steamAppId>221100</steamAppId>
|
||||
<modsDir>%SERVER_ROOT%/WorkshopMods</modsDir>
|
||||
<keysDir>%SERVER_ROOT%/keys</keysDir>
|
||||
<activation type="launch_parameter">
|
||||
<template>-mod=@{modlist(';')}</template>
|
||||
</activation>
|
||||
<postCopyRules>
|
||||
<copyPattern source="*.bikey" destination="%SERVER_ROOT%/keys" />
|
||||
</postCopyRules>
|
||||
<notes>Copies .bikey files into the server keys directory and builds a classic -mod parameter string.</notes>
|
||||
</adapter>
|
||||
13
modules/steam_workshop/lib/GameAdapters/gmod.xml
Normal file
13
modules/steam_workshop/lib/GameAdapters/gmod.xml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<adapter xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="schema.xsd"
|
||||
key="gmod"
|
||||
name="Garry's Mod"
|
||||
supportsHotReload="false">
|
||||
<steamAppId>4000</steamAppId>
|
||||
<modsDir>%SERVER_ROOT%/garrysmod/addons</modsDir>
|
||||
<activation type="launch_parameter">
|
||||
<template>+host_workshop_collection {collectionId}</template>
|
||||
</activation>
|
||||
<notes>Relies on a Steam Workshop collection. Files stay in the Steam library and only the host_workshop_collection parameter changes.</notes>
|
||||
</adapter>
|
||||
37
modules/steam_workshop/lib/GameAdapters/schema.xsd
Normal file
37
modules/steam_workshop/lib/GameAdapters/schema.xsd
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
<xs:element name="adapter">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="steamAppId" type="xs:string" />
|
||||
<xs:element name="modsDir" type="xs:string" />
|
||||
<xs:element name="keysDir" type="xs:string" minOccurs="0" />
|
||||
<xs:element name="activation" minOccurs="0">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="template" type="xs:string" />
|
||||
</xs:sequence>
|
||||
<xs:attribute name="type" type="xs:string" use="optional" />
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="postCopyRules" minOccurs="0">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="copyPattern" minOccurs="0" maxOccurs="unbounded">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="source" type="xs:string" use="required" />
|
||||
<xs:attribute name="destination" type="xs:string" use="optional" />
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="hotReloadCommand" type="xs:string" minOccurs="0" />
|
||||
<xs:element name="notes" type="xs:string" minOccurs="0" />
|
||||
</xs:sequence>
|
||||
<xs:attribute name="key" type="xs:string" use="required" />
|
||||
<xs:attribute name="name" type="xs:string" use="required" />
|
||||
<xs:attribute name="supportsHotReload" type="xs:boolean" use="optional" />
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:schema>
|
||||
390
modules/steam_workshop/lib/SteamWorkshopService.php
Normal file
390
modules/steam_workshop/lib/SteamWorkshopService.php
Normal file
|
|
@ -0,0 +1,390 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
class SteamWorkshopService
|
||||
{
|
||||
private const MIN_INTERVAL = 15;
|
||||
private const MAX_INTERVAL = 360;
|
||||
|
||||
private OGPDatabase $db;
|
||||
private string $configDir;
|
||||
private string $adapterDir;
|
||||
|
||||
public function __construct(OGPDatabase $db)
|
||||
{
|
||||
$this->db = $db;
|
||||
$this->configDir = __DIR__ . '/../data/configs';
|
||||
$this->adapterDir = __DIR__ . '/GameAdapters';
|
||||
|
||||
if (!is_dir($this->configDir)) {
|
||||
mkdir($this->configDir, 0775, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all homes visible to the given user.
|
||||
*
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
public function listHomesForUser(int $userId, bool $isAdmin): array
|
||||
{
|
||||
$accessType = $isAdmin ? 'admin' : 'user_and_group';
|
||||
$homes = $this->db->getHomesFor($accessType, $userId);
|
||||
|
||||
if ($homes === false || $homes === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_values($homes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a single home, ensuring the user is allowed to see it.
|
||||
*/
|
||||
public function getHome(int $homeId, int $userId, bool $isAdmin): ?array
|
||||
{
|
||||
$home = $isAdmin
|
||||
? $this->db->getGameHome($homeId)
|
||||
: $this->db->getUserGameHome($userId, $homeId);
|
||||
|
||||
return is_array($home) ? $home : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{
|
||||
* workshop_enabled: bool,
|
||||
* adapter_key: string,
|
||||
* update_interval_minutes: int,
|
||||
* staging_dir: string,
|
||||
* install_strategy: string,
|
||||
* on_update_action: string,
|
||||
* post_install_script: string,
|
||||
* workshop_items: array<int, array<string, mixed>>,
|
||||
* raw_definition: string,
|
||||
* last_saved_at: int|null
|
||||
* }
|
||||
*/
|
||||
public function loadConfig(int $homeId): array
|
||||
{
|
||||
$path = $this->getConfigPath($homeId);
|
||||
|
||||
if (!is_file($path)) {
|
||||
return $this->defaultConfig();
|
||||
}
|
||||
|
||||
$xml = @simplexml_load_file($path);
|
||||
if ($xml === false) {
|
||||
return $this->defaultConfig();
|
||||
}
|
||||
|
||||
$config = $this->defaultConfig();
|
||||
$config['workshop_enabled'] = ((string)($xml->enabled ?? 'false')) === 'true';
|
||||
$config['adapter_key'] = (string)($xml->adapter['key'] ?? $config['adapter_key']);
|
||||
$config['update_interval_minutes'] = $this->sanitizeInterval((int)($xml->updateInterval ?? $config['update_interval_minutes']));
|
||||
$config['staging_dir'] = trim((string)($xml->stagingDir ?? ''));
|
||||
$config['install_strategy'] = (string)($xml->installStrategy ?? $config['install_strategy']);
|
||||
$config['on_update_action'] = (string)($xml->onUpdateAction ?? $config['on_update_action']);
|
||||
$config['post_install_script'] = trim((string)($xml->postInstallScript ?? ''));
|
||||
$config['raw_definition'] = (string)($xml->rawDefinition ?? '');
|
||||
$config['last_saved_at'] = isset($xml->timestamps->savedAt)
|
||||
? (int)$xml->timestamps->savedAt
|
||||
: null;
|
||||
|
||||
$mods = [];
|
||||
if (isset($xml->mods)) {
|
||||
foreach ($xml->mods->mod as $mod) {
|
||||
$mods[] = [
|
||||
'id' => (string)$mod['id'],
|
||||
'label' => (string)$mod['label'],
|
||||
'enabled' => ((string)$mod['enabled']) !== 'false',
|
||||
'source' => (string)($mod['source'] ?? 'manual'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$config['workshop_items'] = $mods;
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
public function saveConfig(int $homeId, array $config): void
|
||||
{
|
||||
$path = $this->getConfigPath($homeId);
|
||||
$config = $this->normalizeConfig($config);
|
||||
$doc = new DOMDocument('1.0', 'UTF-8');
|
||||
$doc->formatOutput = true;
|
||||
|
||||
$root = $doc->createElement('workshop');
|
||||
$doc->appendChild($root);
|
||||
|
||||
$root->appendChild($doc->createElement('enabled', $config['workshop_enabled'] ? 'true' : 'false'));
|
||||
|
||||
$adapterNode = $doc->createElement('adapter');
|
||||
$adapterNode->setAttribute('key', $config['adapter_key']);
|
||||
$root->appendChild($adapterNode);
|
||||
|
||||
$root->appendChild($doc->createElement('updateInterval', (string)$config['update_interval_minutes']));
|
||||
$root->appendChild($doc->createElement('stagingDir', $config['staging_dir']));
|
||||
$root->appendChild($doc->createElement('installStrategy', $config['install_strategy']));
|
||||
$root->appendChild($doc->createElement('onUpdateAction', $config['on_update_action']));
|
||||
$root->appendChild($doc->createElement('postInstallScript', $config['post_install_script']));
|
||||
$root->appendChild($doc->createElement('rawDefinition', $config['raw_definition']));
|
||||
|
||||
$modsNode = $doc->createElement('mods');
|
||||
foreach ($config['workshop_items'] as $item) {
|
||||
$mod = $doc->createElement('mod');
|
||||
$mod->setAttribute('id', (string)$item['id']);
|
||||
$mod->setAttribute('label', (string)$item['label']);
|
||||
$mod->setAttribute('enabled', !empty($item['enabled']) ? 'true' : 'false');
|
||||
$mod->setAttribute('source', (string)($item['source'] ?? 'manual'));
|
||||
$modsNode->appendChild($mod);
|
||||
}
|
||||
$root->appendChild($modsNode);
|
||||
|
||||
$timestampsNode = $doc->createElement('timestamps');
|
||||
$timestampsNode->appendChild($doc->createElement('savedAt', (string)time()));
|
||||
$root->appendChild($timestampsNode);
|
||||
|
||||
$doc->save($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert POST payload into a config array and merge defaults.
|
||||
*/
|
||||
public function buildConfigFromRequest(array $payload): array
|
||||
{
|
||||
$input = $payload['workshop'] ?? [];
|
||||
$rawMods = trim((string)($input['raw_items'] ?? ''));
|
||||
$items = $this->parseWorkshopItems($rawMods);
|
||||
|
||||
return [
|
||||
'workshop_enabled' => isset($input['workshop_enabled']) ? (bool)$input['workshop_enabled'] : false,
|
||||
'adapter_key' => $this->sanitizeAdapterKey((string)($input['adapter_key'] ?? 'dayz')),
|
||||
'update_interval_minutes' => $this->sanitizeInterval(isset($input['update_interval_minutes']) ? (int)$input['update_interval_minutes'] : null),
|
||||
'staging_dir' => trim((string)($input['staging_dir'] ?? '')),
|
||||
'install_strategy' => $this->sanitizeInstallStrategy((string)($input['install_strategy'] ?? 'copy')),
|
||||
'on_update_action' => $this->sanitizeUpdateAction((string)($input['on_update_action'] ?? 'queue_for_restart')),
|
||||
'post_install_script' => trim((string)($input['post_install_script'] ?? '')),
|
||||
'workshop_items' => $items,
|
||||
'raw_definition' => $rawMods,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Accepts imports such as "123456,@My Mod" per line.
|
||||
*
|
||||
* @return array<int, array{id:string,label:string,enabled:bool,source:string}>
|
||||
*/
|
||||
public function parseWorkshopItems(string $raw): array
|
||||
{
|
||||
if ($raw === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$items = [];
|
||||
$lines = preg_split('/\r\n|\r|\n/', $raw);
|
||||
foreach ($lines as $line) {
|
||||
$line = trim($line);
|
||||
if ($line === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$parts = array_map('trim', explode(',', $line, 2));
|
||||
$id = preg_replace('/[^0-9]/', '', $parts[0]);
|
||||
if ($id === '') {
|
||||
continue;
|
||||
}
|
||||
$label = $parts[1] ?? '';
|
||||
if ($label === '') {
|
||||
$label = '@' . $id;
|
||||
}
|
||||
|
||||
$items[] = [
|
||||
'id' => $id,
|
||||
'label' => $label,
|
||||
'enabled' => true,
|
||||
'source' => 'manual',
|
||||
];
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a SteamCMD command array for a single workshop item.
|
||||
*/
|
||||
public function buildSteamCmdArgs(array $config, string $workshopId, ?string $login = null): array
|
||||
{
|
||||
$loginUser = $login !== null && $login !== '' ? $login : 'anonymous';
|
||||
$adapter = $this->getAdapterByKey($config['adapter_key'] ?? '');
|
||||
$appId = $adapter['steam_app_id'] ?? ($config['steam_app_id'] ?? '');
|
||||
|
||||
return [
|
||||
'+login', $loginUser,
|
||||
'+workshop_download_item', $appId,
|
||||
$workshopId,
|
||||
'validate',
|
||||
];
|
||||
}
|
||||
|
||||
public function getAdapterOptions(): array
|
||||
{
|
||||
$options = [];
|
||||
foreach ($this->loadAdapters() as $adapter) {
|
||||
$options[$adapter['key']] = $adapter['name'];
|
||||
}
|
||||
|
||||
if (empty($options)) {
|
||||
$options['dayz'] = 'DayZ (fallback)';
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load adapter metadata for UI and validation.
|
||||
*
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
public function loadAdapters(): array
|
||||
{
|
||||
$adapters = [];
|
||||
$schema = $this->adapterDir . '/schema.xsd';
|
||||
$useSchema = is_file($schema);
|
||||
$previousLibxml = libxml_use_internal_errors(true);
|
||||
|
||||
foreach (glob($this->adapterDir . '/*.xml') as $file) {
|
||||
if (substr($file, -4) !== '.xml') {
|
||||
continue;
|
||||
}
|
||||
if (basename($file) === 'schema.xsd') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$doc = new DOMDocument();
|
||||
if (!$doc->load($file)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($useSchema && !$doc->schemaValidate($schema)) {
|
||||
libxml_clear_errors();
|
||||
continue;
|
||||
}
|
||||
|
||||
$adapter = simplexml_import_dom($doc);
|
||||
if ($adapter === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$adapters[] = [
|
||||
'key' => (string)($adapter['key'] ?? ''),
|
||||
'name' => (string)($adapter['name'] ?? ''),
|
||||
'steam_app_id' => (string)($adapter->steamAppId ?? ''),
|
||||
'mods_dir' => (string)($adapter->modsDir ?? ''),
|
||||
'keys_dir' => isset($adapter->keysDir) ? (string)$adapter->keysDir : null,
|
||||
'supports_hot_reload' => filter_var((string)($adapter->supportsHotReload ?? 'false'), FILTER_VALIDATE_BOOLEAN),
|
||||
'activation_template' => (string)($adapter->activation->template ?? ''),
|
||||
'notes' => (string)($adapter->notes ?? ''),
|
||||
];
|
||||
}
|
||||
|
||||
$result = array_values(array_filter($adapters, static function (array $adapter): bool {
|
||||
return $adapter['key'] !== '';
|
||||
}));
|
||||
|
||||
libxml_use_internal_errors($previousLibxml);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getAdapterByKey(string $key): array
|
||||
{
|
||||
foreach ($this->loadAdapters() as $adapter) {
|
||||
if ($adapter['key'] === $key) {
|
||||
return $adapter;
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
private function sanitizeInterval(?int $minutes): int
|
||||
{
|
||||
if ($minutes === null || $minutes <= 0) {
|
||||
$minutes = 60;
|
||||
}
|
||||
|
||||
return max(self::MIN_INTERVAL, min(self::MAX_INTERVAL, $minutes));
|
||||
}
|
||||
|
||||
private function sanitizeAdapterKey(string $key): string
|
||||
{
|
||||
$key = strtolower(trim($key));
|
||||
if ($key === '') {
|
||||
return 'dayz';
|
||||
}
|
||||
|
||||
$adapters = $this->getAdapterOptions();
|
||||
if (array_key_exists($key, $adapters)) {
|
||||
return $key;
|
||||
}
|
||||
|
||||
$adapterKeys = array_keys($adapters);
|
||||
return $adapterKeys[0] ?? 'dayz';
|
||||
}
|
||||
|
||||
private function sanitizeInstallStrategy(string $strategy): string
|
||||
{
|
||||
$valid = ['copy', 'symlink', 'staging'];
|
||||
return in_array($strategy, $valid, true) ? $strategy : 'copy';
|
||||
}
|
||||
|
||||
private function sanitizeUpdateAction(string $action): string
|
||||
{
|
||||
$valid = ['queue_for_restart', 'hot_reload_if_supported'];
|
||||
return in_array($action, $valid, true) ? $action : 'queue_for_restart';
|
||||
}
|
||||
|
||||
private function normalizeConfig(array $config): array
|
||||
{
|
||||
$config = array_merge($this->defaultConfig(), $config);
|
||||
$config['update_interval_minutes'] = $this->sanitizeInterval((int)$config['update_interval_minutes']);
|
||||
$config['adapter_key'] = $this->sanitizeAdapterKey((string)$config['adapter_key']);
|
||||
$config['install_strategy'] = $this->sanitizeInstallStrategy((string)$config['install_strategy']);
|
||||
$config['on_update_action'] = $this->sanitizeUpdateAction((string)$config['on_update_action']);
|
||||
$config['workshop_items'] = array_map(static function (array $item): array {
|
||||
$item['id'] = preg_replace('/[^0-9]/', '', (string)($item['id'] ?? ''));
|
||||
$item['label'] = trim((string)($item['label'] ?? ''));
|
||||
$item['enabled'] = !empty($item['enabled']);
|
||||
$item['source'] = $item['source'] ?? 'manual';
|
||||
return $item;
|
||||
}, $config['workshop_items']);
|
||||
|
||||
$config['workshop_items'] = array_values(array_filter($config['workshop_items'], static function (array $item): bool {
|
||||
return $item['id'] !== '';
|
||||
}));
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
private function getConfigPath(int $homeId): string
|
||||
{
|
||||
return sprintf('%s/%d.xml', $this->configDir, $homeId);
|
||||
}
|
||||
|
||||
private function defaultConfig(): array
|
||||
{
|
||||
return [
|
||||
'workshop_enabled' => false,
|
||||
'adapter_key' => 'dayz',
|
||||
'update_interval_minutes' => 60,
|
||||
'staging_dir' => '',
|
||||
'install_strategy' => 'copy',
|
||||
'on_update_action' => 'queue_for_restart',
|
||||
'post_install_script' => '',
|
||||
'workshop_items' => [],
|
||||
'raw_definition' => '',
|
||||
'last_saved_at' => null,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -1,332 +1,17 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
/*
|
||||
*
|
||||
* OGP - Open Game Panel
|
||||
* Copyright (C) 2008 - 2018 The OGP Development Team
|
||||
*
|
||||
* http://www.opengamepanel.org/
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*
|
||||
* Steam Workshop module entrypoint.
|
||||
*/
|
||||
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/form_table_class.php');
|
||||
echo '<link rel="stylesheet" type="text/css" href="css/xbbcode/xbbcode.css">'."\n".
|
||||
'<script type="text/javascript" src="js/xbbcode/xbbcode.js"></script>'."\n".
|
||||
'<script type="text/javascript" src="js/modules/steam_workshop.js"></script>';
|
||||
require_once __DIR__ . '/controllers/SteamWorkshopController.php';
|
||||
|
||||
function exec_ogp_module()
|
||||
function exec_ogp_module(): void
|
||||
{
|
||||
|
||||
Global $db,$view,$settings;
|
||||
echo '<h2>Steam Workshop</h2>';
|
||||
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
|
||||
{
|
||||
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 "<ul>".
|
||||
"<li><a href='?m=steam_workshop&p=uninstall&home_id-mod_id-ip-port=".$_GET['home_id-mod_id-ip-port']."'>".get_lang('uninstall_mods')."</a></li>".
|
||||
"<li><a href='?m=gamemanager&p=game_monitor&home_id-mod_id-ip-port=".$_GET['home_id-mod_id-ip-port']."'>".get_lang('back')."</a></li>".
|
||||
"</ul>";
|
||||
}
|
||||
|
||||
$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 "<p class='note'>". get_lang("update_in_progress") ."</p>\n";
|
||||
echo "<pre>".$log_txt."</pre>\n</script>\n<div id='dialog' ></div>\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 "<p><a href=\"?m=steam_workshop&p=main&home_id-mod_id-ip-port=".$_GET['home_id-mod_id-ip-port']."&show_log\">";
|
||||
echo get_lang("refresh_steam_workshop_status") ."</a></p>";
|
||||
$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 "<pre>".$log_txt."</pre>\n";
|
||||
echo "<table class='center'><tr><td><a href='?m=steam_workshop&p=main&home_id-mod_id-ip-port=".$_GET['home_id-mod_id-ip-port']."'><< ". get_lang("back") ."</a></td></tr></table>";
|
||||
}
|
||||
}
|
||||
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/>');
|
||||
$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 "<table>";
|
||||
foreach($xml->mods->mod as $mod)
|
||||
{
|
||||
if(in_array($mod['id'],$mod_id_array))
|
||||
{
|
||||
echo "<tr><td><h4>".$mod->name."</h4>".
|
||||
"<div><img width='240px' style='float:left;' src='".$mod->image_url."'>".
|
||||
"<div class='bbcode_container' style='padding-left:245px;'>".htmlentities(base64_decode($mod->description))."</div></div><td><tr>";
|
||||
}
|
||||
}
|
||||
echo "</table><a href='?m=steam_workshop&p=main&home_id-mod_id-ip-port=".$_GET['home_id-mod_id-ip-port']."'>".get_lang('back')."</a>";
|
||||
}
|
||||
}
|
||||
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 '<tr><td colspan=2><div id="uninstall_scrolling_checkbox">';
|
||||
foreach($xml->mods->mod as $mod)
|
||||
echo "<input type='checkbox' id='select_mod_$mod[id]' name='workshop_mod_id[]' value='$mod[id]'><label for='select_mod_$mod[id]'>".$mod->name."</label><br>";
|
||||
echo '</div></td></tr>';
|
||||
}
|
||||
$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
|
||||
{
|
||||
print_failure(get_lang('game_home_not_found'));
|
||||
return;
|
||||
}
|
||||
global $db;
|
||||
|
||||
echo '<h2>' . get_lang('steam_workshop') . '</h2>';
|
||||
|
||||
$controller = new SteamWorkshopController($db);
|
||||
$controller->handle();
|
||||
}
|
||||
?>
|
||||
|
|
|
|||
|
|
@ -23,8 +23,14 @@
|
|||
*/
|
||||
// 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' ));
|
||||
$module_menus = array(
|
||||
array(
|
||||
'subpage' => 'main',
|
||||
'name' => 'Steam Workshop',
|
||||
'group' => 'admin'
|
||||
)
|
||||
);
|
||||
?>
|
||||
|
|
@ -22,22 +22,31 @@
|
|||
*
|
||||
*/
|
||||
|
||||
if(isset($server_xml->installer) and $server_xml->installer == "steamcmd")
|
||||
if (isset($server_xml->installer) && $server_xml->installer === "steamcmd")
|
||||
{
|
||||
$mod_xml = xml_get_mod($server_xml, $server_home['mod_key']);
|
||||
require_once("modules/steam_workshop/functions.php");
|
||||
if(isset($mod_xml->installer_name) and !in_array((string)$mod_xml->installer_name, get_blacklist()))
|
||||
$homeId = isset($server_home['home_id']) ? (int)$server_home['home_id'] : 0;
|
||||
if ($homeId > 0)
|
||||
{
|
||||
$label = get_lang('steam_workshop');
|
||||
if ($label === 'steam_workshop')
|
||||
{
|
||||
$label = 'Steam Workshop';
|
||||
}
|
||||
$href = "?m=steam_workshop&p=main&action=edit&home_id=" . $homeId;
|
||||
$module_buttons = array(
|
||||
"<a class='monitorbutton' href='?m=steam_workshop&p=main&home_id-mod_id-ip-port=".$server_home['home_id']."-".$server_home['mod_id']."-".$server_home['ip']."-".$server_home['port']."'>
|
||||
<img src='" . check_theme_image("images/steam_workshop.png") . "' title='Steam Workshop'>
|
||||
<span>Steam Workshop</span>
|
||||
"<a class='monitorbutton' href='" . $href . "'>
|
||||
<img src='" . check_theme_image("images/steam_workshop.png") . "' title='" . $label . "'>
|
||||
<span>" . $label . "</span>
|
||||
</a>"
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
$module_buttons = array();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$module_buttons = array();
|
||||
}
|
||||
?>
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
<navigation>
|
||||
<page key="main" file="main.php" access="user,admin" />
|
||||
<page key="uninstall" file="uninstall.php" access="user,admin" />
|
||||
<page key="workshop_admin" file="workshop_admin.php" access="admin" />
|
||||
<page key="workshop_admin" file="main.php" access="admin" />
|
||||
</navigation>
|
||||
|
|
@ -15,3 +15,111 @@
|
|||
overflow-y: scroll;
|
||||
text-align:left;
|
||||
}
|
||||
|
||||
.sw-admin {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.sw-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.sw-card {
|
||||
border: 1px solid #dcdcdc;
|
||||
border-radius: 6px;
|
||||
padding: 1rem;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.sw-card__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.sw-card__meta {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
|
||||
gap: 0.75rem;
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
.sw-card__meta dt {
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.sw-card__meta dd {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.sw-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.sw-form__grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.sw-form__actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.sw-form label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.sw-form input,
|
||||
.sw-form select,
|
||||
.sw-form textarea {
|
||||
padding: 0.4rem;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #c7c7c7;
|
||||
}
|
||||
|
||||
.sw-mods__table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.sw-mods__table th,
|
||||
.sw-mods__table td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 0.4rem;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: 0.4rem 0.8rem;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #888;
|
||||
text-decoration: none;
|
||||
color: #222;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.btn.primary {
|
||||
background: #0b5ed7;
|
||||
border-color: #0a58ca;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.sw-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.sw-toggle input {
|
||||
width: auto;
|
||||
}
|
||||
|
|
|
|||
25
modules/steam_workshop/views/edit.php
Normal file
25
modules/steam_workshop/views/edit.php
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
/** @var array $home */
|
||||
/** @var array $config */
|
||||
/** @var array $lang */
|
||||
/** @var array $adapterOptions */
|
||||
$homeName = htmlspecialchars($home['home_name'] ?? ('#' . $home['home_id']));
|
||||
$homeId = (int)$home['home_id'];
|
||||
?>
|
||||
<div class="sw-admin sw-edit">
|
||||
<p><a href="?m=steam_workshop&p=main">← <?php echo htmlspecialchars($lang['button_cancel']); ?></a></p>
|
||||
|
||||
<h3><?php echo htmlspecialchars(sprintf($lang['heading_edit_home'], $homeName)); ?></h3>
|
||||
|
||||
<form method="post" action="?m=steam_workshop&p=main&action=save" class="sw-form">
|
||||
<input type="hidden" name="home_id" value="<?php echo $homeId; ?>" />
|
||||
<?php $formConfig = $config; include __DIR__ . '/partials/form_fields.php'; ?>
|
||||
<div class="sw-form__actions">
|
||||
<button class="btn primary" type="submit"><?php echo htmlspecialchars($lang['button_save']); ?></button>
|
||||
<a class="btn" href="?m=steam_workshop&p=main"><?php echo htmlspecialchars($lang['button_cancel']); ?></a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<?php include __DIR__ . '/partials/mod_table.php'; ?>
|
||||
</div>
|
||||
20
modules/steam_workshop/views/index.php
Normal file
20
modules/steam_workshop/views/index.php
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
/** @var array $records */
|
||||
/** @var array $lang */
|
||||
/** @var bool $isAdmin */
|
||||
/** @var array $adapterOptions */
|
||||
?>
|
||||
<div class="sw-admin sw-index">
|
||||
<?php if (empty($records)): ?>
|
||||
<div class="sw-empty">
|
||||
<p><?php echo $isAdmin ? htmlspecialchars($lang['empty_state_admin']) : htmlspecialchars($lang['empty_state_user']); ?></p>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="sw-grid">
|
||||
<?php foreach ($records as $record): ?>
|
||||
<?php $currentRecord = $record; include __DIR__ . '/partials/server_card.php'; ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
69
modules/steam_workshop/views/partials/form_fields.php
Normal file
69
modules/steam_workshop/views/partials/form_fields.php
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
/** @var array $formConfig */
|
||||
/** @var array $adapterOptions */
|
||||
/** @var array $lang */
|
||||
$enabled = !empty($formConfig['workshop_enabled']);
|
||||
$interval = (int)$formConfig['update_interval_minutes'];
|
||||
$stagingDir = htmlspecialchars($formConfig['staging_dir']);
|
||||
$postInstall = htmlspecialchars($formConfig['post_install_script']);
|
||||
$rawDefinition = htmlspecialchars($formConfig['raw_definition']);
|
||||
$installStrategy = $formConfig['install_strategy'];
|
||||
$onUpdateAction = $formConfig['on_update_action'];
|
||||
?>
|
||||
<div class="sw-form__grid">
|
||||
<label class="sw-toggle">
|
||||
<input type="checkbox" name="workshop[workshop_enabled]" value="1" <?php echo $enabled ? 'checked' : ''; ?> />
|
||||
<span><?php echo htmlspecialchars($lang['label_feature_flag']); ?></span>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<span><?php echo htmlspecialchars($lang['label_adapter']); ?></span>
|
||||
<select name="workshop[adapter_key]">
|
||||
<?php foreach ($adapterOptions as $key => $label): ?>
|
||||
<option value="<?php echo htmlspecialchars($key); ?>" <?php echo $formConfig['adapter_key'] === $key ? 'selected' : ''; ?>>
|
||||
<?php echo htmlspecialchars($label); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<span><?php echo htmlspecialchars($lang['label_interval']); ?></span>
|
||||
<input type="number" min="15" max="360" step="5" name="workshop[update_interval_minutes]" value="<?php echo $interval; ?>" />
|
||||
<small><?php echo htmlspecialchars($lang['label_interval_hint']); ?></small>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<span><?php echo htmlspecialchars($lang['label_staging_dir']); ?></span>
|
||||
<input type="text" name="workshop[staging_dir]" value="<?php echo $stagingDir; ?>" placeholder="/home/ogp_agent/workshop-staging" />
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<span><?php echo htmlspecialchars($lang['label_install_strategy']); ?></span>
|
||||
<select name="workshop[install_strategy]">
|
||||
<option value="copy" <?php echo $installStrategy === 'copy' ? 'selected' : ''; ?>><?php echo htmlspecialchars($lang['install_copy']); ?></option>
|
||||
<option value="symlink" <?php echo $installStrategy === 'symlink' ? 'selected' : ''; ?>><?php echo htmlspecialchars($lang['install_symlink']); ?></option>
|
||||
<option value="staging" <?php echo $installStrategy === 'staging' ? 'selected' : ''; ?>><?php echo htmlspecialchars($lang['install_staging']); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<span><?php echo htmlspecialchars($lang['label_on_update_action']); ?></span>
|
||||
<select name="workshop[on_update_action]">
|
||||
<option value="queue_for_restart" <?php echo $onUpdateAction === 'queue_for_restart' ? 'selected' : ''; ?>><?php echo htmlspecialchars($lang['action_queue_for_restart']); ?></option>
|
||||
<option value="hot_reload_if_supported" <?php echo $onUpdateAction === 'hot_reload_if_supported' ? 'selected' : ''; ?>><?php echo htmlspecialchars($lang['action_hot_reload_if_supported']); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<span><?php echo htmlspecialchars($lang['label_post_install_script']); ?></span>
|
||||
<input type="text" name="workshop[post_install_script]" value="<?php echo $postInstall; ?>" placeholder="/home/ogp_agent/scripts/workshop-hook.sh" />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label>
|
||||
<span><?php echo htmlspecialchars($lang['label_mod_import']); ?></span>
|
||||
<textarea name="workshop[raw_items]" rows="8" placeholder="123456789,@Example Mod 987654321,@QoL Pack"><?php echo $rawDefinition; ?></textarea>
|
||||
<small><?php echo htmlspecialchars($lang['hint_mod_import']); ?></small>
|
||||
</label>
|
||||
33
modules/steam_workshop/views/partials/mod_table.php
Normal file
33
modules/steam_workshop/views/partials/mod_table.php
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
/** @var array $config */
|
||||
/** @var array $lang */
|
||||
$mods = $config['workshop_items'] ?? [];
|
||||
?>
|
||||
<div class="sw-mods">
|
||||
<h4><?php echo htmlspecialchars($lang['mods_table_heading']); ?></h4>
|
||||
<?php if (empty($mods)): ?>
|
||||
<p><?php echo htmlspecialchars($lang['mods_table_empty']); ?></p>
|
||||
<?php else: ?>
|
||||
<table class="table sw-mods__table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?php echo htmlspecialchars($lang['mods_header_id']); ?></th>
|
||||
<th><?php echo htmlspecialchars($lang['mods_header_label']); ?></th>
|
||||
<th><?php echo htmlspecialchars($lang['mods_header_source']); ?></th>
|
||||
<th><?php echo htmlspecialchars($lang['mods_header_enabled']); ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($mods as $mod): ?>
|
||||
<tr>
|
||||
<td><?php echo htmlspecialchars($mod['id']); ?></td>
|
||||
<td><?php echo htmlspecialchars($mod['label']); ?></td>
|
||||
<td><?php echo htmlspecialchars($mod['source']); ?></td>
|
||||
<td><?php echo !empty($mod['enabled']) ? htmlspecialchars($lang['status_enabled']) : htmlspecialchars($lang['status_disabled']); ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
63
modules/steam_workshop/views/partials/server_card.php
Normal file
63
modules/steam_workshop/views/partials/server_card.php
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
/** @var array $currentRecord */
|
||||
/** @var array $lang */
|
||||
$home = $currentRecord['home'];
|
||||
$config = $currentRecord['config'];
|
||||
$adapter = $currentRecord['adapter'];
|
||||
$homeName = htmlspecialchars($home['home_name'] ?? ('#' . $home['home_id']));
|
||||
$homeId = (int)($home['home_id'] ?? 0);
|
||||
$modCount = count($config['workshop_items']);
|
||||
$interval = (int)$config['update_interval_minutes'];
|
||||
$enabled = !empty($config['workshop_enabled']);
|
||||
$lastSaved = $config['last_saved_at'] ? date('Y-m-d H:i', (int)$config['last_saved_at']) : '—';
|
||||
$adapterName = htmlspecialchars($adapter['name'] ?? strtoupper($config['adapter_key']));
|
||||
$hotReload = !empty($adapter['supports_hot_reload']);
|
||||
$ip = htmlspecialchars($home['ip'] ?? '');
|
||||
$port = $home['port'] ?? '';
|
||||
$address = $ip;
|
||||
if ($ip !== '' && $port !== '') {
|
||||
$address .= ':' . htmlspecialchars((string)$port);
|
||||
}
|
||||
?>
|
||||
<div class="sw-card">
|
||||
<div class="sw-card__header">
|
||||
<div>
|
||||
<h3><?php echo $homeName; ?></h3>
|
||||
<?php if ($address !== ''): ?>
|
||||
<p><?php echo $address; ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div>
|
||||
<a class="btn" href="?m=steam_workshop&p=main&action=edit&home_id=<?php echo $homeId; ?>">
|
||||
<?php echo htmlspecialchars($lang['button_edit']); ?>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<dl class="sw-card__meta">
|
||||
<div>
|
||||
<dt><?php echo htmlspecialchars($lang['summary_adapter']); ?></dt>
|
||||
<dd><?php echo $adapterName; ?></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt><?php echo htmlspecialchars($lang['summary_interval']); ?></dt>
|
||||
<dd><?php echo $interval; ?> min</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt><?php echo htmlspecialchars($lang['summary_mods']); ?></dt>
|
||||
<dd><?php echo $modCount; ?></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt><?php echo htmlspecialchars($lang['summary_last_saved']); ?></dt>
|
||||
<dd><?php echo htmlspecialchars($lastSaved); ?></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Status</dt>
|
||||
<dd><?php echo $enabled ? htmlspecialchars($lang['status_enabled']) : htmlspecialchars($lang['status_disabled']); ?></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt><?php echo htmlspecialchars($lang['summary_hot_reload']); ?></dt>
|
||||
<dd><?php echo $hotReload ? htmlspecialchars($lang['status_hot_reload']) : htmlspecialchars($lang['status_restart_required']); ?></dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
Loading…
Add table
Add a link
Reference in a new issue