pdo = $pdo; $this->importService = new PlayniteImportService($pdo); $this->auth = $auth; } /** * Show the import form */ public function showImport(Request $request, Response $response, $args) { return $this->view->render($response, 'admin/playnite/import.twig', [ 'title' => 'Import Playnite Games', 'csrf_token' => $this->generateCSRFToken() ]); } /** * Handle file upload and preview */ public function upload(Request $request, Response $response, $args) { $data = $request->getParsedBody(); $csrfToken = $data['csrf_token'] ?? ''; // Verify CSRF token if (!$this->verifyCSRFToken($csrfToken)) { return $this->view->render($response->withStatus(400), 'admin/playnite/import.twig', [ 'title' => 'Import Playnite Games', 'error' => 'Invalid CSRF token', 'csrf_token' => $this->generateCSRFToken() ]); } $uploadedFiles = $request->getUploadedFiles(); if (empty($uploadedFiles['playnite_file'])) { return $this->view->render($response->withStatus(400), 'admin/playnite/import.twig', [ 'title' => 'Import Playnite Games', 'error' => 'No file uploaded', 'csrf_token' => $this->generateCSRFToken() ]); } $file = $uploadedFiles['playnite_file']; // Validate file if ($file->getError() !== UPLOAD_ERR_OK) { return $this->view->render($response->withStatus(400), 'admin/playnite/import.twig', [ 'title' => 'Import Playnite Games', 'error' => 'Upload error: ' . $this->getUploadErrorMessage($file->getError()), 'csrf_token' => $this->generateCSRFToken() ]); } // Check file type $filename = $file->getClientFilename(); $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); if (!in_array($extension, ['json'])) { return $this->view->render($response->withStatus(400), 'admin/playnite/import.twig', [ 'title' => 'Import Playnite Games', 'error' => 'Only JSON files are supported', 'csrf_token' => $this->generateCSRFToken() ]); } // Move uploaded file to temp location $tempPath = sys_get_temp_dir() . '/playnite_import_' . uniqid() . '.json'; $file->moveTo($tempPath); try { // Parse and validate the file $result = $this->importService->parsePlayniteFile($tempPath); // Store the temp file path and results in session for the confirmation step $_SESSION['playnite_import'] = [ 'temp_file' => $tempPath, 'preview_data' => $result ]; return $this->view->render($response, 'admin/playnite/preview.twig', [ 'title' => 'Preview Playnite Import', 'preview' => $result, 'filename' => $filename ]); } catch (\Exception $e) { // Clean up temp file if (file_exists($tempPath)) { unlink($tempPath); } return $this->view->render($response->withStatus(400), 'admin/playnite/import.twig', [ 'title' => 'Import Playnite Games', 'error' => 'Error parsing file: ' . $e->getMessage(), 'csrf_token' => $this->generateCSRFToken() ]); } } /** * Confirm and execute the import */ public function confirm(Request $request, Response $response, $args) { if (!isset($_SESSION['playnite_import'])) { return $response->withRedirect($this->getRoutePath('admin.playnite.import')); } $importData = $_SESSION['playnite_import']; $tempPath = $importData['temp_file']; $previewData = $importData['preview_data']; // Get import options from form $queryParams = $request->getQueryParams(); $updateExisting = ($queryParams['update_existing'] ?? 'false') === 'true'; try { // Execute the import $importResult = $this->importService->importGames($previewData['games'], $updateExisting); // Clean up temp file if (file_exists($tempPath)) { unlink($tempPath); } // Clear session data unset($_SESSION['playnite_import']); return $this->view->render($response, 'admin/playnite/result.twig', [ 'title' => 'Import Complete', 'import_result' => $importResult, 'preview_data' => $previewData ]); } catch (\Exception $e) { // Clean up temp file if (file_exists($tempPath)) { unlink($tempPath); } // Clear session data unset($_SESSION['playnite_import']); return $this->view->render($response->withStatus(500), 'admin/playnite/import.twig', [ 'title' => 'Import Playnite Games', 'error' => 'Import failed: ' . $e->getMessage(), 'csrf_token' => $this->generateCSRFToken() ]); } } /** * Cancel the import (cleanup) */ public function cancel(Request $request, Response $response, $args) { if (isset($_SESSION['playnite_import'])) { $tempPath = $_SESSION['playnite_import']['temp_file']; if (file_exists($tempPath)) { unlink($tempPath); } unset($_SESSION['playnite_import']); } return $response->withRedirect($this->getRoutePath('admin.playnite.import')); } /** * API endpoint for programmatic imports */ public function apiImport(Request $request, Response $response, $args) { $data = $request->getParsedBody(); if (!isset($data['games']) || !is_array($data['games'])) { return $this->jsonResponse($response->withStatus(400), [ 'error' => 'Games data is required' ]); } try { $importResult = $this->importService->importGames($data['games'], $data['update_existing'] ?? true); return $this->jsonResponse($response, [ 'success' => true, 'result' => $importResult ]); } catch (\Exception $e) { return $this->jsonResponse($response->withStatus(500), [ 'error' => $e->getMessage() ]); } } /** * Get upload error message */ private function getUploadErrorMessage(int $errorCode): string { switch ($errorCode) { case UPLOAD_ERR_INI_SIZE: return 'File too large (exceeds server limit)'; case UPLOAD_ERR_FORM_SIZE: return 'File too large (exceeds form limit)'; case UPLOAD_ERR_PARTIAL: return 'File only partially uploaded'; case UPLOAD_ERR_NO_FILE: return 'No file uploaded'; case UPLOAD_ERR_NO_TMP_DIR: return 'No temporary directory'; case UPLOAD_ERR_CANT_WRITE: return 'Cannot write to disk'; case UPLOAD_ERR_EXTENSION: return 'File upload stopped by extension'; default: return 'Unknown upload error'; } } }