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 ''; $this->gameGroups = $this->service->listWorkshopGameGroups(); if ($_SERVER['REQUEST_METHOD'] === 'POST') { $this->processPost(); } $mappings = $this->service->getAdapterMappings(); $adapters = $this->service->loadAdapters(); $adapterOptions = $this->service->getAdapterOptions(); $gameRows = $this->buildGameRows($mappings); $requestedGame = $this->sanitizeGameKeyInput($_GET['adapter_game'] ?? ''); $activeGame = $this->adapterFormGameKey !== null ? $this->adapterFormGameKey : $requestedGame; $this->render('admin/index', [ 'lang' => $this->lang, 'mappings' => $mappings, 'adapters' => $adapters, 'adapterOptions' => $adapterOptions, 'gameRows' => $gameRows, 'activeGameKey' => $activeGame, ]); } private function processPost(): void { $action = $_POST['admin_action'] ?? 'save_mappings'; switch ($action) { case 'save_adapter': $this->processAdapterSave(); break; case 'delete_adapter': $this->processAdapterDelete(); break; case 'save_mappings': default: $this->processSaveMappings(); break; } } private function processSaveMappings(): void { $payload = $_POST['mapping'] ?? []; if (!is_array($payload)) { $payload = []; } $fanOut = []; $groupIndex = $this->indexGameGroups(); foreach ((array)$payload as $groupKey => $adapterKey) { $groupKey = (string)$groupKey; $adapterKey = (string)$adapterKey; if (!isset($groupIndex[$groupKey])) { continue; } foreach ((array)$groupIndex[$groupKey] as $gameKey) { $fanOut[$gameKey] = $adapterKey; } } $this->service->saveAdapterMappings($fanOut); print_success($this->lang['message_mappings_saved'] ?? 'Adapter mappings saved.'); } private function processAdapterSave(): void { $gameKey = $this->sanitizeGameKeyInput($_POST['game_key'] ?? ''); if ($gameKey === '') { print_failure($this->lang['error_game_key_required'] ?? 'Game key required.'); return; } $payload = $_POST['adapter'] ?? []; if (!is_array($payload)) { $payload = []; } try { $this->service->saveGameAdapter($gameKey, $payload); $this->propagateAdapterMapping($gameKey); print_success($this->lang['message_adapter_saved'] ?? 'Adapter saved.'); $this->adapterFormOverride = null; $this->adapterFormGameKey = null; } catch (RuntimeException $e) { $this->adapterFormGameKey = $gameKey; $this->adapterFormOverride = [ 'name' => trim((string)($payload['name'] ?? '')), 'steam_app_id' => trim((string)($payload['steam_app_id'] ?? '')), 'mods_dir' => trim((string)($payload['mods_dir'] ?? '')), 'keys_dir' => trim((string)($payload['keys_dir'] ?? '')), 'supports_hot_reload' => !empty($payload['supports_hot_reload']), 'activation_template' => trim((string)($payload['activation_template'] ?? '')), 'notes' => trim((string)($payload['notes'] ?? '')), ]; print_failure($e->getMessage()); } } private function processAdapterDelete(): void { $gameKey = $this->sanitizeGameKeyInput($_POST['game_key'] ?? ''); if ($gameKey === '') { print_failure($this->lang['error_game_key_required'] ?? 'Game key required.'); return; } if ($this->service->deleteGameAdapter($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 $mappings): array { $rows = []; foreach ($this->gameGroups as $group) { $primaryKey = isset($group['primary_game_key']) ? (string)$group['primary_game_key'] : ''; $override = ($primaryKey !== '' && $this->adapterFormGameKey === $primaryKey) ? $this->adapterFormOverride : null; $mappingValues = []; foreach ((array)$group['game_keys'] as $gameKey) { if (isset($mappings[$gameKey]) && $mappings[$gameKey] !== '') { $mappingValues[$mappings[$gameKey]] = true; } } $rows[] = [ '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((array)$mappingValues) > 1, 'selected_adapter' => count((array)$mappingValues) === 1 ? array_key_first($mappingValues) : '', 'exists' => $primaryKey !== '' && $this->service->gameAdapterExists($primaryKey), 'adapter' => $primaryKey !== '' ? $this->service->getGameAdapter($primaryKey) : null, 'updated_at' => $primaryKey !== '' ? $this->service->getGameAdapterUpdatedAt($primaryKey) : null, 'form' => $this->service->getAdapterFormData($primaryKey, $override), ]; } return $rows; } private function indexGameGroups(): array { $index = []; foreach ($this->gameGroups as $group) { $index[$group['group_key']] = $group['game_keys']; } return $index; } private function propagateAdapterMapping(string $primaryGameKey): void { foreach ($this->gameGroups as $group) { if (!in_array($primaryGameKey, $group['game_keys'], true)) { continue; } foreach ((array)$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 ((array)$group['game_keys'] as $gameKey) { $this->service->removeAdapterMapping($gameKey, $primaryGameKey); } return; } $this->service->removeAdapterMapping($primaryGameKey, $primaryGameKey); } private function sanitizeGameKeyInput($value): string { $gameKey = strtolower(trim((string)$value)); $sanitized = preg_replace('/[^a-z0-9_\-.]/', '', $gameKey); return is_string($sanitized) ? $sanitized : ''; } 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 []; } }