Posta configurabile dall'app, contatti dei proclamatori, conferma email

- Server di posta in Impostazioni (password cifrata, email di prova): se
  compilato prende il posto del .env; non si esporta e l'import XML non lo
  cancella. MAIL_SCHEME=tls nel .env.example non era valido per Laravel 11.
- Proclamatori con email e telefono facoltativi, cifrati, nell'XML e
  cancellati dall'anonimizzazione; il numero apre WhatsApp.
- Dopo l'assegnazione un riquadro in evidenza con il link del PDF,
  "Invia su WhatsApp" con il messaggio pronto e la scelta "inviato /
  non ancora", che resta finché non si sceglie.
- Conferma dell'email obbligatoria per tutti gli utenti, anche esistenti:
  email in italiano, link valido senza login, nuova conferma a ogni cambio
  di indirizzo, "Reinvia conferma" in Utenti. Senza conferma restano
  raggiungibili profilo, cambio password e, per l'admin, il server di
  posta; comando utenti:conferma-email per l'emergenza.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-12 17:12:31 +02:00
parent e7148c7b32
commit fc8623f7db
35 changed files with 1737 additions and 14 deletions

View File

@@ -0,0 +1,121 @@
<?php
namespace App\Services;
use App\Models\Setting;
use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Mail\MailManager;
use Illuminate\Support\Facades\Mail;
use RuntimeException;
/**
* Il server di posta configurato dall'applicazione (Impostazioni → Posta).
*
* Se in Impostazioni c'è un server, vale quello; altrimenti resta quello del
* .env, come prima. La configurazione si applica quando Laravel crea il
* sistema di posta (AppServiceProvider) e prima di ogni job della coda,
* perché il worker vive a lungo e avrebbe tenuto quella del primo invio.
*/
class ConfigurazionePosta
{
public const CIFRATURA_AUTOMATICA = 'auto';
public const CIFRATURA_SSL = 'ssl';
public const CIFRATURA_NESSUNA = 'nessuna';
public const CIFRATURE = [
self::CIFRATURA_AUTOMATICA => 'Automatica (STARTTLS se il server la offre, di solito porta 587)',
self::CIFRATURA_SSL => 'SSL/TLS dall\'inizio (di solito porta 465)',
self::CIFRATURA_NESSUNA => 'Nessuna (solo per server interni)',
];
/** C'è un server di posta configurato dall'applicazione. */
public function configurataNellApp(): bool
{
return trim((string) Setting::instance()->mail_host) !== '';
}
/**
* Le email partono davvero: server dall'app, oppure un mailer del .env che
* non sia "log" o "array" (che le scrivono nel log o le buttano).
*/
public function funzionante(): bool
{
if ($this->configurataNellApp()) {
return true;
}
$mailer = (string) config('mail.default');
return ! in_array($mailer, ['log', 'array'], true)
&& ($mailer !== 'smtp' || trim((string) config('mail.mailers.smtp.host')) !== '');
}
/** Porta la configurazione dell'app nel sistema di posta di Laravel. */
public function applica(?MailManager $manager = null): void
{
try {
$impostazioni = Setting::instance();
} catch (\Throwable) {
// Database non ancora pronto (prima installazione, migrazioni in
// corso): resta il .env.
return;
}
if (trim((string) $impostazioni->mail_host) === '') {
return;
}
try {
$password = $impostazioni->mail_password;
} catch (DecryptException) {
// Cifrata con un'altra APP_KEY: meglio tentare senza password (e
// ricevere dal server un errore che dice "autenticazione") che
// far cadere ogni pagina che tocca la posta.
$password = null;
}
$cifratura = $impostazioni->mail_cifratura ?: self::CIFRATURA_AUTOMATICA;
config([
'mail.default' => 'smtp',
'mail.mailers.smtp' => [
'transport' => 'smtp',
'scheme' => $cifratura === self::CIFRATURA_SSL ? 'smtps' : 'smtp',
'host' => $impostazioni->mail_host,
'port' => $impostazioni->mail_port ?: ($cifratura === self::CIFRATURA_SSL ? 465 : 587),
'username' => $impostazioni->mail_username ?: null,
'password' => $password ?: null,
'timeout' => 20,
'local_domain' => config('mail.mailers.smtp.local_domain'),
// Senza, Symfony prova STARTTLS da solo appena il server lo offre.
'auto_tls' => $cifratura !== self::CIFRATURA_NESSUNA,
],
'mail.from.address' => $impostazioni->mail_from_address ?: config('mail.from.address'),
'mail.from.name' => $impostazioni->mail_from_name ?: ($impostazioni->congregazione_nome ?: config('mail.from.name')),
]);
// Un mailer già creato tiene la configurazione con cui è nato.
($manager ?? app('mail.manager'))->purge('smtp');
}
/**
* Invia l'email di prova. Lancia RuntimeException con un messaggio
* leggibile se il server rifiuta o non risponde.
*/
public function inviaProva(string $destinatario): void
{
$this->applica();
try {
Mail::raw(
"Questa è un'email di prova di TerManager2.\n\nSe la stai leggendo, il server di posta è configurato correttamente: "
. 'gli utenti riceveranno le email per confermare il loro indirizzo.',
fn ($messaggio) => $messaggio->to($destinatario)->subject('TerManager2 — email di prova'),
);
} catch (\Throwable $e) {
throw new RuntimeException('Il server di posta ha rifiutato l\'invio: ' . $e->getMessage(), 0, $e);
}
}
}