123 lines
5.1 KiB
PHP
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);
|
|
}
|
|
}
|