Files
KantSites/src/Command/AdminPathCommand.php
T
kantarci 495b26896e
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
Fassung 1.7.0: verlässliche Installation, übersichtliche Verwaltung und bedienbare Bewegung
2026-09-14 10:49:54 +02:00

123 lines
5.1 KiB
PHP

<?php
namespace App\Command;
use Doctrine\DBAL\Connection;
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;
/**
* Ändert die Adresse der Verwaltung.
*
* `/admin` ist die erste Adresse, die ein Scanner probiert, und jeder Anmeldeversuch, der dort
* ankommt, kostet Rechenzeit und Logzeilen. Heißt die Verwaltung anders, findet sie niemand, der
* sie nicht kennt. Das ersetzt kein gutes Passwort - es nimmt der Maschinerie nur das Ziel.
*
* Der Wert steht in `.env` und nicht in der Datenbank: die Routen stehen fest, bevor die
* erste Datenbankabfrage läuft. Deshalb dieser Befehl - er schreibt die Datei, prüft vorher, dass
* die Adresse benutzbar ist, und räumt den Zwischenspeicher auf, damit die neue Adresse sofort
* gilt.
*/
#[AsCommand(name: 'cms:admin:path', description: 'Zeigt die Adresse der Verwaltung oder ändert sie.')]
final class AdminPathCommand extends Command
{
/** Adressen, die schon vergeben sind oder es werden könnten. */
private const TABU = ['assets', 'media', 'install', 'einwilligung', 'formular', 'kategorie', 'schlagwort', 'beitraege', 'feed', 'sitemap', 'robots', 'llms', 'favicon'];
public function __construct(private Connection $db, private string $projectDir, private string $adminPath, private string $environment)
{
parent::__construct();
}
protected function configure(): void
{
$this->addArgument('pfad', InputArgument::OPTIONAL, 'Die neue Adresse, ohne Schrägstrich. Ohne Angabe wird die aktuelle gezeigt.');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$neu = trim((string) $input->getArgument('pfad'), " \t/");
if ('' === $neu) {
$io->writeln(sprintf('Die Verwaltung liegt unter <info>/%s</info>.', $this->adminPath));
$io->writeln('Ändern: <comment>php bin/console cms:admin:path meine-verwaltung</comment>');
return Command::SUCCESS;
}
if (1 !== preg_match('/^[a-z0-9]+(?:-[a-z0-9]+)*$/D', $neu) || strlen($neu) > 40) {
$io->error('Erlaubt sind Kleinbuchstaben, Ziffern und Bindestriche, höchstens 40 Zeichen.');
return Command::FAILURE;
}
if (in_array($neu, self::TABU, true)) {
$io->error(sprintf('„%s" ist für die Website selbst reserviert.', $neu));
return Command::FAILURE;
}
// Eine Seite mit dieser Adresse wäre von der Verwaltung verdeckt - unauffindbar, ohne dass
// jemand merkt, warum.
$seite = $this->db->fetchOne('SELECT path FROM page WHERE website_id = ? AND path = ? AND deleted_at IS NULL', ['default', $neu]);
if (is_string($seite)) {
$io->error(sprintf('Es gibt bereits eine Seite unter /%s. Die Verwaltung würde sie verdecken.', $neu));
return Command::FAILURE;
}
try {
\App\Ops\Environment::write($this->projectDir, ['ADMIN_PATH' => $neu]);
} catch (\RuntimeException $e) {
$io->error($e->getMessage());
return Command::FAILURE;
}
// Ohne das Aufräumen gälte die alte Adresse weiter - und die neue wäre nicht erreichbar.
// Die Umgebung, in der dieser Befehl gerade läuft, bleibt dabei unangetastet: Symfony lädt
// Dienste nach, und wer sich den Boden unter den Füßen wegräumt, endet mit einem Fehler,
// obwohl alles geklappt hat. Der nächste Aufruf baut sie ohnehin neu.
$offen = [];
foreach (glob($this->projectDir.'/var/cache/*') ?: [] as $pfad) {
if (!is_dir($pfad)) {
continue;
}
if (basename($pfad) === $this->environment) {
$offen[] = basename($pfad);
continue;
}
$this->removeDirectory($pfad);
}
$io->success(sprintf('Die Verwaltung liegt jetzt unter /%s.', $neu));
if ([] !== $offen) {
$io->writeln(sprintf('Falls die Website noch die alte Adresse kennt: <comment>php bin/console cache:clear --env=%s</comment>', implode(' --env=', $offen)));
}
if ($this->adminPath !== $neu) {
$io->writeln('Merk dir die Adresse: <comment>/'.$this->adminPath.'</comment> antwortet ab sofort mit 404, wie jede andere unbekannte Adresse auch.');
}
$io->writeln('Zurück geht es mit: <comment>php bin/console cms:admin:path admin</comment>');
return Command::SUCCESS;
}
private function removeDirectory(string $pfad): void
{
if (!is_dir($pfad)) {
return;
}
foreach (scandir($pfad) ?: [] as $eintrag) {
if ('.' === $eintrag || '..' === $eintrag) {
continue;
}
$voll = $pfad.'/'.$eintrag;
is_dir($voll) ? $this->removeDirectory($voll) : @unlink($voll);
}
@rmdir($pfad);
}
}