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>
93 lines
4.0 KiB
PHP
93 lines
4.0 KiB
PHP
<?php
|
||
|
||
namespace App\Command;
|
||
|
||
use App\Ops\Environment;
|
||
use Symfony\Component\Console\Attribute\AsCommand;
|
||
use Symfony\Component\Console\Command\Command;
|
||
use Symfony\Component\Console\Input\InputArgument;
|
||
use Symfony\Component\Console\Input\InputInterface;
|
||
use Symfony\Component\Console\Output\OutputInterface;
|
||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||
|
||
/**
|
||
* Zeigt die öffentliche Adresse der Website oder ändert sie.
|
||
*
|
||
* Die Adresse (`SITE_ORIGIN`) steht in `.env`, weil sie zur Installation gehört und nicht zum
|
||
* Inhalt: Der Installer schreibt sie einmal, danach gab es nur den Editor für die Datei. Dieser
|
||
* Befehl prüft, was hineinkommt - mit Schema, ohne Pfad -, und schreibt es an dieselbe Stelle.
|
||
*
|
||
* Mit oder ohne www ist damit eine Entscheidung an einem Ort: Canonical, Sitemap, Feed und die
|
||
* Umleitung der anderen Schreibweise folgen dieser Einstellung.
|
||
*/
|
||
#[AsCommand(name: 'cms:site:origin', description: 'Zeigt die öffentliche Adresse der Website (SITE_ORIGIN) oder ändert sie.')]
|
||
final class SiteOriginCommand extends Command
|
||
{
|
||
public function __construct(private string $projectDir, private string $siteOrigin)
|
||
{
|
||
parent::__construct();
|
||
}
|
||
|
||
protected function configure(): void
|
||
{
|
||
$this->addArgument('adresse', InputArgument::OPTIONAL, 'Die neue Adresse mit Schema, z. B. https://www.meine-firma.de. Ohne Angabe wird die aktuelle gezeigt.');
|
||
}
|
||
|
||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||
{
|
||
$io = new SymfonyStyle($input, $output);
|
||
$neu = trim((string) $input->getArgument('adresse'));
|
||
if ('' === $neu) {
|
||
$io->writeln(sprintf('Die Website ist eingetragen als <info>%s</info>.', '' === $this->siteOrigin ? '(leer)' : $this->siteOrigin));
|
||
$io->writeln('Ändern: <comment>php bin/console cms:site:origin https://www.meine-firma.de</comment>');
|
||
|
||
return Command::SUCCESS;
|
||
}
|
||
|
||
try {
|
||
$neu = self::normalize($neu);
|
||
} catch (\InvalidArgumentException $e) {
|
||
$io->error($e->getMessage());
|
||
|
||
return Command::FAILURE;
|
||
}
|
||
try {
|
||
Environment::write($this->projectDir, ['SITE_ORIGIN' => $neu]);
|
||
} catch (\RuntimeException $e) {
|
||
$io->error($e->getMessage());
|
||
|
||
return Command::FAILURE;
|
||
}
|
||
|
||
$io->success(sprintf('Die Website ist jetzt eingetragen als %s.', $neu));
|
||
$io->writeln('Canonical, Sitemap, Feed und strukturierte Daten nennen diese Adresse; die Schreibweise '.(str_starts_with(parse_url($neu, PHP_URL_HOST) ?: '', 'www.') ? 'ohne' : 'mit').' www leitet dorthin um.');
|
||
$io->writeln('Falls die Website den alten Wert noch kennt: <comment>php bin/console cache:clear --env=prod</comment>');
|
||
|
||
return Command::SUCCESS;
|
||
}
|
||
|
||
/**
|
||
* Schema und Host, kleingeschrieben, ohne Pfad, Abfrage oder abschließenden Schrägstrich.
|
||
*
|
||
* @throws \InvalidArgumentException
|
||
*/
|
||
public static function normalize(string $adresse): string
|
||
{
|
||
$adresse = rtrim(trim($adresse), '/');
|
||
$teile = parse_url($adresse);
|
||
$schema = strtolower((string) ($teile['scheme'] ?? ''));
|
||
if (!\in_array($schema, ['http', 'https'], true)) {
|
||
throw new \InvalidArgumentException('Die Adresse beginnt mit https:// – zum Beispiel https://www.meine-firma.de');
|
||
}
|
||
$host = strtolower((string) ($teile['host'] ?? ''));
|
||
if ('' === $host || false === filter_var($schema.'://'.$host, FILTER_VALIDATE_URL) || isset($teile['user'], $teile['pass'])) {
|
||
throw new \InvalidArgumentException('Diese Adresse ist keine gültige URL.');
|
||
}
|
||
if (isset($teile['path']) && '' !== $teile['path'] || isset($teile['query']) || isset($teile['fragment'])) {
|
||
throw new \InvalidArgumentException('Nur Schema und Domain, ohne Pfad: https://www.meine-firma.de statt https://www.meine-firma.de/start');
|
||
}
|
||
|
||
return $schema.'://'.$host.(isset($teile['port']) ? ':'.$teile['port'] : '');
|
||
}
|
||
}
|