From 7eaa3f6efac7137df982cde29059fe5a7308ad08 Mon Sep 17 00:00:00 2001 From: Umut Kantarci Date: Tue, 13 Oct 2026 07:48:43 +0200 Subject: [PATCH] Tidy the mail and system screens and let the mail log be cleared Text in admin cards had no size of its own, so 16px browser default sat next to 13px notes; cards now use 13px throughout while fields stay at 16px for iOS. The mail page gets option cards, a 2x2 SMTP grid, a styled save button in the card foot and a log with status pills and collapsed server messages, plus "Protokoll leeren". The package card uses the same sections and a styled file picker. The E-Mail-Versand subitem only shows inside System and sits beside it in the horizontal strip. Co-Authored-By: Claude Opus 5.5 --- docs/STATUS.md | 10 ++ public/assets/admin.css | 83 +++++++++-- src/Controller/MailController.php | 13 ++ src/Ops/MailCheck.php | 10 ++ templates/admin/_mail_entry.html.twig | 15 ++ templates/admin/mail.html.twig | 195 ++++++++++++++------------ templates/admin/shell.html.twig | 5 +- templates/admin/system.html.twig | 16 +-- tests/SystemScreenTest.php | 17 +++ 9 files changed, 251 insertions(+), 113 deletions(-) create mode 100644 templates/admin/_mail_entry.html.twig diff --git a/docs/STATUS.md b/docs/STATUS.md index aeac239..8173220 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -1,5 +1,15 @@ # Projektstatus +## Nach 1.35.0: E-Mail-Versand und Systemseite aufgeräumt, Protokoll leeren (13.10.2026) + +**Anlass:** Umut: E-Mail-Seite und „Inhalte übertragen“ sehen uneinheitlich aus, Unterpunkt „E-Mail-Versand“ steht immer offen, „Logs leeren“ fehlt. + +**Erledigt:** Fließtext in Karten hat jetzt durchgehend 13 px (vorher Browser-16 px neben 13 px), Felder bleiben 16 px. Neue gemeinsame Bausteine in `admin.css`: `.card-section` (Abschnitt mit Trennlinie), `.button-row`, `.form-foot`, `.option`/`.option-list` (Auswahl als Karten), `.pill-error`, Dateiauswahl im Knopfstil. E-Mail-Seite: Wege als Karten mit Erklärung, SMTP 2×2, Testmail-Abschnitt, „Speichern“ als Hauptknopf im Kartenfuß (war ungestylt). Protokoll mit Zustandsplakette, Hinweis vorn, Servermeldung zugeklappt (`admin/_mail_entry.html.twig`). „Protokoll leeren“ (`POST /admin/system/e-mail/protokoll`, löscht `mail.log` und `mail.log.1`). Unterpunkt nur im Bereich System sichtbar; in der waagrechten Leiste (761–1000 px) neben „System“. + +**Geprüft:** `SystemScreenTest` erweitert (Leeren nur mit Token, danach leer, Editor 403, Unterpunkt auf Einstellungen nicht sichtbar). PHPUnit vollständig 682 grün, php-cs-fixer sauber, `lint:twig`, `test:admin`, `test:editor` grün. Im Browser (Hüfner lokal) bei 1240, 900 und 375 px: E-Mail-Seite, Systemseite, Einstellungen; kein waagrechtes Scrollen am Handy. + +**Offen:** nicht veröffentlicht (kein Tag/Release). + ## Fassung 1.35.0: E-Mail-Versand als eigene Seite, HTTPS-Probe bei IONOS (12.10.2026) **Anlass:** huefner-gartenbau.de: mit `native://default` geht der Versand, Umut will die Einstellung samt SMTP in der Verwaltung, als Unterpunkt statt auf „System“. „HTTPS erzwingen“ meldete 403, obwohl HTTPS lief. diff --git a/public/assets/admin.css b/public/assets/admin.css index 5411341..abe1ee6 100644 --- a/public/assets/admin.css +++ b/public/assets/admin.css @@ -293,7 +293,24 @@ label { display: block; margin: 18px 0 7px; font-size: 13px; font-weight: 600 } } .card-head h2, .card-head h3 { margin: 0 } .card-head .muted { font-size: 12px } -.card-body { padding: 8px 22px 24px; min-width: 0 } +.card-body { padding: 8px 22px 24px; min-width: 0; font-size: 13px; line-height: 1.6 } +/* Fließtext in Karten ist 13 px wie Hinweise und Fakten. Felder bleiben bei 16 px - darunter + vergrößert iOS beim Antippen die ganze Seite. :where() hält die Regel schwächer als jede Klasse. */ +:where(.card-body) :where(input:not([type=checkbox], [type=radio]), select, textarea) { font-size: 16px } +.card-body h3 { font-size: 14px; margin: 26px 0 4px } +.card-body h3:first-child { margin-top: 14px } +/* Abschnitte einer Karte: Trennlinie, Überschrift, gleicher Abstand - als fieldset oder form. */ +.card-section { border: 0; border-top: 1px solid var(--line); margin: 22px 0 0; padding: 18px 0 0; min-width: 0 } +.card-section > legend { float: left; width: 100%; font-weight: 650; font-size: 14px; margin: 0 0 12px; padding: 0 } +.card-section > legend + * { clear: both } +.card-section > h3, .card-body .card-section > h3:first-child { font-size: 14px; font-weight: 650; margin: 0 0 12px } +.card-section .check-label { margin: 14px 0 0 } +.card-section > h3 + .check-label, .card-section > h3 + input + .check-label { margin-top: 0 } +.card-section label.field-first { margin-top: 0 } +/* Dateiauswahl im Stil der übrigen Knöpfe statt des grauen Browserknopfs. */ +input[type=file] { padding: 6px; cursor: pointer; max-width: 720px } +input[type=file]::file-selector-button { margin-right: 12px; padding: 7px 14px; border: 1px solid var(--line); border-radius: 6px; background: #f3f5f1; color: var(--ink); font: inherit; font-size: 13px; font-weight: 600; cursor: pointer } +input[type=file]:hover::file-selector-button { background: #e8ede6 } .card-body code { overflow-wrap: anywhere } /* Das Kleingedruckte einer Karte: zugeklappt, damit auf einem Handy der Knopf oben bleibt. */ .hint { margin: 12px 0 0 } @@ -306,6 +323,22 @@ label { display: block; margin: 18px 0 7px; font-size: 13px; font-weight: 600 } .card-body > p:first-child { margin-top: 14px } .card-body .primary, .card-body .secondary { margin-top: 22px } .card-foot { padding: 16px 22px; border-top: 1px solid var(--line); background: #fafbf8; border-radius: 0 0 12px 12px } +/* Der Fuss eines Formulars in einer Karte: Hauptknopf links, Erklärung daneben. */ +.form-foot { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 16px; font-size: 12px } +.form-foot .primary { margin: 0 } +/* Knöpfe nebeneinander, gleicher Abstand, ohne den Abstand nach oben der Einzelknöpfe. */ +.button-row { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 16px } +.card-body .button-row .primary, .card-body .button-row .secondary { margin: 0 } +/* Auswahl als Karten: ganze Fläche klickbar, Titel und eine Zeile Erklärung. */ +.option-list { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 8px; max-width: 720px } +.option { display: flex; align-items: flex-start; gap: 10px; margin: 0; padding: 12px 14px; border: 1px solid var(--line); border-radius: 10px; background: var(--paper); font-weight: 400; cursor: pointer } +.option:hover { border-color: #a9b8af } +.option:has(input:checked) { border-color: var(--accent); background: #f4f8f1; box-shadow: inset 0 0 0 1px var(--accent) } +.option input { width: 16px; min-height: 16px; margin: 2px 0 0; flex: none; accent-color: var(--accent) } +.option > span { display: grid; gap: 2px; min-width: 0 } +.option strong { font-size: 13px; font-weight: 600 } +.option > span > span { font-size: 12px; color: var(--muted); line-height: 1.45 } +.check-label input, .option input { accent-color: var(--accent) } /* A card that opens: used for everything secondary, so a screen starts calm. */ details.card > summary { @@ -376,6 +409,7 @@ details.card > summary:hover { background: #fafbf8 } .pill-live { background: #e7f0d6; color: #2c5340 } .pill-draft { background: #fff2d9; color: #6f4e18 } .pill-off { background: #f1f2f0; color: #5a635e } +.pill-error { background: #fff0eb; color: var(--danger) } /* The per-row settings. One disclosure that opens in place, instead of six competing popovers that clip each other at the edge of the card. */ @@ -593,6 +627,10 @@ details.card > summary:hover { background: #fafbf8 } .nav-label { display: none } .nav-list { display: flex; gap: 4px } .nav-item { white-space: nowrap; padding: 8px 12px } + /* In der Leiste stehen Unterpunkte direkt neben ihrem Hauptpunkt, nicht in einer eigenen Zeile. */ + .nav-list > li:has(.nav-sub) { display: flex; gap: 4px } + .nav-sub { display: flex; margin: 0 } + .nav-sub .nav-item { padding: 8px 12px } .shell-foot { margin: 0 0 0 auto; padding: 0 0 0 12px; border-top: 0; border-left: 1px solid #2a3d35; display: flex; align-items: center; gap: 10px } .shell-account { padding: 3px; gap: 2px } .shell-user { padding: 3px } @@ -718,23 +756,37 @@ details.card > summary:hover { background: #fafbf8 } .check-bad { color: var(--danger) } .log-list { list-style: none; margin: 14px 0 0; padding: 0; display: grid; gap: 6px } .log-list code { font-size: 11px; overflow-wrap: anywhere } -.mail-result { margin-top: 14px } -#mail .facts { margin-top: 0 } +.mail-result { margin-top: 16px } .mail-result p { margin: 0 0 6px } .mail-result p:last-child { margin-bottom: 0 } .mail-result pre { white-space: pre-wrap; overflow-wrap: anywhere; font-size: 11px; margin: 6px 0 0 } +.mail-result-head { margin: 0 0 10px } /* Unterpunkte im Menü: eingerückt auf die Höhe des Textes, ohne Symbol. */ -.nav-sub { list-style: none; margin: 2px 0 4px; padding: 0 } -.nav-sub .nav-item { padding-left: 42px; font-size: 13px } -.mail-form { margin-top: 18px; max-width: 640px } -.mail-form fieldset { border: 0; margin: 0 0 16px; padding: 0; min-width: 0 } -.mail-form legend { font-weight: 600; font-size: 13px; margin-bottom: 6px; padding: 0 } -.mail-form .check-label { display: flex; gap: 8px; align-items: baseline; margin: 4px 0 } -.mail-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 8px 12px } -.mail-grid label { margin-top: 0 } -.mail-grid input { width: 100%; box-sizing: border-box } -.mail-actions { display: flex; flex-wrap: wrap; gap: 8px } -.mail-actions button { margin: 0 } +.nav-sub { list-style: none; margin: 2px 0 4px; padding: 0; display: grid; gap: 2px } +.nav-sub .nav-item { padding: 7px 10px 7px 39px; font-size: 12.5px; color: var(--shell-muted) } +.nav-sub .nav-item:hover { color: var(--shell-ink) } +.nav-sub .nav-item[aria-current=page] { color: #fff; background: #21362d } +/* E-Mail-Versand: der eingestellte Weg als Zeile, darunter Abschnitte mit Trennlinie. */ +.mail-current { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px; margin-top: 14px; padding: 12px 14px; border-radius: 10px; background: #f6f8f4 } +.mail-current-source { flex-basis: 100%; font-size: 12px } +.mail-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 0 14px; max-width: 720px } +.mail-grid label { margin: 0 0 7px } +.mail-grid > div { margin-bottom: 4px } +/* Protokoll: Zustand links, daneben was, wann und - bei Fehlern - warum. */ +.mail-log { list-style: none; margin: 14px 0 0; padding: 0 } +.mail-entry { display: grid; grid-template-columns: 112px minmax(0, 1fr); gap: 4px 14px; align-items: start; padding: 12px 0; border-top: 1px solid var(--line) } +.mail-entry:first-child { border-top: 0; padding-top: 4px } +.mail-entry .pill { justify-self: start; margin-top: 1px } +.mail-entry-text p { margin: 0 } +.mail-entry-line { overflow-wrap: anywhere } +.mail-entry-time { font-size: 12px; color: var(--muted) } +.mail-entry-hint { margin-top: 6px !important; color: var(--ink) } +.mail-entry-detail { margin-top: 6px } +.mail-entry-detail summary { font-size: 12px; font-weight: 600; color: var(--muted); cursor: pointer; width: fit-content } +.mail-entry-detail pre { margin: 6px 0 0; padding: 10px 12px; border-radius: 8px; background: #f6f8f4; font-size: 11px; line-height: 1.55; white-space: pre-wrap; overflow-wrap: anywhere } +@media (max-width: 560px) { + .mail-entry { grid-template-columns: minmax(0, 1fr) } +} /* Ohne SMTP brauchen die Felder niemand; wo :has() fehlt, bleiben sie einfach sichtbar. */ .mail-form:has(input[name=weg]:checked:not([value=smtp])) .smtp-fields { display: none } @@ -848,6 +900,9 @@ details.card > summary:hover { background: #fafbf8 } .nav-list { display: grid; gap: 2px } /* Vierundvierzig Pixel: die Flaeche, die ein Daumen zuverlaessig trifft. */ .nav-item { min-height: 44px; display: flex; align-items: center; gap: 12px; padding: 10px 14px; white-space: normal } + .nav-list > li:has(.nav-sub) { display: block } + .nav-sub { display: grid; margin: 2px 0 4px } + .nav-sub .nav-item { padding: 10px 14px 10px 44px } .shell-foot { margin-left: auto; border-left: 0; padding-left: 0 } .shell-version { display: none } } diff --git a/src/Controller/MailController.php b/src/Controller/MailController.php index 2b2600f..9f218a9 100644 --- a/src/Controller/MailController.php +++ b/src/Controller/MailController.php @@ -113,6 +113,19 @@ final class MailController extends AbstractController return $this->backToMail($request); } + /** „Letzte Versuche“ leeren: var/log/mail.log und mail.log.1. */ + #[Route('/%kant.admin_path%/system/e-mail/protokoll', name: 'admin_mail_clear', methods: ['POST'])] + public function clear(Request $request): Response + { + $this->denyAccessUnlessGranted('ROLE_ADMIN'); + if (!$this->isCsrfTokenValid('system', $request->request->getString('_csrf_token'))) { + return $this->backToMail($request, 'Deine Sitzung ist abgelaufen. Bitte melde dich erneut an.'); + } + $this->mail->clearLog(); + + return $this->backToMail($request, 'Das Protokoll ist leer.', 'ok'); + } + /** @param array{an: string, von: string} $eingabe */ private function mailAdressFehler(array $eingabe): ?string { diff --git a/src/Ops/MailCheck.php b/src/Ops/MailCheck.php index c6549bd..8cfc239 100644 --- a/src/Ops/MailCheck.php +++ b/src/Ops/MailCheck.php @@ -212,6 +212,16 @@ final class MailCheck return $eintraege; } + /** Protokoll leeren: die Datei und ihre ältere Hälfte. */ + public function clearLog(): void + { + foreach ([$this->logFile, $this->logFile.'.1'] as $datei) { + if (is_file($datei)) { + @unlink($datei); + } + } + } + public function logFile(): string { return $this->logFile; diff --git a/templates/admin/_mail_entry.html.twig b/templates/admin/_mail_entry.html.twig new file mode 100644 index 0000000..6398526 --- /dev/null +++ b/templates/admin/_mail_entry.html.twig @@ -0,0 +1,15 @@ +{# Ein Versuch, gleich gebaut für „Letzte Versuche“ und „Alle Wege ausprobiert“: Zustand, worum es + ging, wann - bei Fehlern der Hinweis vorn und die Meldung des Servers zugeklappt dahinter. #} +
  • + {{ eintrag.ok ? 'Angenommen' : 'Fehlgeschlagen' }} +
    +

    + {% if titel is defined %}{{ titel }}{% else %}{{ eintrag.anlass }} an {{ eintrag.an }}{% if eintrag.von %} von {{ eintrag.von }}{% endif %}{% if eintrag.weg is defined and eintrag.weg %} · {{ eintrag.weg }}{% endif %}{% endif %} +

    + {% if eintrag.zeit is defined %}

    {{ eintrag.zeit }} UTC

    {% endif %} + {% if not eintrag.ok %} + {% if eintrag.hinweis %}

    {{ eintrag.hinweis }}

    {% endif %} +
    Meldung des Servers
    {{ eintrag.meldung }}
    + {% endif %} +
    +
  • diff --git a/templates/admin/mail.html.twig b/templates/admin/mail.html.twig index b7a192f..1c2150c 100644 --- a/templates/admin/mail.html.twig +++ b/templates/admin/mail.html.twig @@ -10,120 +10,135 @@ {% set f = mailForm ?? {} %} {% set e = mail.einstellung %} {% set weg = f.weg ?? e.weg %} -
    +{% set beschreibung = { + smtp: 'Empfohlen. Ein echtes Postfach, unabhängig vom Hoster.', + native: 'Über den Server. Der Absender muss zu einer Domain beim Hoster gehören.', + 'sendmail-t': 'Über den Server, falls mail() fehlt.', + 'sendmail-bs': 'Über den Server, bei manchen Hostern gesperrt.', +} %} + +{% if mailNotice %} +

    {{ mailNotice.text }}

    +{% endif %} + +
    +

    Versandweg

    gilt für alle Formulare
    -
    -
    Eingestellt
    -
    - {% if mail.gueltig %} - {{ mail.art }}{% if mail.ziel %} {{ mail.ziel }}{% endif %}{% if mail.benutzer %} · angemeldet als {{ mail.benutzer }}{% endif %} - {% else %} - Die Einstellung ist ungültig. - {% endif %} -
    Gespeichert in {{ mail.quelle }} als MAILER_DSN.{% if mail.quelle != '.env' %} Sie hat dort Vorrang; speichern lässt sich hier nichts, testen schon.{% endif %} -
    -
    - - {% if mailNotice %} -

    {{ mailNotice.text }}

    - {% endif %} - - {% if mailResult %} -
    -

    {{ mailResult.ok ? 'Testmail an ' ~ mailResult.an ~ ' vom Mailserver angenommen' : 'Testmail an ' ~ mailResult.an ~ ' nicht versendet' }}{% if mailResult.weg is defined %} ({{ mailResult.weg }}){% endif %}.{% if not mailResult.ok %} {{ mailResult.meldung }}{% endif %}

    - {% if mailResult.hinweis %}

    {{ mailResult.hinweis }}

    {% endif %} - {% if mailResult.dialog %}
    Antworten des Mailservers
    {{ mailResult.dialog }}
    {% endif %} +
    + Eingestellt + {% if mail.gueltig %} + {{ mail.art }}{% if mail.ziel %} {{ mail.ziel }}{% endif %}{% if mail.benutzer %} als {{ mail.benutzer }}{% endif %} + {% else %} + Die Einstellung ist ungültig. + {% endif %} + in {{ mail.quelle }} als MAILER_DSN{% if mail.quelle != '.env' %} – hat Vorrang, speichern lässt sich hier nichts, testen schon{% endif %}
    - {% endif %} - {% if mailResults %} -
    -

    Alle Wege ausprobiert – an {{ (f.an ?? '') }}. Was angenommen wurde, kann gespeichert werden; angekommen ist es damit noch nicht, bitte im Postfach nachsehen.

    -
      - {% for r in mailResults %} -
    • - {{ r.ok ? 'Angenommen' : 'Fehlgeschlagen' }} · {{ r.weg }} - {% if not r.ok %}
      {{ r.meldung }}{% if r.hinweis %}
      {{ r.hinweis }}{% endif %}{% endif %} -
    • - {% endfor %} -
    -
    - {% endif %} - - - -
    - Versandweg - +
    + Weg wählen +
    + {% for key, w in mail.wege %} - + {% endfor %} -
    +
    + -
    - SMTP -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    +
    + SMTP-Zugang +
    +
    + +
    -

    587 mit STARTTLS ist üblich (IONOS, Microsoft 365: smtp.office365.com), 465 verschlüsselt von Anfang an. Das Passwort wird nie angezeigt und landet nur in .env.

    -
    - -
    - Testmail -
    -
    - - -
    -
    - - -
    +
    + +
    -

    Derselbe Absender wie bei den Formularen. Über den Server (mail(), sendmail) muss er zu einer eigenen Domain beim Hoster gehören, bei SMTP ist es meist das angemeldete Postfach.

    -
    +
    + + +
    +
    + + +
    +
    +

    587 mit STARTTLS ist üblich (IONOS, Microsoft 365: smtp.office365.com), 465 verschlüsselt von Anfang an. Das Passwort wird nie angezeigt und landet nur in .env.

    +
    -
    +
    + Testmail +
    +
    + + +
    +
    + + +
    +
    +

    Derselbe Absender wie bei den Formularen. Über den Server (mail(), sendmail) muss er zu einer eigenen Domain beim Hoster gehören, bei SMTP ist es meist das angemeldete Postfach.

    +
    - {% if mail.quelle == '.env' %}{% endif %}
    -

    „Alle Wege testen“ schickt je Weg eine Testmail – bis zu vier. Gespeichert wird {{ mail.quelle == '.env' ? 'nur mit „Diesen Weg speichern“' : 'hier nichts, weil MAILER_DSN in der Server-Umgebung steht' }}.

    - +

    „Alle Wege testen“ schickt je Weg eine Testmail – bis zu vier.

    + {% if mailResult %} +
    +

    {{ mailResult.ok ? 'Testmail an ' ~ mailResult.an ~ ' vom Mailserver angenommen' : 'Testmail an ' ~ mailResult.an ~ ' nicht versendet' }}{% if mailResult.weg is defined %} ({{ mailResult.weg }}){% endif %}.{% if not mailResult.ok %} {{ mailResult.meldung }}{% endif %}

    + {% if mailResult.hinweis %}

    {{ mailResult.hinweis }}

    {% endif %} + {% if mailResult.dialog %}
    Antworten des Mailservers
    {{ mailResult.dialog }}
    {% endif %} +
    + {% endif %} + + {% if mailResults %} +
    +

    Alle Wege ausprobiert – an {{ f.an ?? '' }}. Angenommen heißt noch nicht angekommen: bitte im Postfach nachsehen.

    +
      + {% for r in mailResults %} + {% include 'admin/_mail_entry.html.twig' with {eintrag: r, titel: r.weg} only %} + {% endfor %} +
    +
    + {% endif %} +
    -
    +
    + {% if mail.quelle == '.env' %} + + Erst mit „Speichern“ versenden die Formulare auf dem gewählten Weg. + {% else %} + MAILER_DSN steht in der Server-Umgebung – speichern lässt sich hier nichts. + {% endif %} +
    +
    -

    Letzte Versuche

    var/log/mail.log
    +
    +

    Letzte Versuche

    + {% if mail.protokoll is empty %} + var/log/mail.log + {% else %} +
    + + +
    + {% endif %} +
    {% if mail.protokoll is empty %}

    Noch nichts versendet – weder Testmail noch Formular.

    {% else %} -
      +
        {% for eintrag in mail.protokoll %} -
      • - {{ eintrag.ok ? 'Angenommen' : 'Fehlgeschlagen' }} · {{ eintrag.zeit }} UTC · {{ eintrag.anlass }} an {{ eintrag.an }}{% if eintrag.von %} von {{ eintrag.von }}{% endif %}{% if eintrag.weg is defined and eintrag.weg %} · {{ eintrag.weg }}{% endif %} - {% if not eintrag.ok %}
        {{ eintrag.meldung }}{% if eintrag.hinweis %}
        {{ eintrag.hinweis }}{% endif %}{% endif %} -
      • + {% include 'admin/_mail_entry.html.twig' with {eintrag: eintrag} only %} {% endfor %}
      +

      Die letzten zehn Versuche aus var/log/mail.log, neueste zuerst. Zeiten in UTC.

      {% endif %}
    diff --git a/templates/admin/shell.html.twig b/templates/admin/shell.html.twig index d3097b5..77c0192 100644 --- a/templates/admin/shell.html.twig +++ b/templates/admin/shell.html.twig @@ -47,10 +47,13 @@ {% if is_granted('ROLE_ADMIN') %}
  • {% include 'admin/icon.html.twig' with {name: 'theme'} only %}Design
  • {% endif %} {% if is_granted('ROLE_PUBLISHER') %}
  • {% include 'admin/icon.html.twig' with {name: 'settings'} only %}Einstellungen
  • {% endif %} {% if is_granted('ROLE_ADMIN') %}
  • {% include 'admin/icon.html.twig' with {name: 'system'} only %}System - {# Unterpunkte von System: eigene Seiten, damit „System“ nicht alles trägt. #} + {# Unterpunkte von System: eigene Seiten, damit „System“ nicht alles trägt. Sichtbar nur + im Bereich System, sonst stünde das Menü dauerhaft aufgeklappt. #} + {% if nav|default('') in ['system', 'mail'] %} + {% endif %}
  • {% endif %} {% if is_granted('ROLE_ADMIN') %}
  • {% include 'admin/icon.html.twig' with {name: 'tasks'} only %}Aufgaben
  • {% endif %} {% if is_granted('ROLE_ADMIN') %}
  • {% include 'admin/icon.html.twig' with {name: 'plugins'} only %}Plugins
  • {% endif %} diff --git a/templates/admin/system.html.twig b/templates/admin/system.html.twig index d843087..12be0ef 100644 --- a/templates/admin/system.html.twig +++ b/templates/admin/system.html.twig @@ -268,22 +268,22 @@

    Ein Inhaltspaket enthält Seiten mit allen Fassungen, Bereiche, Menüs, Medien und PDFs, Formulare, Weiterleitungen, Kategorien, Übersetzungen, Design und Einstellungen. Nicht darin: Anfragen, Statistik, Zugangsdaten zu Diensten (Turnstile) und – ohne Häkchen – die Konten.

    -

    Exportieren

    -
    + +

    Exportieren

    Nur nötig, wenn die andere Website noch kein Konto hat. Das Paket enthält dann die Passwort-Hashes – nicht weitergeben.

    - +
    -

    Importieren

    -
    + +

    Importieren

    - +

    Höchstens {{ uploadLimit }} je Upload (Einstellung des Hostings). Größere Pakete per FTP nach var/pakete/ legen, dann erscheinen sie hier zur Auswahl.

    {% if packages is not empty %} -
    +
    Oder ein Paket aus var/pakete/ {% for package in packages %}
    diff --git a/tests/SystemScreenTest.php b/tests/SystemScreenTest.php index f9688b5..b551eab 100644 --- a/tests/SystemScreenTest.php +++ b/tests/SystemScreenTest.php @@ -412,6 +412,21 @@ final class SystemScreenTest extends WebTestCase $client->followRedirect(); self::assertStringContainsString('in der Server-Umgebung gesetzt', (string) $client->getResponse()->getContent()); + // Umut: „Logs leeren Funktion brauch ich“ - nur mit Token, danach ist die Liste leer. + $client->request('POST', '/admin/system/e-mail/protokoll', ['_csrf_token' => 'erfunden']); + self::assertFileExists($this->path.'-mail.log', 'Ohne Token bleibt das Protokoll.'); + $client->request('POST', '/admin/system/e-mail/protokoll', ['_csrf_token' => $token]); + self::assertFileDoesNotExist($this->path.'-mail.log'); + $client->followRedirect(); + $html = (string) $client->getResponse()->getContent(); + self::assertStringContainsString('Das Protokoll ist leer.', $html); + self::assertStringContainsString('Noch nichts versendet', $html); + self::assertStringNotContainsString('Protokoll leeren', $html, 'Ohne Einträge gibt es nichts zu leeren.'); + + // Der Unterpunkt steht nur im Bereich System offen, nicht dauerhaft. + $client->request('GET', '/admin/settings'); + self::assertStringNotContainsString('href="/admin/system/e-mail"', (string) $client->getResponse()->getContent()); + // Nur für Administratoren, und nur mit Token. $client->request('POST', '/admin/system/e-mail', ['_csrf_token' => 'erfunden', 'aktion' => 'test', 'weg' => 'smtp', 'server' => 'cms.test', 'to' => 'umut@cms.test']); self::assertEmailCount(0); @@ -420,6 +435,8 @@ final class SystemScreenTest extends WebTestCase self::assertSame(403, $redaktion->getResponse()->getStatusCode()); $redaktion->request('POST', '/admin/system/e-mail', ['_csrf_token' => $token, 'aktion' => 'test', 'weg' => 'smtp', 'server' => 'cms.test', 'to' => 'umut@cms.test']); self::assertSame(403, $redaktion->getResponse()->getStatusCode()); + $redaktion->request('POST', '/admin/system/e-mail/protokoll', ['_csrf_token' => $token]); + self::assertSame(403, $redaktion->getResponse()->getStatusCode()); } private function login(string $username = 'admin'): KernelBrowser