- module.php: bump db_version to 2; full v2 schema in install_queries[0]; ALTER TABLE migration in install_queries[2] adding 11 new profile columns plus server_workshop_settings table
- WorkshopRepository: expand saveProfile() with all new fields (steam_app_id, steamcmd_login_mode, steamcmd_path, mod_separator, copy_keys, key paths, pre/post scripts, validation_notes); add nullOrStr helper; add server settings CRUD (getServerSettings, saveServerSettings, recordUpdateResult, setUpdateQueued, listQueuedUpdateHomes); update insertOrUpdateMod with custom_folder; update listAllEnabledMods to select new columns
- WorkshopProfileController: full extractProfileData() with all new fields; validateProfileData with folder template check
- views/admin/profile_form.php: full rewrite – all required fields (steam_app_id, workshop_app_id, login mode, SteamCMD path, OS, cache path, install path, folder naming dropdown, launch params, mod separator, copy method, copy keys with JS show/hide, key paths, pre/per-mod/post bash scripts with DayZ example, validation notes, config file template)
- views/admin/profiles.php: updated list view columns (App IDs, Login, Method)
- WorkshopInstaller.php: full rewrite with %var% template support (+ legacy {var} compat); buildTemplateVars() resolves all 14 variables; pre/post script execution; triggerSteamCmdDownload uses new profile fields; copyKeys helper
- WorkshopModController: add save_settings and queue_update POST actions; handleModsPage loads serverSettings + allProfiles
- views/user_workshop_mods.php: full rewrite – server settings form (enable, profile selector, update mode, restart behavior), update status grid (status/error/time/success time), queue update button, mod table with custom_folder column and toggle/order auto-submit
- lang/en_US.php: ~80 new string keys for all v2 fields
- steam_workshop.css: new v2 styles (3-col grid, script textarea, status grid, server settings card, badges)"
Agent-Logs-Url: https://github.com/GameServerPanel/GSP/sessions/e7f0d80d-f775-4794-adbd-cf48b55bc9c1
Co-authored-by: iaretechnician <2749183+iaretechnician@users.noreply.github.com>
267 lines
10 KiB
PHP
267 lines
10 KiB
PHP
<?php
|
||
declare(strict_types=1);
|
||
/*
|
||
* OGP / GSP – Steam Workshop
|
||
* WorkshopProfileController: admin CRUD for Workshop game profiles
|
||
* (gsp_workshop_game_profiles table).
|
||
*
|
||
* Routed via workshop_admin.php:
|
||
* ?m=steam_workshop&p=workshop_admin&sw_action=profiles → list
|
||
* ?m=steam_workshop&p=workshop_admin&sw_action=profile_form → create/edit
|
||
* POST sw_action=profile_save → save
|
||
* POST sw_action=profile_delete → delete
|
||
*/
|
||
|
||
require_once __DIR__ . '/../lib/WorkshopRepository.php';
|
||
|
||
class WorkshopProfileController
|
||
{
|
||
private WorkshopRepository $repo;
|
||
private array $lang;
|
||
|
||
public function __construct(OGPDatabase $db)
|
||
{
|
||
$this->repo = new WorkshopRepository($db);
|
||
$this->lang = $this->loadLang();
|
||
}
|
||
|
||
// ------------------------------------------------------------------
|
||
// Dispatch
|
||
// ------------------------------------------------------------------
|
||
|
||
public function handle(): void
|
||
{
|
||
global $db;
|
||
|
||
$userId = (int)($_SESSION['user_id'] ?? 0);
|
||
if (!$db->isAdmin($userId)) {
|
||
print_failure($this->lang['error_admin_only'] ?? 'Administrator access required.');
|
||
return;
|
||
}
|
||
|
||
echo '<link rel="stylesheet" type="text/css" href="modules/steam_workshop/steam_workshop.css" />';
|
||
|
||
$action = $_GET['sw_action'] ?? 'list';
|
||
|
||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||
$postAction = $_POST['sw_action'] ?? '';
|
||
switch ($postAction) {
|
||
case 'profile_save':
|
||
$this->handleSave();
|
||
return;
|
||
case 'profile_delete':
|
||
$this->handleDelete();
|
||
return;
|
||
}
|
||
}
|
||
|
||
switch ($action) {
|
||
case 'config_form':
|
||
case 'profile_form':
|
||
$this->handleForm((int)($_GET['profile_id'] ?? 0));
|
||
break;
|
||
default:
|
||
$this->handleList();
|
||
break;
|
||
}
|
||
}
|
||
|
||
// ------------------------------------------------------------------
|
||
// Actions
|
||
// ------------------------------------------------------------------
|
||
|
||
private function handleList(): void
|
||
{
|
||
$profiles = $this->repo->listProfiles();
|
||
$this->render('admin/profiles', [
|
||
'lang' => $this->lang,
|
||
'profiles' => $profiles,
|
||
]);
|
||
}
|
||
|
||
private function handleForm(int $profileId): void
|
||
{
|
||
$profile = $profileId > 0 ? $this->repo->getProfileById($profileId) : null;
|
||
$this->render('admin/profile_form', [
|
||
'lang' => $this->lang,
|
||
'profile' => $profile,
|
||
'profileId' => $profileId,
|
||
]);
|
||
}
|
||
|
||
private function handleSave(): void
|
||
{
|
||
$id = (int)($_POST['profile_id'] ?? 0);
|
||
$data = $this->extractProfileData($_POST);
|
||
|
||
$errors = $this->validateProfileData($data);
|
||
if (!empty($errors)) {
|
||
foreach ($errors as $err) {
|
||
print_failure($err);
|
||
}
|
||
$profile = $id > 0 ? $this->repo->getProfileById($id) : null;
|
||
$this->render('admin/profile_form', [
|
||
'lang' => $this->lang,
|
||
'profile' => array_merge($profile ?? [], $data, ['id' => $id]),
|
||
'profileId' => $id,
|
||
]);
|
||
return;
|
||
}
|
||
|
||
$data['id'] = $id;
|
||
$savedId = $this->repo->saveProfile($data);
|
||
|
||
if ($savedId > 0) {
|
||
print_success($this->lang['profile_saved'] ?? 'Workshop profile saved.');
|
||
} else {
|
||
print_failure($this->lang['profile_save_error'] ?? 'Failed to save Workshop profile.');
|
||
}
|
||
|
||
$this->handleList();
|
||
}
|
||
|
||
private function handleDelete(): void
|
||
{
|
||
$id = (int)($_POST['profile_id'] ?? 0);
|
||
if ($id <= 0) {
|
||
print_failure($this->lang['profile_not_found'] ?? 'Profile not found.');
|
||
$this->handleList();
|
||
return;
|
||
}
|
||
|
||
if ($this->repo->deleteProfile($id)) {
|
||
print_success($this->lang['profile_deleted'] ?? 'Workshop profile deleted.');
|
||
} else {
|
||
print_failure($this->lang['profile_delete_error'] ?? 'Failed to delete Workshop profile.');
|
||
}
|
||
|
||
$this->handleList();
|
||
}
|
||
|
||
// ------------------------------------------------------------------
|
||
// Input helpers
|
||
// ------------------------------------------------------------------
|
||
|
||
/**
|
||
* @param array<string,mixed> $post
|
||
* @return array<string,mixed>
|
||
*/
|
||
private function extractProfileData(array $post): array
|
||
{
|
||
// supported_os can be multiple values (SET type)
|
||
$osRaw = $post['supported_os'] ?? [];
|
||
if (!is_array($osRaw)) {
|
||
$osRaw = [$osRaw];
|
||
}
|
||
$allowedOs = ['linux', 'windows'];
|
||
$osValues = array_values(array_intersect($osRaw, $allowedOs));
|
||
$supportedOs = implode(',', $osValues !== [] ? $osValues : ['linux']);
|
||
|
||
$allowedCopyMethods = ['copy', 'rsync', 'symlink'];
|
||
$copyMethod = in_array($post['copy_method'] ?? '', $allowedCopyMethods, true)
|
||
? (string)$post['copy_method']
|
||
: 'rsync';
|
||
|
||
$allowedLoginModes = ['anonymous', 'account'];
|
||
$steamcmdLoginMode = in_array($post['steamcmd_login_mode'] ?? '', $allowedLoginModes, true)
|
||
? (string)$post['steamcmd_login_mode']
|
||
: 'anonymous';
|
||
|
||
$allowedFolderFormats = ['@%mod_name%', '@%workshop_id%', 'custom'];
|
||
$folderNamingFormat = in_array($post['folder_naming_format'] ?? '', $allowedFolderFormats, true)
|
||
? (string)$post['folder_naming_format']
|
||
: '@%workshop_id%';
|
||
|
||
$allowedSeparators = ['semicolon', 'comma', 'space'];
|
||
$modSeparator = in_array($post['mod_separator'] ?? '', $allowedSeparators, true)
|
||
? (string)$post['mod_separator']
|
||
: 'semicolon';
|
||
|
||
// When folder naming is preset (@%mod_name% or @%workshop_id%), derive template from format.
|
||
// When 'custom', use the admin-supplied value.
|
||
$folderNameTemplate = $folderNamingFormat !== 'custom'
|
||
? $folderNamingFormat
|
||
: trim((string)($post['folder_name_template'] ?? '@%workshop_id%'));
|
||
|
||
return [
|
||
'game_key' => trim((string)($post['game_key'] ?? '')),
|
||
'game_name' => trim((string)($post['game_name'] ?? '')),
|
||
'steam_app_id' => preg_replace('/[^0-9]/', '', (string)($post['steam_app_id'] ?? '')) ?? '',
|
||
'workshop_app_id' => preg_replace('/[^0-9]/', '', (string)($post['workshop_app_id'] ?? '')) ?? '',
|
||
'steam_login_required' => !empty($post['steam_login_required']) ? 1 : 0,
|
||
'steamcmd_login_mode' => $steamcmdLoginMode,
|
||
'steamcmd_path' => trim((string)($post['steamcmd_path'] ?? '')),
|
||
'supported_os' => $supportedOs,
|
||
'cache_path_template' => trim((string)($post['cache_path_template'] ?? '')),
|
||
'install_path_template' => trim((string)($post['install_path_template'] ?? '')),
|
||
'folder_naming_format' => $folderNamingFormat,
|
||
'folder_name_template' => $folderNameTemplate,
|
||
'mod_launch_param' => trim((string)($post['mod_launch_param'] ?? '')),
|
||
'mod_separator' => $modSeparator,
|
||
'copy_method' => $copyMethod,
|
||
'copy_keys' => !empty($post['copy_keys']) ? 1 : 0,
|
||
'key_source_path' => trim((string)($post['key_source_path'] ?? '')),
|
||
'key_dest_path' => trim((string)($post['key_dest_path'] ?? '')),
|
||
'pre_update_script' => trim((string)($post['pre_update_script'] ?? '')),
|
||
'install_script' => trim((string)($post['install_script'] ?? '')),
|
||
'post_update_script' => trim((string)($post['post_update_script'] ?? '')),
|
||
'config_file_template' => trim((string)($post['config_file_template'] ?? '')),
|
||
'launch_param_template' => trim((string)($post['launch_param_template'] ?? '')),
|
||
'requires_restart' => !empty($post['requires_restart']) ? 1 : 0,
|
||
'validation_notes' => trim((string)($post['validation_notes'] ?? '')),
|
||
'enabled' => !empty($post['enabled']) ? 1 : 0,
|
||
];
|
||
}
|
||
|
||
/**
|
||
* @param array<string,mixed> $data
|
||
* @return list<string>
|
||
*/
|
||
private function validateProfileData(array $data): array
|
||
{
|
||
$errors = [];
|
||
if (($data['game_key'] ?? '') === '') {
|
||
$errors[] = $this->lang['error_game_key_required'] ?? 'Game key is required.';
|
||
} elseif (!preg_match('/^[a-z0-9_\-.]+$/i', (string)$data['game_key'])) {
|
||
$errors[] = $this->lang['error_game_key_invalid'] ?? 'Game key may only contain letters, digits, underscores, dots, and hyphens.';
|
||
}
|
||
if (($data['game_name'] ?? '') === '') {
|
||
$errors[] = $this->lang['error_game_name_required'] ?? 'Game name is required.';
|
||
}
|
||
if (($data['workshop_app_id'] ?? '') === '') {
|
||
$errors[] = $this->lang['error_app_id_required'] ?? 'Workshop App ID is required.';
|
||
}
|
||
if (($data['cache_path_template'] ?? '') === '') {
|
||
$errors[] = $this->lang['error_cache_path_required'] ?? 'SteamCMD cache path template is required.';
|
||
}
|
||
if (($data['install_path_template'] ?? '') === '') {
|
||
$errors[] = $this->lang['error_install_path_required'] ?? 'Server install path template is required.';
|
||
}
|
||
if (($data['folder_naming_format'] ?? '') === 'custom' && ($data['folder_name_template'] ?? '') === '') {
|
||
$errors[] = $this->lang['error_folder_template_required'] ?? 'Custom folder name template is required when format is set to custom.';
|
||
}
|
||
return $errors;
|
||
}
|
||
|
||
// ------------------------------------------------------------------
|
||
// Rendering
|
||
// ------------------------------------------------------------------
|
||
|
||
private function render(string $view, array $data = []): void
|
||
{
|
||
extract($data);
|
||
require __DIR__ . '/../views/' . $view . '.php';
|
||
}
|
||
|
||
private function loadLang(): array
|
||
{
|
||
$file = __DIR__ . '/../lang/en_US.php';
|
||
if (is_file($file)) {
|
||
$strings = require $file;
|
||
if (is_array($strings)) {
|
||
return $strings;
|
||
}
|
||
}
|
||
return [];
|
||
}
|
||
}
|