diff --git a/modules/steam_workshop/controllers/AdminWorkshopController.php b/modules/steam_workshop/controllers/AdminWorkshopController.php new file mode 100644 index 00000000..031006ac --- /dev/null +++ b/modules/steam_workshop/controllers/AdminWorkshopController.php @@ -0,0 +1,76 @@ +service = new SteamWorkshopService($db); + $this->lang = $this->loadLang(); + } + + public function handle(): void + { + global $db; + + $userId = (int)($_SESSION['user_id'] ?? 0); + if (!$db->isAdmin($userId)) { + print_failure($this->lang['error_admin_only'] ?? 'Admin access required.'); + return; + } + + echo ''; + + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $this->processSave(); + } + + $gameKeys = $this->service->listAvailableGameKeys(); + $mappings = $this->service->getAdapterMappings(); + $adapters = $this->service->loadAdapters(); + $adapterOptions = $this->service->getAdapterOptions(); + + $this->render('admin/index', [ + 'lang' => $this->lang, + 'gameKeys' => $gameKeys, + 'mappings' => $mappings, + 'adapters' => $adapters, + 'adapterOptions' => $adapterOptions, + ]); + } + + private function processSave(): void + { + $payload = $_POST['mapping'] ?? []; + if (!is_array($payload)) { + $payload = []; + } + $this->service->saveAdapterMappings($payload); + print_success($this->lang['message_mappings_saved'] ?? 'Adapter mappings saved.'); + } + + 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 []; + } +} diff --git a/modules/steam_workshop/controllers/SteamWorkshopController.php b/modules/steam_workshop/controllers/SteamWorkshopController.php index 9d27216f..de3c2707 100644 --- a/modules/steam_workshop/controllers/SteamWorkshopController.php +++ b/modules/steam_workshop/controllers/SteamWorkshopController.php @@ -54,10 +54,11 @@ class SteamWorkshopController } $config = $this->service->buildConfigFromRequest($_POST); + $adapterLocked = $this->applyGameAdapterOverride($home, $config); $this->service->saveConfig($homeId, $config); print_success($this->lang['message_config_saved'] ?? 'Workshop configuration saved.'); - $this->renderEdit($home, $config, $isAdmin); + $this->renderEdit($home, $config, $isAdmin, $adapterLocked); } private function handleEdit(int $userId, bool $isAdmin): void @@ -77,7 +78,8 @@ class SteamWorkshopController } $config = $this->service->loadConfig($homeId); - $this->renderEdit($home, $config, $isAdmin); + $adapterLocked = $this->applyGameAdapterOverride($home, $config); + $this->renderEdit($home, $config, $isAdmin, $adapterLocked); } private function renderIndex(int $userId, bool $isAdmin): void @@ -86,6 +88,7 @@ class SteamWorkshopController $homes = $this->service->listHomesForUser($userId, $isAdmin); foreach ($homes as $home) { $config = $this->service->loadConfig((int)$home['home_id']); + $this->applyGameAdapterOverride($home, $config); $adapter = $this->service->getAdapterByKey($config['adapter_key']); $records[] = [ 'home' => $home, @@ -102,7 +105,7 @@ class SteamWorkshopController ]); } - private function renderEdit(array $home, array $config, bool $isAdmin): void + private function renderEdit(array $home, array $config, bool $isAdmin, bool $adapterLocked): void { $this->render('edit', [ 'lang' => $this->lang, @@ -110,9 +113,22 @@ class SteamWorkshopController 'config' => $config, 'isAdmin' => $isAdmin, 'adapterOptions' => $this->service->getAdapterOptions(), + 'adapterLocked' => $adapterLocked, ]); } + private function applyGameAdapterOverride(array $home, array &$config): bool + { + $gameKey = isset($home['game_key']) ? (string)$home['game_key'] : ''; + $mapped = $this->service->getAdapterKeyForGame($gameKey); + if ($mapped !== null && $mapped !== '') { + $config['adapter_key'] = $mapped; + return true; + } + + return false; + } + private function render(string $view, array $data = []): void { extract($data); diff --git a/modules/steam_workshop/lang/en_US.php b/modules/steam_workshop/lang/en_US.php index 0de88db5..db506d1e 100644 --- a/modules/steam_workshop/lang/en_US.php +++ b/modules/steam_workshop/lang/en_US.php @@ -16,6 +16,7 @@ return [ '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.', + 'adapter_locked_note' => 'This adapter is enforced for the current game type by your administrator.', 'status_enabled' => 'Enabled', 'status_disabled' => 'Disabled', 'status_hot_reload' => 'Hot reload ready', diff --git a/modules/steam_workshop/lib/SteamWorkshopService.php b/modules/steam_workshop/lib/SteamWorkshopService.php index 166e4d72..d7002765 100644 --- a/modules/steam_workshop/lib/SteamWorkshopService.php +++ b/modules/steam_workshop/lib/SteamWorkshopService.php @@ -9,16 +9,20 @@ class SteamWorkshopService private OGPDatabase $db; private string $configDir; private string $adapterDir; + private string $adapterMapFile; public function __construct(OGPDatabase $db) { $this->db = $db; $this->configDir = __DIR__ . '/../data/configs'; $this->adapterDir = __DIR__ . '/GameAdapters'; + $this->adapterMapFile = __DIR__ . '/../data/game_adapter_map.json'; if (!is_dir($this->configDir)) { mkdir($this->configDir, 0775, true); } + + $this->ensureDataFiles(); } /** @@ -308,6 +312,89 @@ class SteamWorkshopService return []; } + /** + * Return adapter key chosen for the given game key, or null if unmapped. + */ + public function getAdapterKeyForGame(string $gameKey): ?string + { + $gameKey = trim($gameKey); + if ($gameKey === '') { + return null; + } + + $map = $this->getAdapterMappings(); + return $map[$gameKey] ?? null; + } + + /** + * Persist adapter mappings (game_key => adapter_key). + */ + public function saveAdapterMappings(array $mappings): void + { + $sanitized = []; + $options = $this->getAdapterOptions(); + foreach ($mappings as $gameKey => $adapterKey) { + $gameKey = trim((string)$gameKey); + $adapterKey = $this->sanitizeAdapterKey((string)$adapterKey); + if ($gameKey === '' || !isset($options[$adapterKey])) { + continue; + } + $sanitized[$gameKey] = $adapterKey; + } + + file_put_contents($this->adapterMapFile, json_encode($sanitized, JSON_PRETTY_PRINT)); + } + + /** + * @return array + */ + public function getAdapterMappings(): array + { + if (!is_file($this->adapterMapFile)) { + return []; + } + + $raw = file_get_contents($this->adapterMapFile); + $decoded = json_decode((string)$raw, true); + if (!is_array($decoded)) { + return []; + } + + $result = []; + foreach ($decoded as $gameKey => $adapterKey) { + if (!is_string($gameKey) || !is_string($adapterKey)) { + continue; + } + $result[$gameKey] = $adapterKey; + } + + return $result; + } + + /** + * Discover available game keys from server config XMLs. + * + * @return array + */ + public function listAvailableGameKeys(): array + { + $keys = []; + $configDir = defined('SERVER_CONFIG_LOCATION') ? SERVER_CONFIG_LOCATION : __DIR__ . '/../../config_games/server_configs'; + foreach (glob($configDir . '/*.xml') as $file) { + $xml = @simplexml_load_file($file); + if ($xml === false) { + continue; + } + if (isset($xml->game_key)) { + $keys[] = trim((string)$xml->game_key); + } + } + + $keys = array_filter(array_unique($keys)); + sort($keys); + return array_values($keys); + } + private function sanitizeInterval(?int $minutes): int { if ($minutes === null || $minutes <= 0) { @@ -387,4 +474,16 @@ class SteamWorkshopService 'last_saved_at' => null, ]; } + + private function ensureDataFiles(): void + { + $dir = dirname($this->adapterMapFile); + if (!is_dir($dir)) { + mkdir($dir, 0775, true); + } + + if (!is_file($this->adapterMapFile)) { + file_put_contents($this->adapterMapFile, json_encode([])); + } + } } diff --git a/modules/steam_workshop/views/admin/index.php b/modules/steam_workshop/views/admin/index.php new file mode 100644 index 00000000..a45849fb --- /dev/null +++ b/modules/steam_workshop/views/admin/index.php @@ -0,0 +1,75 @@ + +
+

+

+ +
+ + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+
+ +

+ + + + + + + + + + + + + + + + + + + + + + + +
Steam App ID
+
diff --git a/modules/steam_workshop/views/partials/form_fields.php b/modules/steam_workshop/views/partials/form_fields.php index 5c9c1c76..0b0539c0 100644 --- a/modules/steam_workshop/views/partials/form_fields.php +++ b/modules/steam_workshop/views/partials/form_fields.php @@ -3,6 +3,7 @@ declare(strict_types=1); /** @var array $formConfig */ /** @var array $adapterOptions */ /** @var array $lang */ +/** @var bool $adapterLocked */ $enabled = !empty($formConfig['workshop_enabled']); $interval = (int)$formConfig['update_interval_minutes']; $stagingDir = htmlspecialchars($formConfig['staging_dir']); @@ -10,6 +11,7 @@ $postInstall = htmlspecialchars($formConfig['post_install_script']); $rawDefinition = htmlspecialchars($formConfig['raw_definition']); $installStrategy = $formConfig['install_strategy']; $onUpdateAction = $formConfig['on_update_action']; +$currentAdapterName = $adapterOptions[$formConfig['adapter_key']] ?? strtoupper($formConfig['adapter_key']); ?>