feat: assign custom 404 pages with the website theme (1.8.0)
CMS quality / php (8.2) (push) Canceled after 0s
CMS quality / php (8.4) (push) Canceled after 0s
CMS quality / browser (push) Canceled after 0s
CMS quality / mysql (push) Canceled after 0s

This commit is contained in:
kantarci committed 2026-09-14 12:18:33 +02:00
1 parent a3c89bb69d
commit 16da844934
17 files changed
+638 -92

No files matched your search

+44 -1
View File
@@ -3,6 +3,7 @@
namespace App\Content;
use App\Domain\InvalidPage;
use App\Domain\StaleWrite;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Exception\TableNotFoundException;
@@ -53,6 +54,7 @@ final class SiteSettings
'robotsExtra' => '',
// Which stored page answers at the site root. Everything else derives from this.
'homePath' => 'home',
'notFoundPageId' => '',
// The consent question. Off by default, because a site that loads nothing from third
// parties has nothing to ask about, and a banner without a reason is only in the way.
'consentBanner' => false,
@@ -96,6 +98,8 @@ final class SiteSettings
public function save(array $input): void
{
$values = self::DEFAULTS;
// Assigned separately, so saving company data cannot clear the error page.
$values['notFoundPageId'] = $this->load()['notFoundPageId'];
foreach (['legalName' => 200, 'description' => 300, 'phone' => 40, 'email' => 120, 'street' => 120, 'postalCode' => 16, 'city' => 120] as $key => $max) {
$values[$key] = mb_substr(trim((string) ($input[$key] ?? '')), 0, $max);
}
@@ -194,9 +198,13 @@ final class SiteSettings
throw new InvalidPage('Die Startseite braucht eine gültige Adresse.');
}
// A root that points nowhere would answer every visit with 404, so it must exist.
if (false === $this->db->fetchOne('SELECT id FROM page WHERE website_id = ? AND path = ? AND deleted_at IS NULL', ['default', $home])) {
$homeId = $this->db->fetchOne('SELECT id FROM page WHERE website_id = ? AND path = ? AND deleted_at IS NULL', ['default', $home]);
if (false === $homeId) {
throw new InvalidPage(sprintf('Es gibt keine Seite mit der Adresse /%s.', $home));
}
if ($homeId === $values['notFoundPageId']) {
throw new InvalidPage('Die 404-Seite kann nicht gleichzeitig die Startseite sein.');
}
$values['homePath'] = $home;
$values['consentBanner'] = (bool) ($input['consentBanner'] ?? false);
@@ -237,6 +245,41 @@ final class SiteSettings
]);
}
/** Change only the 404 assignment, preserving settings saved in other forms. */
public function assignNotFoundPage(string $id, string $expectedId): void
{
$this->db->transactional(function () use ($id, $expectedId): void {
$lock = $this->db->getDatabasePlatform() instanceof \Doctrine\DBAL\Platforms\SQLitePlatform ? '' : ' FOR UPDATE';
$raw = $this->db->fetchOne('SELECT settings FROM website WHERE id = ?'.$lock, ['default']);
$stored = is_string($raw) ? json_decode($raw, true, 512, JSON_THROW_ON_ERROR) : [];
$stored = is_array($stored) ? $stored : [];
$current = $stored['notFoundPageId'] ?? '';
if ($current !== $expectedId) {
throw new StaleWrite('Die 404-Zuordnung wurde inzwischen geändert. Lade die Einstellungen neu.');
}
if ('' !== $id) {
$page = $this->db->fetchAssociative('SELECT path, type FROM page WHERE id = ? AND website_id = ? AND deleted_at IS NULL', [$id, 'default']);
if (false === $page || 'page' !== $page['type']) {
throw new InvalidPage('Bitte wähle eine vorhandene Seite als 404-Seite.');
}
if ($page['path'] === ($stored['homePath'] ?? self::DEFAULTS['homePath'])) {
throw new InvalidPage('Die Startseite kann nicht gleichzeitig die 404-Seite sein.');
}
}
if ($current === $id) {
return;
}
$stored['notFoundPageId'] = $id;
$changed = $this->db->executeStatement('UPDATE website SET settings = ? WHERE id = ?', [
json_encode($stored, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE), 'default',
]);
if (1 !== $changed) {
throw new StaleWrite('Die Einstellungen wurden inzwischen geändert. Lade die Seite neu.');
}
});
$this->loaded = null;
}
/**
* Which of the two mandatory pages are missing, by their settings key.
*