Files
KantSites/src/Controller/SettingsController.php
T
kantarciandClaude Fable 5.1 7eb462ac7b Redirect the other www spelling to SITE_ORIGIN, hold roles to the role help and fix the audit findings
SITE_ORIGIN alone decides whether the site is www or not: CanonicalHostSubscriber
sends the other spelling there with 301, cms:site:origin writes the setting, the
system screen shows it. The roles now match the role help (editor: drafts,
publisher: live content, admin: design, settings, users, system). The installer no
longer reopens on an unreachable database, the session cookie follows ADMIN_PATH,
percent-encoded credentials no longer break the container, consent only accepts
same-site decisions, JSON-LD is HTML-safe, own CSS stays in its block, plus the
editor, updater, download and public-output findings from the full review
(ADR 099, ADR 100).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-15 20:15:20 +02:00

192 lines
8.7 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Controller;
use App\Content\Consent;
use App\Content\DemoContent;
use App\Content\MediaLibrary;
use App\Content\NotFoundPage;
use App\Content\PageRepository;
use App\Content\RedirectRepository;
use App\Content\SiteSettings;
use App\Content\SocialProfiles;
use App\Content\Theme;
use App\Domain\InvalidPage;
use App\Domain\StaleWrite;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
final class SettingsController extends AbstractController
{
public function __construct(private SiteSettings $settings, private RedirectRepository $redirects, private PageRepository $pages, private MediaLibrary $media, private Theme $theme, private NotFoundPage $notFoundPage)
{
}
#[Route('/%kant.admin_path%/settings', name: 'admin_settings', methods: ['GET'])]
public function index(Request $request): Response
{
// Alles hier wirkt sofort auf die Website: 404-Seite und Weiterleitungen sind Sache des
// Veroeffentlichers, die Unternehmensdaten samt Einwilligung und Spamschutz die des
// Administrators - der Bildschirm blendet den Teil aus, den die Rolle nicht aendern darf.
$this->denyAccessUnlessGranted('ROLE_PUBLISHER');
$choosable = [];
foreach ($this->pages->listForEditor() as $page) {
// Only a published page can serve as the root; a draft would answer every visit with 404.
if ('page' === $page['type'] && null !== $page['published_revision_id']) {
$document = json_decode($page['document'] ?? '{}', true, 512, JSON_THROW_ON_ERROR);
$choosable[] = ['path' => $page['path'], 'title' => $document['title'] ?? $page['path']];
}
}
return $this->render('admin/settings.html.twig', [
'settings' => $this->settings->load(),
'notFoundSelection' => $this->notFoundPage->selected(),
'redirects' => $this->redirects->all($request->query->getInt('seite', 1)),
'choosableHome' => $choosable,
'consentGroups' => Consent::GROUPS,
'missingLegal' => $this->settings->missingLegalPages(),
'consentGroupHelp' => Consent::GROUP_HELP,
// Für das Symbol der Website: die Mediathek zur Auswahl, die neuesten zuerst.
'images' => $this->media->page(1, false, 96)['items'],
// Fuer die Farbfelder: dieselbe Palette, die auch der Editor anbietet.
'palette' => $this->theme->load()['colors'],
'socialNetworks' => SocialProfiles::NETWORKS,
'weekdays' => SiteSettings::DAYS,
'socialPlaceholders' => SocialProfiles::PLACEHOLDERS,
]);
}
#[Route('/%kant.admin_path%/settings/company', name: 'admin_settings_save', methods: ['POST'])]
public function save(Request $request): Response
{
$this->denyAccessUnlessGranted('ROLE_ADMIN');
if (!$this->isCsrfTokenValid('settings', $request->request->getString('_csrf_token'))) {
return $this->back('Deine Sitzung ist abgelaufen. Bitte melde dich erneut an.');
}
try {
$this->settings->save($request->request->all());
} catch (InvalidPage $e) {
return $this->back($e->getMessage());
}
return $this->back('Die Unternehmensdaten wurden gespeichert.', 'success');
}
#[Route('/%kant.admin_path%/settings/not-found', name: 'admin_not_found_save', methods: ['POST'])]
public function saveNotFound(Request $request): Response
{
$this->denyAccessUnlessGranted('ROLE_PUBLISHER');
if (!$this->isCsrfTokenValid('settings', $request->request->getString('_csrf_token'))) {
return $this->back('Deine Sitzung ist abgelaufen. Bitte melde dich erneut an.');
}
try {
$expectedId = $request->request->getString('expectedId');
if ('create' === $request->request->getString('action')) {
$id = $this->notFoundPage->create($expectedId);
$this->addFlash('success', 'Die 404-Vorlage wurde als Entwurf angelegt und zugewiesen. Nach dem Veröffentlichen ersetzt sie die Standard-404.');
return $this->redirectToRoute('admin_editor', ['id' => $id]);
}
$id = $request->request->getString('notFoundPageId');
$this->settings->assignNotFoundPage($id, $expectedId);
$selection = $this->notFoundPage->selected();
$message = '' === $id ? 'Die Standard-404 im Website-Design ist wieder aktiv.'
: ($selection['published'] ? 'Die 404-Seite wurde zugewiesen.' : 'Die Seite wurde zugewiesen. Bis zu ihrer Veröffentlichung erscheint die Standard-404.');
$this->addFlash('success', $message);
} catch (InvalidPage|StaleWrite $error) {
$this->addFlash('error', $error->getMessage());
}
return $this->redirectToRoute('admin_settings', ['_fragment' => 'not-found']);
}
#[Route('/%kant.admin_path%/settings/redirects', name: 'admin_redirect_save', methods: ['POST'])]
public function saveRedirect(Request $request): Response
{
$this->denyAccessUnlessGranted('ROLE_PUBLISHER');
if (!$this->isCsrfTokenValid('settings', $request->request->getString('_csrf_token'))) {
return $this->back('Deine Sitzung ist abgelaufen. Bitte melde dich erneut an.');
}
$remove = $request->request->getString('remove');
if ('' !== $remove) {
$this->redirects->delete($remove);
return $this->back('Die Weiterleitung wurde entfernt.', 'success');
}
try {
$this->redirects->save(
$request->request->getString('from'),
$request->request->getString('to'),
$request->request->getInt('status', 301),
);
} catch (InvalidPage $e) {
return $this->back($e->getMessage());
}
return $this->back('Die Weiterleitung wurde gespeichert.', 'success');
}
/**
* Creates the privacy statement and the imprint, with a skeleton whose gaps are marked.
*
* The pages are mandatory, the text is not something a CMS can write: what gets created is the
* structure with every required item named and marked as still to be filled in.
*/
#[Route('/%kant.admin_path%/settings/rechtliches', name: 'admin_legal_pages', methods: ['POST'])]
public function legalPages(Request $request): Response
{
$this->denyAccessUnlessGranted('ROLE_PUBLISHER');
if (!$this->isCsrfTokenValid('settings', $request->request->getString('_csrf_token'))) {
return $this->back('Deine Sitzung ist abgelaufen. Bitte melde dich erneut an.');
}
$settings = $this->settings->load();
$created = [];
try {
foreach (DemoContent::LEGAL as $slug => $label) {
$key = 'datenschutz' === $slug ? 'privacyPath' : 'imprintPath';
// An existing page is never overwritten - it may already carry the real text.
$existing = null !== $this->pages->findPublished($slug) || $this->pageExists($slug);
if (!$existing) {
$id = $this->pages->createPage($slug, null, 'page', DemoContent::legalPage($slug));
$page = $this->pages->findForEditor($id);
$this->pages->publish($id, $page['draft_revision_id'], $page['version']);
$created[] = $label;
}
$settings[$key] = $slug;
}
$this->settings->save([...$settings, 'consentGroups' => $settings['consentGroups'], 'sameAs' => implode('
', $settings['sameAs'])]);
} catch (InvalidPage $e) {
return $this->back($e->getMessage());
}
return $this->back(
[] === $created
? 'Die Seiten waren schon vorhanden und sind jetzt verknüpft. Der Inhalt bleibt unverändert.'
: sprintf('%s angelegt und verknüpft. Beide Seiten sind veröffentlicht und müssen noch ausgefüllt werden – die Pflichtangaben sind darin markiert.', implode(' und ', $created)),
'success',
);
}
/** Also finds a page that exists but is not published yet. */
private function pageExists(string $path): bool
{
foreach ($this->pages->listForEditor() as $page) {
if ($path === $page['path']) {
return true;
}
}
return false;
}
private function back(string $message, string $type = 'error'): Response
{
$this->addFlash($type, $message);
return $this->redirectToRoute('admin_settings');
}
}