installation->isInstalled()) { throw new NotFoundHttpException('Diese Installation ist abgeschlossen.'); } $error = null; try { $this->installation->installKey(); $given = $request->isMethod('POST') ? $request->request->getString('schluessel') : $request->query->getString('schluessel'); if ('' !== $given) { if ($this->installation->acceptsKey($given)) { // Weiter ohne den Schlüssel in der Adresse: er soll weder im Verlauf noch in // einem Serverprotokoll als Verweis stehen bleiben. $response = $this->redirectToRoute('install'); $response->headers->setCookie(Cookie::create(self::KEY_COOKIE, $this->installation->keyProof(), 0, '/install', null, $request->isSecure(), true, false, Cookie::SAMESITE_STRICT)); return $response; } $error = 'Dieser Schlüssel passt nicht.'; } } catch (\RuntimeException $e) { $error = $e->getMessage(); } $response = $this->render('install/key.html.twig', ['step' => 1, 'error' => $error, 'keyFile' => Installation::KEY_FILE]); $response->headers->set('Referrer-Policy', 'no-referrer'); return $response; } #[Route('/install', name: 'install', methods: ['GET'], priority: 10)] public function check(Request $request): Response { if ($blocked = $this->guard($request)) { return $blocked; } $checks = $this->installation->checks(); return $this->render('install/check.html.twig', [ 'step' => 1, 'checks' => $checks, 'ready' => $this->installation->ready($checks), ]); } #[Route('/install/datenbank', name: 'install_database', methods: ['GET', 'POST'], priority: 10)] public function database(Request $request): Response { if ($blocked = $this->guard($request)) { return $blocked; } $error = null; $driver = $request->request->getString('driver', 'sqlite'); if ($request->isMethod('POST') && $this->signed($request, 'datenbank')) { $input = [ 'host' => $request->request->getString('host', '127.0.0.1'), 'port' => $request->request->getString('port', '3306'), 'name' => $request->request->getString('name', 'cms'), 'user' => $request->request->getString('user'), 'password' => $request->request->getString('password'), ]; $result = $this->installation->testDatabase('mysql' === $driver ? 'mysql' : 'sqlite', $input); $error = $result['error']; if (null === $error) { try { $this->installation->writeEnvironment(['APP_ENV' => 'prod', 'APP_DEBUG' => '0', 'DATABASE_URL' => $result['url']]); // The new connection only exists in the next request, because the environment // file is read while booting. That is also why this is a redirect and not a // rendered page: the migration screen has to talk to the new database. return $this->redirectToRoute('install_migration'); } catch (\RuntimeException $e) { $error = $e->getMessage(); } } } return $this->render('install/database.html.twig', [ 'step' => 2, 'driver' => $driver, 'error' => $error, 'values' => $request->request->all(), 'mysql' => \extension_loaded('pdo_mysql'), // SQLite nur, wenn die Fassung die Tabellenfunktionen kennt, mit denen Doctrine das // Schema liest - sonst scheitert erst der naechste Schritt, und zwar unverstaendlich. 'sqlite' => \extension_loaded('pdo_sqlite') && null !== $this->installation->sqliteVersion() && version_compare((string) $this->installation->sqliteVersion(), Installation::MIN_SQLITE, '>='), 'sqliteVersion' => $this->installation->sqliteVersion(), 'minSqlite' => Installation::MIN_SQLITE, ...$this->token('datenbank'), ]); } #[Route('/install/migration', name: 'install_migration', methods: ['GET', 'POST'], priority: 10)] public function migration(Request $request): Response { if ($blocked = $this->guard($request)) { return $blocked; } if (!$this->installation->environmentWritten()) { return $this->redirectToRoute('install_database'); } $error = null; $applied = null; if ($request->isMethod('POST') && $this->signed($request, 'migration')) { @set_time_limit(0); try { $applied = $this->installation->migrate(); return $this->redirectToRoute('install_website'); } catch (\Throwable $e) { $error = $e->getMessage(); } } return $this->render('install/migration.html.twig', [ 'step' => 3, 'pending' => $error ? [] : $this->pending(), 'applied' => $applied, 'error' => $error, ...$this->token('migration'), ]); } #[Route('/install/website', name: 'install_website', methods: ['GET', 'POST'], priority: 10)] public function website(Request $request): Response { if ($blocked = $this->guard($request)) { return $blocked; } if (!$this->installation->schemaReady()) { return $this->redirectToRoute('install_migration'); } $error = null; if ($request->isMethod('POST') && $this->signed($request, 'website')) { try { $this->installation->createSite( $request->request->getString('name'), $request->request->getString('origin'), $request->request->getString('locale', 'de'), ); return $this->redirectToRoute('install_account'); } catch (InvalidPage|\RuntimeException $e) { $error = $e->getMessage(); } } return $this->render('install/website.html.twig', [ 'step' => 4, 'error' => $error, 'values' => $request->request->all(), // The address the browser is on is almost always the right one, so it is prefilled. 'origin' => $request->getSchemeAndHttpHost(), ...$this->token('website'), ]); } #[Route('/install/konto', name: 'install_account', methods: ['GET', 'POST'], priority: 10)] public function account(Request $request): Response { if ($blocked = $this->guard($request)) { return $blocked; } if (!$this->installation->siteReady()) { return $this->redirectToRoute('install_website'); } $error = null; if ($request->isMethod('POST') && $this->signed($request, 'konto')) { try { $this->installation->createAdmin( $request->request->getString('username'), $request->request->getString('password'), $request->request->getString('repeat'), ); // The last screen is rendered here, not after a redirect: one request later this // route is gone, because the account it just created ends the installation. return $this->render('install/done.html.twig', [ 'step' => 6, 'username' => strtolower(trim($request->request->getString('username'))), ]); } catch (InvalidPage $e) { $error = $e->getMessage(); } } return $this->render('install/account.html.twig', [ 'step' => 5, 'error' => $error, 'username' => $request->request->getString('username'), ...$this->token('konto'), ]); } /** Reading the pending list must never be the reason the screen cannot be shown. */ private function pending(): array { try { return $this->installation->pendingMigrations(); } catch (\Throwable) { return []; } } /** 404 nach der Installation; davor ohne Schlüsselnachweis zurück zur Eingabe des Schlüssels. */ private function guard(Request $request): ?Response { if ($this->installation->isInstalled()) { throw new NotFoundHttpException('Diese Installation ist abgeschlossen.'); } try { if ($this->installation->acceptsProof($request->cookies->getString(self::KEY_COOKIE))) { return null; } } catch (\RuntimeException) { // Kein Schlüssel lesbar oder schreibbar: die Eingabeseite sagt, woran es liegt. } return new RedirectResponse($this->generateUrl('install_key')); } private function token(string $step): array { $issued = time(); return ['issued' => $issued, 'signature' => $this->installation->sign($step, $issued)]; } private function signed(Request $request, string $step): bool { return $this->installation->verify($step, $request->request->getInt('issued'), $request->request->getString('signature')); } }