История коммитов

.
i18n(admin): translate the mail settings screen into russian
The 28 strings of the new screen, appended to ru.po without touching what was there, and the dictionary regenerated from it. The other languages come from Crowdin.
.
chore(i18n): regenerate the message templates
The templates had drifted from the sources: apart from the strings of the new mail settings screen, which land in the admin domain, every change is a line number.
.
fix(security): keep a policy from widening the link schemes of another
The library keeps its scheme validators in a registry shared by the whole process and, with the override it enables by default, hands out one that is already there without checking it against the policy being applied. One policy allowing http therefore let every policy built after it through as well, however narrow its own list of schemes - so a policy declaring https only accepted http, mailto and ftp as soon as anything else had sanitized a link first.

Not an XSS hole: javascript: and data: are refused by other means. What leaked are the ordinary schemes a policy deliberately left out.

This is what the flaky CustomHtmlPolicyTest was reporting: the suite runs defects first, so a failure moved the test to the front of the queue, where nothing had built a purifier yet and it passed again. With --order-by=default it failed every time. The regression test now builds the permissive policy first, the way the defect needs.
.
docs: bump the documentation submodule
.
feat(admin): edit the mail settings from the panel
The mail of a site could only be configured by editing a file, and whether the settings worked could only be found out by waiting for the queue and reading a log. The panel gains a screen for the connection string, the transport, the smtp parameters and the staging redirection, behind the permission that guards the settings of the site.

The settings are checked against the dsn resolver before they are written: a configuration the mailer cannot build a transport from would take the mail of the site down until somebody edited the file by hand. Signing and the queue stay in the file - they are set once and are not what this screen is opened for.

The test message is sent immediately rather than queued, so the answer of the mail server, including the text of its error, is shown on the same screen where the settings were entered.
.
feat(mail): send every message to one mailbox on a staging copy
A copy of a live site runs with the database of the live one, so the first test of the registration writes to a real visitor. Setting redirect_to sends every message to the given mailbox instead, keeping the real recipients in the X-Original-To header.

Both the headers and the smtp envelope are redirected: changing only the headers would still deliver the message to the address it was addressed to. The listener is attached only when the setting is filled in, so a live site dispatches nothing.
.
docs: bump the documentation submodule
.
refactor(mail): queue a message through a typed dto
Every producer wrote the queue row by hand: the recipient, the subject and the template variables shared one untyped array, so a misspelled key was found by the cron a minute later, if at all. A message is now queued through MailQueueInterface::push() with a QueuedEmailDTO, and the six call sites in registration, profile and contacts no longer touch the model at all.

Putting a message in and taking messages out are separate interfaces on purpose: a module needs neither claiming nor retrying, and the sender needs no producer methods.

The addresses of a message are validated as it is queued, so a broken one is reported where the mistake was made instead of exhausting the delivery attempts first. The contact form catches that: its notification address comes from the settings of the site rather than a validated form, and a typo there must not fail the visitor whose message was already saved.

The dto also closes a hole of the old shape: template variables are merged last, so a variable named email_to can no longer redirect the message.
.
docs: bump the documentation submodule
.
feat(mail): send every message as html and text, and address it properly
A message carrying only html reads as bulk mail to a spam filter and is unreadable in a client that shows text. Each message now goes out as both: the text form comes from a .txt.twig template beside the html one - written here for all six emails of the theme - and is derived from the markup by the converter of symfony/mime when a template does not provide one. The text templates reuse the strings of the html ones, so nothing new has to be translated.

Twig now picks its escaping strategy from the file name instead of always escaping as html. Every existing .twig template is html and is escaped exactly as before; a .txt.twig one is not markup, and escaping it turned the ampersand of a confirmation link into & in the text a reader follows.

The queue understands reply_to, reply_to_name, cc and bcc, the last two taking one address or several. The contact form notification sets reply-to to the visitor who wrote it, so answering the notification answers them rather than the mailbox of the site.

Messages can be signed with dkim, configured by the dkim section. A key that cannot be loaded is logged and ignored: the mail of a site must not stop because of it.