From b2b46b23dbd04c69e309912e6eb2cd532f7d6f10 Mon Sep 17 00:00:00 2001 From: Frank Harris Date: Sat, 17 Jan 2026 10:18:59 -0600 Subject: [PATCH] updated steam --- .../controllers/AdminWorkshopController.php | 107 +++++-- .../lib/SteamWorkshopService.php | 86 +++++- modules/steam_workshop/steam_workshop.css | 104 +++++++ modules/steam_workshop/views/admin/index.php | 292 ++++++++++-------- 4 files changed, 426 insertions(+), 163 deletions(-) diff --git a/modules/steam_workshop/controllers/AdminWorkshopController.php b/modules/steam_workshop/controllers/AdminWorkshopController.php index 8f75fdcc..ec604505 100644 --- a/modules/steam_workshop/controllers/AdminWorkshopController.php +++ b/modules/steam_workshop/controllers/AdminWorkshopController.php @@ -9,6 +9,7 @@ class AdminWorkshopController private array $lang; private ?array $adapterFormOverride = null; private ?string $adapterFormGameKey = null; + private array $gameGroups = []; public function __construct(OGPDatabase $db) { @@ -28,29 +29,25 @@ class AdminWorkshopController echo ''; + $this->gameGroups = $this->service->listWorkshopGameGroups(); + if ($_SERVER['REQUEST_METHOD'] === 'POST') { $this->processPost(); } - $gameKeys = $this->service->listAvailableGameKeys(); $mappings = $this->service->getAdapterMappings(); $adapters = $this->service->loadAdapters(); $adapterOptions = $this->service->getAdapterOptions(); - $gameRows = $this->buildGameRows($gameKeys); - - $activeGame = $this->resolveActiveGameKey(); - $adapterForm = $activeGame !== '' - ? $this->service->getAdapterFormData($activeGame, $this->adapterFormOverride) - : null; + $gameRows = $this->buildGameRows($mappings); + $requestedGame = $this->sanitizeGameKeyInput($_GET['adapter_game'] ?? ''); + $activeGame = $this->adapterFormGameKey !== null ? $this->adapterFormGameKey : $requestedGame; $this->render('admin/index', [ 'lang' => $this->lang, - 'gameKeys' => $gameKeys, 'mappings' => $mappings, 'adapters' => $adapters, 'adapterOptions' => $adapterOptions, 'gameRows' => $gameRows, - 'adapterForm' => $adapterForm, 'activeGameKey' => $activeGame, ]); } @@ -78,7 +75,22 @@ class AdminWorkshopController if (!is_array($payload)) { $payload = []; } - $this->service->saveAdapterMappings($payload); + + $fanOut = []; + $groupIndex = $this->indexGameGroups(); + foreach ($payload as $groupKey => $adapterKey) { + $groupKey = (string)$groupKey; + $adapterKey = (string)$adapterKey; + if (!isset($groupIndex[$groupKey])) { + continue; + } + + foreach ($groupIndex[$groupKey] as $gameKey) { + $fanOut[$gameKey] = $adapterKey; + } + } + + $this->service->saveAdapterMappings($fanOut); print_success($this->lang['message_mappings_saved'] ?? 'Adapter mappings saved.'); } @@ -97,7 +109,7 @@ class AdminWorkshopController try { $this->service->saveGameAdapter($gameKey, $payload); - $this->service->upsertAdapterMapping($gameKey, $gameKey); + $this->propagateAdapterMapping($gameKey); print_success($this->lang['message_adapter_saved'] ?? 'Adapter saved.'); $this->adapterFormOverride = null; $this->adapterFormGameKey = null; @@ -125,36 +137,85 @@ class AdminWorkshopController } if ($this->service->deleteGameAdapter($gameKey)) { - $this->service->removeAdapterMapping($gameKey, $gameKey); + $this->clearGroupMappings($gameKey); print_success($this->lang['message_adapter_deleted'] ?? 'Adapter deleted.'); } else { print_failure($this->lang['error_adapter_delete_failed'] ?? 'Unable to delete adapter.'); } } - private function buildGameRows(array $gameKeys): array + private function buildGameRows(array $mappings): array { $rows = []; - foreach ($gameKeys as $gameKey) { + foreach ($this->gameGroups as $group) { + $primaryKey = $group['primary_game_key']; + $override = ($this->adapterFormGameKey === $primaryKey) ? $this->adapterFormOverride : null; + + $mappingValues = []; + foreach ($group['game_keys'] as $gameKey) { + if (isset($mappings[$gameKey]) && $mappings[$gameKey] !== '') { + $mappingValues[$mappings[$gameKey]] = true; + } + } + $rows[] = [ - 'game_key' => $gameKey, - 'exists' => $this->service->gameAdapterExists($gameKey), - 'adapter' => $this->service->getGameAdapter($gameKey), - 'updated_at' => $this->service->getGameAdapterUpdatedAt($gameKey), + 'group_key' => $group['group_key'], + 'app_id' => $group['app_id'], + 'game_name' => $group['game_name'], + 'game_keys' => $group['game_keys'], + 'primary_game_key' => $primaryKey, + 'mixed_mapping' => count($mappingValues) > 1, + 'selected_adapter' => count($mappingValues) === 1 ? array_key_first($mappingValues) : '', + 'exists' => $this->service->gameAdapterExists($primaryKey), + 'adapter' => $this->service->getGameAdapter($primaryKey), + 'updated_at' => $this->service->getGameAdapterUpdatedAt($primaryKey), + 'form' => $this->service->getAdapterFormData($primaryKey, $override), ]; } return $rows; } - private function resolveActiveGameKey(): string + private function indexGameGroups(): array { - if ($this->adapterFormGameKey !== null) { - return $this->adapterFormGameKey; + $index = []; + foreach ($this->gameGroups as $group) { + $index[$group['group_key']] = $group['game_keys']; } - $queryKey = $_GET['adapter_game'] ?? ''; - return $this->sanitizeGameKeyInput($queryKey); + return $index; + } + + private function propagateAdapterMapping(string $primaryGameKey): void + { + foreach ($this->gameGroups as $group) { + if (!in_array($primaryGameKey, $group['game_keys'], true)) { + continue; + } + + foreach ($group['game_keys'] as $gameKey) { + $this->service->upsertAdapterMapping($gameKey, $primaryGameKey); + } + return; + } + + $this->service->upsertAdapterMapping($primaryGameKey, $primaryGameKey); + } + + private function clearGroupMappings(string $primaryGameKey): void + { + foreach ($this->gameGroups as $group) { + if (!in_array($primaryGameKey, $group['game_keys'], true)) { + continue; + } + + foreach ($group['game_keys'] as $gameKey) { + $this->service->removeAdapterMapping($gameKey, $primaryGameKey); + } + return; + } + + $this->service->removeAdapterMapping($primaryGameKey, $primaryGameKey); } private function sanitizeGameKeyInput($value): string diff --git a/modules/steam_workshop/lib/SteamWorkshopService.php b/modules/steam_workshop/lib/SteamWorkshopService.php index 5c803d3e..2773a1be 100644 --- a/modules/steam_workshop/lib/SteamWorkshopService.php +++ b/modules/steam_workshop/lib/SteamWorkshopService.php @@ -497,23 +497,63 @@ class SteamWorkshopService return unlink($path); } - public function listAvailableGameKeys(): array + public function listWorkshopGameGroups(): array { - $keys = []; $configDir = defined('SERVER_CONFIG_LOCATION') ? SERVER_CONFIG_LOCATION : __DIR__ . '/../../config_games/server_configs'; + $groups = []; + 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); + + $gameKey = isset($xml->game_key) ? trim((string)$xml->game_key) : ''; + if ($gameKey === '') { + continue; } + + $appId = $this->parseSteamAppIdFromConfig($xml); + if ($appId === null) { + continue; + } + + $groupKey = $this->buildWorkshopGroupKey($appId); + if (!isset($groups[$groupKey])) { + $gameName = isset($xml->game_name) ? trim((string)$xml->game_name) : ''; + $groups[$groupKey] = [ + 'group_key' => $groupKey, + 'app_id' => $appId, + 'game_name' => $gameName !== '' ? $gameName : $gameKey, + 'game_keys' => [], + ]; + } + + $groups[$groupKey]['game_keys'][] = $gameKey; } - $keys = array_filter(array_unique($keys)); - sort($keys); - return array_values($keys); + foreach ($groups as &$group) { + $group['game_keys'] = array_values(array_unique($group['game_keys'])); + sort($group['game_keys']); + $group['primary_game_key'] = $group['game_keys'][0]; + } + unset($group); + + usort($groups, static function (array $a, array $b): int { + return strcmp($a['game_name'], $b['game_name']); + }); + + return array_values($groups); + } + + public function listAvailableGameKeys(): array + { + $keys = []; + foreach ($this->listWorkshopGameGroups() as $group) { + $keys = array_merge($keys, $group['game_keys']); + } + + return array_values(array_unique($keys)); } private function sanitizeInterval(?int $minutes): int @@ -676,4 +716,36 @@ class SteamWorkshopService file_put_contents($this->adapterMapFile, json_encode([])); } } + private function parseSteamAppIdFromConfig($xml): ?string + { + if (!isset($xml->mods) || !isset($xml->mods->mod)) { + return null; + } + + $candidate = null; + foreach ($xml->mods->mod as $mod) { + $installerName = trim((string)($mod->installer_name ?? '')); + if ($installerName === '' || preg_match('/\D/', $installerName)) { + continue; + } + + $modName = strtolower(trim((string)($mod->name ?? ''))); + $modKey = strtolower(trim((string)($mod['key'] ?? ''))); + + if ($modKey === 'default' || $modName === 'none' || $modName === '') { + return $installerName; + } + + if ($candidate === null) { + $candidate = $installerName; + } + } + + return $candidate; + } + + private function buildWorkshopGroupKey(string $appId): string + { + return 'steamapp_' . $appId; + } } diff --git a/modules/steam_workshop/steam_workshop.css b/modules/steam_workshop/steam_workshop.css index c8167c2e..89c0ac9d 100644 --- a/modules/steam_workshop/steam_workshop.css +++ b/modules/steam_workshop/steam_workshop.css @@ -123,3 +123,107 @@ .sw-toggle input { width: auto; } + +.sw-game-table__wrapper { + margin-top: 1rem; + overflow-x: auto; +} + +.sw-game-table__row td { + vertical-align: top; +} + +.sw-game-label { + display: flex; + flex-direction: column; + gap: 0.35rem; +} + +.sw-game-label__title { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.4rem; +} + +.sw-game-label__key { + font-weight: 600; +} + +.sw-game-label__name { + font-weight: 600; +} + +.sw-game-label__hint { + color: #666; +} + +.sw-game-label__hint--warning { + color: #b15a00; +} + +.sw-badge { + display: inline-flex; + align-items: center; + padding: 0.1rem 0.5rem; + border-radius: 999px; + font-size: 0.75rem; +} + +.sw-badge--custom { + background: #eef4ff; + color: #0b5ed7; +} + +.sw-badge--app { + background: #f1f3f5; + color: #444; +} + +.sw-game-variants { + display: flex; + flex-wrap: wrap; + gap: 0.25rem; +} + +.sw-chip { + background: #f4f6f8; + border-radius: 999px; + padding: 0.1rem 0.55rem; + font-size: 0.8rem; +} + +.sw-actions { + display: flex; + flex-wrap: wrap; + gap: 0.35rem; +} + +.sw-inline-delete { + display: inline; +} + +.sw-game-table__form-row { + display: none; +} + +.sw-game-table__form-row.is-open { + display: table-row; +} + +.sw-inline-form { + background: #f6f8fb; + border: 1px solid #d0dae9; + border-radius: 6px; + padding: 1rem; +} + +.sw-checkbox { + display: flex; + align-items: center; + gap: 0.4rem; +} + +.sw-admin__mapping-actions { + margin-top: 1rem; +} diff --git a/modules/steam_workshop/views/admin/index.php b/modules/steam_workshop/views/admin/index.php index cee66915..7081577b 100644 --- a/modules/steam_workshop/views/admin/index.php +++ b/modules/steam_workshop/views/admin/index.php @@ -1,169 +1,162 @@
-

-

+
+

+

+
-
+ - + + +
+
- + + + + - + - + - - - + + + + + + + + + +
- + + + + + + + + + — + + + + +
+ + + +
+ +
+
+ + + +
+ + + + + + +
+ + + + + +
+ + +
+
-
- -
- +
-

-

- - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - — - - - - - - -
- - - -
- -
- -
- -

-
- - - -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- -
- -
- - -
- -
- - -
- -
- - -
-
- -

- +
+

@@ -192,3 +185,36 @@ declare(strict_types=1);
+ +