- The installer only opens with a per-installation key that install.php creates and shows; the key is deleted with the first account. - Lock files keep .env, the database, backups and logs out of reach when a domain points at the project folder instead of public/. - Linked sections render by their own schema version, so an old section cannot carry unfiltered HTML into a newer page. - Editors no longer move, trash or change areas and post data of published pages; a scheduled publication goes live with the revision that was scheduled (migration 20261012000100). - Form stamps are bound to their form, IPv6 senders count per /64, and a flood per form or overall stops storing with a visible message. - The consent redirect rejects control characters, update targets must look like 1.2.3, and own CSS cannot reach outside its block. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
57 lines
2.1 KiB
PHP
57 lines
2.1 KiB
PHP
<?php
|
|
|
|
namespace App\EventSubscriber;
|
|
|
|
use App\Ops\Installation;
|
|
use App\Ops\Protection;
|
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
|
use Symfony\Component\HttpFoundation\RedirectResponse;
|
|
use Symfony\Component\HttpKernel\Event\RequestEvent;
|
|
use Symfony\Component\HttpKernel\KernelEvents;
|
|
|
|
/**
|
|
* An installation that does not exist yet answers with the installer instead of an error.
|
|
*
|
|
* Without this, the first visit to a freshly unpacked website is a 500 from a missing table - the
|
|
* one moment where a person needs a sentence, not a stack trace.
|
|
*
|
|
* The cost of knowing is kept at zero for a running website: once an account exists, a marker file
|
|
* is written and no request asks the database again. The marker only ever silences this
|
|
* subscriber; the installer itself always asks the database, so deleting the file does not reopen
|
|
* anything.
|
|
*/
|
|
final class InstallSubscriber implements EventSubscriberInterface
|
|
{
|
|
public const MARKER = 'var/installed';
|
|
|
|
public function __construct(private Installation $installation, private string $projectDir)
|
|
{
|
|
}
|
|
|
|
public static function getSubscribedEvents(): array
|
|
{
|
|
// After maintenance (40), before the router: an unfinished installation has no routes
|
|
// worth matching except its own.
|
|
return [KernelEvents::REQUEST => ['onRequest', 36]];
|
|
}
|
|
|
|
public function onRequest(RequestEvent $event): void
|
|
{
|
|
// Fehlende Sperrdateien nachtragen (var/, vendor/, Projektstamm) - einmal je Prozess.
|
|
Protection::ensure($this->projectDir);
|
|
if (!$event->isMainRequest() || is_file($this->projectDir.'/'.self::MARKER)) {
|
|
return;
|
|
}
|
|
if ($this->installation->isInstalled()) {
|
|
@file_put_contents($this->projectDir.'/'.self::MARKER, gmdate('c')."\n");
|
|
|
|
return;
|
|
}
|
|
$path = $event->getRequest()->getPathInfo();
|
|
if (str_starts_with($path, '/install') || preg_match('#^/(assets/|favicon)#', $path)) {
|
|
return;
|
|
}
|
|
$event->setResponse(new RedirectResponse('/install'));
|
|
}
|
|
}
|