Files
KantSites/templates/admin/mail.html.twig
T
kantarciandClaude Fable 5.2 ead47cc924 Keep old pages readable, detect stale settings writes, make backups honest
- Own CSS is parsed on write only. A page whose stored CSS no longer
  passes a stricter rule answers 200 and opens in the editor; that one
  block loses its CSS. A document read several times in one request is
  validated once.
- Settings and theme refuse a write from an outdated form.
- A backup fails when a media file does not arrive completely and
  leaves no half directory behind; rotation counts intact backups only.
- The archive update takes its backup behind the maintenance page.
- A package import puts all media in place before removing the old
  ones, reports honestly when that fails and keeps a maintenance state
  that was already on.
- Restoring replaces the SQLite file in one move and points at pending
  migrations.
- Six irreversible actions ask first, in a dialog of the admin's own.
- Ctrl+S in the editor takes the field being typed in along; Delete
  no longer removes a block while a dialog is open.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 11:41:05 +01:00

148 lines
8.6 KiB
Twig
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.
{% extends 'admin/shell.html.twig' %}
{% set nav = 'mail' %}
{% block title %}E-Mail-Versand{% endblock %}
{% block heading %}E-Mail-Versand{% endblock %}
{% block lead %}Wie Formulare ihre Mails verschicken – ausprobieren, speichern und sehen, woran es hakt.{% endblock %}
{% block content %}
{# E-Mail-Versand (ADR 121, 122): Weg wählen und ausprobieren, alle Wege des Servers testen,
speichern - und die letzten Versuche mit Ursache. Das SMTP-Passwort steht nie im Formular. #}
{% 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 %}
<p class="notice {{ mailNotice.art == 'ok' ? 'ok' : 'error' }}" role="{{ mailNotice.art == 'ok' ? 'status' : 'alert' }}">{{ mailNotice.text }}</p>
{% endif %}
<form class="card mail-form" id="mail" action="{{ path('admin_mail_save') }}" method="post">
<input type="hidden" name="_csrf_token" value="{{ csrf_token('system') }}">
<div class="card-head"><h2>Versandweg</h2><span class="muted">gilt für alle Formulare</span></div>
<div class="card-body">
<div class="mail-current">
<span class="muted">Eingestellt</span>
{% if mail.gueltig %}
<strong>{{ mail.art }}</strong>{% if mail.ziel %} <code>{{ mail.ziel }}</code>{% endif %}{% if mail.benutzer %} <span class="muted">als</span> <code>{{ mail.benutzer }}</code>{% endif %}
{% else %}
<strong class="danger-text">Die Einstellung ist ungültig.</strong>
{% endif %}
<span class="muted mail-current-source">in <code>{{ mail.quelle }}</code> als <code>MAILER_DSN</code>{% if mail.quelle != '.env' %} – hat Vorrang, speichern lässt sich hier nichts, testen schon{% endif %}</span>
</div>
<fieldset class="card-section">
<legend>Weg wählen</legend>
<div class="option-list">
<label class="option" for="weg-smtp"><input id="weg-smtp" type="radio" name="weg" value="smtp"{% if weg == 'smtp' %} checked{% endif %}><span><strong>SMTP mit Postfach</strong><span>{{ beschreibung.smtp }}</span></span></label>
{% for key, w in mail.wege %}
<label class="option" for="weg-{{ key }}"><input id="weg-{{ key }}" type="radio" name="weg" value="{{ key }}"{% if weg == key %} checked{% endif %}><span><strong>{{ w.label }}</strong><span>{{ beschreibung[key] ?? '' }}</span></span></label>
{% endfor %}
</div>
</fieldset>
<fieldset class="card-section smtp-fields">
<legend>SMTP-Zugang</legend>
<div class="mail-grid">
<div>
<label for="mail-server">Server</label>
<input id="mail-server" name="server" type="text" inputmode="url" autocomplete="off" spellcheck="false" maxlength="200" placeholder="smtp.ionos.de" value="{{ f.server ?? e.server }}">
</div>
<div>
<label for="mail-port">Port</label>
<input id="mail-port" name="port" type="number" min="1" max="65535" value="{{ f.port ?? e.port }}" aria-describedby="mail-port-help">
</div>
<div>
<label for="mail-user">Benutzer</label>
<input id="mail-user" name="benutzer" type="text" autocomplete="off" spellcheck="false" maxlength="200" placeholder="formular@meine-firma.de" value="{{ f.benutzer ?? e.benutzer }}">
</div>
<div>
<label for="mail-pass">Passwort</label>
<input id="mail-pass" name="passwort" type="password" autocomplete="new-password" maxlength="500" placeholder="{{ e.passwort ? 'gespeichert – leer lassen zum Behalten' : '' }}">
</div>
</div>
<p class="field-help" id="mail-port-help">587 mit STARTTLS ist üblich (IONOS, Microsoft 365: <code>smtp.office365.com</code>), 465 verschlüsselt von Anfang an. Das Passwort wird nie angezeigt und landet nur in <code>.env</code>.</p>
</fieldset>
<fieldset class="card-section">
<legend>Testmail</legend>
<div class="mail-grid">
<div>
<label for="mail-to">An</label>
<input id="mail-to" name="to" type="email" autocomplete="email" maxlength="200" placeholder="name@beispiel.de" value="{{ f.an ?? (mailResult.an ?? '') }}">
</div>
<div>
<label for="mail-from">Absender</label>
<input id="mail-from" name="from" type="email" maxlength="200" placeholder="leer: dieselbe Adresse" value="{{ f.von ?? mail.absender }}" aria-describedby="mail-from-help">
</div>
</div>
<p class="field-help" id="mail-from-help">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.</p>
<div class="button-row">
<button class="secondary" type="submit" name="aktion" value="test">Diesen Weg testen</button>
<button class="secondary" type="submit" name="aktion" value="alle">Alle Wege testen</button>
</div>
<p class="field-help">„Alle Wege testen“ schickt je Weg eine Testmail – bis zu vier.</p>
{% if mailResult %}
<div class="notice {{ mailResult.ok ? 'ok' : 'error' }} mail-result" role="{{ mailResult.ok ? 'status' : 'alert' }}">
<p><strong>{{ mailResult.ok ? 'Testmail an ' ~ mailResult.an ~ ' vom Mailserver angenommen' : 'Testmail an ' ~ mailResult.an ~ ' nicht versendet' }}</strong>{% if mailResult.weg is defined %} ({{ mailResult.weg }}){% endif %}.{% if not mailResult.ok %} {{ mailResult.meldung }}{% endif %}</p>
{% if mailResult.hinweis %}<p>{{ mailResult.hinweis }}</p>{% endif %}
{% if mailResult.dialog %}<details><summary>Antworten des Mailservers</summary><pre>{{ mailResult.dialog }}</pre></details>{% endif %}
</div>
{% endif %}
{% if mailResults %}
<div class="mail-result">
<p class="mail-result-head"><strong>Alle Wege ausprobiert</strong> <span class="muted">– an {{ f.an ?? '' }}. Angenommen heißt noch nicht angekommen: bitte im Postfach nachsehen.</span></p>
<ul class="mail-log">
{% for r in mailResults %}
{% include 'admin/_mail_entry.html.twig' with {eintrag: r, titel: r.weg} only %}
{% endfor %}
</ul>
</div>
{% endif %}
</fieldset>
</div>
<div class="card-foot form-foot">
{% if mail.quelle == '.env' %}
<button class="primary" type="submit" name="aktion" value="speichern">Diesen Weg speichern</button>
<span class="muted">Erst mit „Speichern“ versenden die Formulare auf dem gewählten Weg.</span>
{% else %}
<span class="muted">MAILER_DSN steht in der Server-Umgebung – speichern lässt sich hier nichts.</span>
{% endif %}
</div>
</form>
<div class="card">
<div class="card-head">
<h2>Letzte Versuche</h2>
{% if mail.protokoll is empty %}
<span class="muted">var/log/mail.log</span>
{% else %}
<form action="{{ path('admin_mail_clear') }}" method="post" data-confirm-title="Versandprotokoll leeren?" data-confirm-text="Alle Einträge in var/log/mail.log werden entfernt." data-confirm-label="Protokoll leeren" data-confirm-variant="danger">
<input type="hidden" name="_csrf_token" value="{{ csrf_token('system') }}">
<button class="text-button" type="submit">Protokoll leeren</button>
</form>
{% endif %}
</div>
<div class="card-body">
{% if mail.protokoll is empty %}
<p class="muted">Noch nichts versendet – weder Testmail noch Formular.</p>
{% else %}
<ul class="mail-log">
{% for eintrag in mail.protokoll %}
{% include 'admin/_mail_entry.html.twig' with {eintrag: eintrag} only %}
{% endfor %}
</ul>
<p class="field-help">Die letzten zehn Versuche aus <code>var/log/mail.log</code>, neueste zuerst. Zeiten in UTC.</p>
{% endif %}
</div>
</div>
<p class="footnote">KantSites · Arbeitsstand</p>
{% endblock %}