add SMTP sender
This commit is contained in:
@@ -1 +1 @@
|
||||
{"version":2,"defects":{"Tests\\Feature\\ExampleTest::test_the_application_returns_a_successful_response":7,"Tests\\Feature\\CalendarEventsTest::test_generate_settimanale_events_produces_weekly_occurrences":7},"times":{"Tests\\Unit\\ExampleTest::test_that_true_is_true":0,"Tests\\Feature\\ExampleTest::test_the_application_returns_a_successful_response":0.111,"Tests\\Feature\\CalendarEventsTest::test_find_first_sunday_of_june_2026":0.034,"Tests\\Feature\\CalendarEventsTest::test_find_second_saturday_of_june_2026":0.001,"Tests\\Feature\\CalendarEventsTest::test_find_fifth_sunday_returns_null_when_not_in_month":0.001,"Tests\\Feature\\CalendarEventsTest::test_generate_mensile_events_for_all_months":0.025,"Tests\\Feature\\CalendarEventsTest::test_generate_mensile_events_respects_selected_months":0.001,"Tests\\Feature\\CalendarEventsTest::test_generate_annuale_events_produces_correct_dates":0.001,"Tests\\Feature\\CalendarEventsTest::test_generate_settimanale_events_produces_weekly_occurrences":0.004}}
|
||||
{"version":2,"defects":{"Tests\\Feature\\ExampleTest::test_the_application_returns_a_successful_response":7,"Tests\\Feature\\CalendarEventsTest::test_generate_settimanale_events_produces_weekly_occurrences":7},"times":{"Tests\\Unit\\ExampleTest::test_that_true_is_true":0.005,"Tests\\Feature\\ExampleTest::test_the_application_returns_a_successful_response":0.049,"Tests\\Feature\\CalendarEventsTest::test_find_first_sunday_of_june_2026":0.017,"Tests\\Feature\\CalendarEventsTest::test_find_second_saturday_of_june_2026":0.001,"Tests\\Feature\\CalendarEventsTest::test_find_fifth_sunday_returns_null_when_not_in_month":0.001,"Tests\\Feature\\CalendarEventsTest::test_generate_mensile_events_for_all_months":0.013,"Tests\\Feature\\CalendarEventsTest::test_generate_mensile_events_respects_selected_months":0.001,"Tests\\Feature\\CalendarEventsTest::test_generate_annuale_events_produces_correct_dates":0.001,"Tests\\Feature\\CalendarEventsTest::test_generate_settimanale_events_produces_weekly_occurrences":0.003}}
|
||||
@@ -608,6 +608,72 @@ php artisan route:list --name=email
|
||||
|
||||
(Last updated: 26 Maggio 2026 - Fix Salvataggio is_admin)
|
||||
|
||||
### 26 Maggio 2026 - Sender Accounts (Mittenti Aggiuntivi solo invio)
|
||||
|
||||
- **Nuova tabella**: `sender_accounts` con migration `2026_05_26_000002`
|
||||
- **Colonne**: `id`, `email_address` (UNIQUE), `email_name`, `smtp_host`, `smtp_port`, `smtp_encryption`, `smtp_username`, `smtp_password` (Crypt), `reply_to`, `verify_email`, `note`, `is_active`, timestamps
|
||||
- **Model `SenderAccount`**: `$fillable`, `$casts`, metodi:
|
||||
- `getDecryptedPassword()` — decripta password SMTP con fallback
|
||||
- `sanitizeName()` — pulisce il nome visualizzato
|
||||
- `getReplyTo()` — priorità: `reply_to` → `verify_email` → null
|
||||
- `buildDsn()` — costruisce DSN Symfony Mailer
|
||||
- `buildSymfonyEmail(to, subject, body)` — costruisce email con From, Reply-To
|
||||
- `sendEmail(to, subject, body, attachmentPaths)` — invia via Symfony Mailer
|
||||
- `sendReport(subject, body)` — invia report riepilogo a `verify_email`
|
||||
- **Logica Reply-To**: se impostato `reply_to` (es. `no-reply@parrocchia.it`) → quello; fallback `verify_email`; nessun Reply-To se entrambi null
|
||||
- **Report invio**: dopo ogni invio massivo via sender, una email di riepilogo viene inviata a `verify_email` con: mittente, oggetto, totale, ok, falliti, dettaglio errori
|
||||
- **Migration `2026_05_26_000003`**: aggiunte colonne `log_falliti` (JSON), `mittente_nome`, `mittente_email` a `mailing_messaggi`
|
||||
- **Model `MailingMessaggio`**: aggiornato `$fillable` e `$casts` per `log_falliti` (array)
|
||||
- **CRUD in `EmailSettingsController`**: 4 nuovi metodi protetti da `authorizeWrite('settings')`:
|
||||
- `senderStore()` — crea nuovo sender con password criptata
|
||||
- `senderUpdate()` — modifica sender, password opzionale
|
||||
- `senderDestroy()` — elimina sender
|
||||
- `senderTestSmtp()` — test invio email via sender
|
||||
- **Routes** (6 nuove):
|
||||
- `POST /impostazioni/sender` → `senderStore`
|
||||
- `PUT /impostazioni/sender/{id}` → `senderUpdate`
|
||||
- `DELETE /impostazioni/sender/{id}` → `senderDestroy`
|
||||
- `POST /impostazioni/sender/{id}/test` → `senderTestSmtp`
|
||||
- `GET /mailing/invio` → `MailingController@invio` (prima mancante)
|
||||
- `POST /mailing/invio/elabora` → `MailingController@invioElabora` (prima mancante)
|
||||
- **`EmailController@compose`**: passa `$senderAccounts` alla view; nuova view mostra dropdown "Da (mittente)" prima dei destinatari
|
||||
- **`EmailController@send`** modificato:
|
||||
- Se `mittente_id` > 0 → usa `SenderAccount::sendEmail()` con allegati; salta store sent message
|
||||
- Se `mittente_id` = 0 o assente → comportamento attuale con `EmailSetting`
|
||||
- Processa allegati `allegati[]` e `documenti_selezionati`
|
||||
- Nuovo helper: `sendWithSender()`, `processAttachments()`, `resolveAttachmentPaths()`
|
||||
- `sendViaImap()` ora accetta `$attachmentPaths` e li attacha all'email
|
||||
- **`MailingController`** riscritto con invio SMTP reale:
|
||||
- `invia()` — invio 1:1 a ogni destinatario, log su `mailing_messaggi`
|
||||
- `invioElabora()` — invio massivo 1:1 per ogni email da liste multiple, log su `mailing_messaggi`
|
||||
- Tracking: stato `in_coda` → `inviato`/`parziale`/`fallito`, conteggi ok/fail, log_falliti JSON
|
||||
- Summary email a `verify_email` dopo invio massivo
|
||||
- `sendViaSystem()` — invio via EmailSetting (fallback quando nessun sender selezionato)
|
||||
- `resolveMailingAttachmentPaths()` — risolve path file da Documenti selezionati
|
||||
- **View `email-settings/index.blade.php`**:
|
||||
- Nuovo tab "Mittenti Aggiuntivi" nella sidebar
|
||||
- Tabella con email, nome, SMTP, Reply-To, Verify, Stato, Azioni (Modifica/Test/Elimina)
|
||||
- Modal CRUD per creare/modificare sender con tutti i campi
|
||||
- JS: `resetSenderForm()`, `editSender(id)`, `testSenderSmtp(id)`
|
||||
- Sezione informativa "Come funziona"
|
||||
- **View `email/compose.blade.php`**: dropdown "Da (mittente)" se ci sono sender attivi (default: Sistema)
|
||||
- **View `mailing/nuovo.blade.php`**: dropdown "Mittente" se ci sono sender attivi
|
||||
- **View `mailing/invio.blade.php`**: dropdown "Mittente" se ci sono sender attivi
|
||||
- **Files creati**:
|
||||
- `database/migrations/2026_05_26_000002_create_sender_accounts_table.php`
|
||||
- `database/migrations/2026_05_26_000003_add_log_falliti_to_mailing_messaggi.php`
|
||||
- `app/Models/SenderAccount.php`
|
||||
- **Files modificati**:
|
||||
- `app/Models/MailingMessaggio.php`
|
||||
- `app/Http/Controllers/Admin/EmailSettingsController.php`
|
||||
- `app/Http/Controllers/EmailController.php`
|
||||
- `app/Http/Controllers/MailingController.php`
|
||||
- `routes/web.php`
|
||||
- `resources/views/admin/email-settings/index.blade.php`
|
||||
- `resources/views/email/compose.blade.php`
|
||||
- `resources/views/mailing/nuovo.blade.php`
|
||||
- `resources/views/mailing/invio.blade.php`
|
||||
|
||||
### 26 Maggio 2026 - Export ICS/iCal Eventi
|
||||
- **Nuovo Service**: `app/Services/IcsExportService.php` — generazione file ICS RFC 5545
|
||||
- `generateSingle(Evento)` — esporta un singolo evento
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\EmailSetting;
|
||||
use App\Models\SenderAccount;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
|
||||
@@ -13,7 +14,8 @@ class EmailSettingsController extends Controller
|
||||
{
|
||||
$this->authorizeWrite('settings');
|
||||
$settings = EmailSetting::first() ?? new EmailSetting();
|
||||
return view('admin.email-settings.index', compact('settings'));
|
||||
$senderAccounts = SenderAccount::orderBy('email_address')->get();
|
||||
return view('admin.email-settings.index', compact('settings', 'senderAccounts'));
|
||||
}
|
||||
|
||||
public function save(Request $request)
|
||||
@@ -183,4 +185,91 @@ class EmailSettingsController extends Controller
|
||||
return back()->with('error', 'Errore sync: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function senderStore(Request $request)
|
||||
{
|
||||
$this->authorizeWrite('settings');
|
||||
$data = $request->validate([
|
||||
'email_address' => 'required|email|max:255|unique:sender_accounts,email_address',
|
||||
'email_name' => 'nullable|string|max:255',
|
||||
'smtp_host' => 'nullable|string|max:255',
|
||||
'smtp_port' => 'nullable|integer|min:1|max:65535',
|
||||
'smtp_encryption' => 'nullable|in:tls,ssl,none',
|
||||
'smtp_username' => 'nullable|string|max:255',
|
||||
'smtp_password' => 'nullable|string',
|
||||
'reply_to' => 'nullable|email|max:255',
|
||||
'verify_email' => 'nullable|email|max:255',
|
||||
'note' => 'nullable|string|max:500',
|
||||
'is_active' => 'boolean',
|
||||
]);
|
||||
|
||||
if (!empty($data['smtp_password'])) {
|
||||
$data['smtp_password'] = Crypt::encryptString($data['smtp_password']);
|
||||
} else {
|
||||
unset($data['smtp_password']);
|
||||
}
|
||||
|
||||
$sender = SenderAccount::create($data);
|
||||
|
||||
return back()->with('success', 'Mittente "' . $sender->email_address . '" creato con successo.');
|
||||
}
|
||||
|
||||
public function senderUpdate(Request $request, int $id)
|
||||
{
|
||||
$this->authorizeWrite('settings');
|
||||
$sender = SenderAccount::findOrFail($id);
|
||||
|
||||
$data = $request->validate([
|
||||
'email_address' => 'required|email|max:255|unique:sender_accounts,email_address,' . $id,
|
||||
'email_name' => 'nullable|string|max:255',
|
||||
'smtp_host' => 'nullable|string|max:255',
|
||||
'smtp_port' => 'nullable|integer|min:1|max:65535',
|
||||
'smtp_encryption' => 'nullable|in:tls,ssl,none',
|
||||
'smtp_username' => 'nullable|string|max:255',
|
||||
'smtp_password' => 'nullable|string',
|
||||
'reply_to' => 'nullable|email|max:255',
|
||||
'verify_email' => 'nullable|email|max:255',
|
||||
'note' => 'nullable|string|max:500',
|
||||
'is_active' => 'boolean',
|
||||
]);
|
||||
|
||||
if (!empty($data['smtp_password'])) {
|
||||
$data['smtp_password'] = Crypt::encryptString($data['smtp_password']);
|
||||
} else {
|
||||
unset($data['smtp_password']);
|
||||
}
|
||||
|
||||
$sender->update($data);
|
||||
|
||||
return back()->with('success', 'Mittente "' . $sender->email_address . '" aggiornato.');
|
||||
}
|
||||
|
||||
public function senderDestroy(int $id)
|
||||
{
|
||||
$this->authorizeDelete('settings');
|
||||
$sender = SenderAccount::findOrFail($id);
|
||||
$sender->delete();
|
||||
|
||||
return back()->with('success', 'Mittente eliminato.');
|
||||
}
|
||||
|
||||
public function senderTestSmtp(Request $request, int $id)
|
||||
{
|
||||
$this->authorizeWrite('settings');
|
||||
$request->validate(['email' => 'required|email']);
|
||||
|
||||
$sender = SenderAccount::findOrFail($id);
|
||||
|
||||
try {
|
||||
$sender->sendEmail(
|
||||
$request->email,
|
||||
'Test Configurazione SMTP - ' . config('app.name'),
|
||||
'Test email da ' . config('app.name') . ' - Configurazione SMTP mittente "' . $sender->email_address . '" funziona!'
|
||||
);
|
||||
|
||||
return response()->json(['success' => true, 'message' => 'Email di test inviata a ' . $request->email]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json(['success' => false, 'message' => 'Errore invio: ' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ use App\Models\EmailFolder;
|
||||
use App\Models\EmailMessage;
|
||||
use App\Models\EmailAttachment;
|
||||
use App\Models\EmailSetting;
|
||||
use App\Models\SenderAccount;
|
||||
use App\Models\MailingList;
|
||||
use App\Models\Gruppo;
|
||||
use App\Models\Individuo;
|
||||
@@ -90,6 +91,7 @@ class EmailController extends Controller
|
||||
$mailingLists = MailingList::where('attiva', true)->get();
|
||||
$gruppi = Gruppo::orderBy('nome')->get();
|
||||
$individui = Individuo::orderBy('cognome')->orderBy('nome')->get();
|
||||
$senderAccounts = SenderAccount::active()->get();
|
||||
|
||||
$prefill = [];
|
||||
if ($request->reply_to) {
|
||||
@@ -108,7 +110,7 @@ class EmailController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
return view('email.compose', compact('folders', 'mailingLists', 'gruppi', 'individui', 'prefill'));
|
||||
return view('email.compose', compact('folders', 'mailingLists', 'gruppi', 'individui', 'prefill', 'senderAccounts'));
|
||||
}
|
||||
|
||||
public function send(Request $request)
|
||||
@@ -125,11 +127,6 @@ class EmailController extends Controller
|
||||
|
||||
$validated = $request->validate($rules);
|
||||
|
||||
$settings = EmailSetting::getActive();
|
||||
if (!$settings) {
|
||||
return back()->with('error', 'Account email non configurato.');
|
||||
}
|
||||
|
||||
$recipients = $this->resolveRecipients($request);
|
||||
|
||||
\Illuminate\Support\Facades\Log::info('Email send attempt', [
|
||||
@@ -137,44 +134,94 @@ class EmailController extends Controller
|
||||
'recipients_count' => count($recipients),
|
||||
'subject' => $validated['subject'],
|
||||
'destinatario_tipo' => $request->destinatario_tipo,
|
||||
'to' => $validated['to'] ?? null,
|
||||
'individui' => $request->individui,
|
||||
'gruppo_id' => $request->gruppo_id,
|
||||
'mailing_list_id' => $request->mailing_list_id,
|
||||
'mittente_id' => $request->mittente_id,
|
||||
]);
|
||||
|
||||
if (empty($recipients)) {
|
||||
return back()->with('error', 'Nessun destinatario trovato. Seleziona almeno un destinatario dal menu a tendina o inserisci un indirizzo email manuale.')->withInput();
|
||||
}
|
||||
|
||||
$useSender = $request->filled('mittente_id') && $request->mittente_id > 0;
|
||||
|
||||
if ($useSender) {
|
||||
return $this->sendWithSender($request, $validated, $recipients);
|
||||
}
|
||||
|
||||
$settings = EmailSetting::getActive();
|
||||
if (!$settings) {
|
||||
return back()->with('error', 'Account email non configurato.');
|
||||
}
|
||||
|
||||
$imapSuccess = false;
|
||||
$imapError = null;
|
||||
$attachments = $this->processAttachments($request);
|
||||
|
||||
foreach ($recipients as $recipient) {
|
||||
try {
|
||||
$this->sendViaImap($settings, $recipient, $validated['subject'], $validated['body'], $request->cc);
|
||||
$bodyWithSig = $this->appendSignature($validated['body'], $settings->getSignature(), $settings->signature_enabled ?? false);
|
||||
$this->sendViaImap($settings, $recipient, $validated['subject'], $bodyWithSig, $request->cc, $attachments);
|
||||
$imapSuccess = true;
|
||||
} catch (\Exception $e) {
|
||||
$imapError = $e->getMessage();
|
||||
\Illuminate\Support\Facades\Log::error('IMAP send error', ['error' => $e->getMessage()]);
|
||||
\Illuminate\Support\Facades\Log::error('SMTP send error', ['error' => $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$this->storeSentMessage($settings, $recipients, $validated['subject'], $validated['body'], $request->cc);
|
||||
\Illuminate\Support\Facades\Log::info('Stored sent message');
|
||||
$bodyWithSig = $this->appendSignature($validated['body'], $settings->getSignature(), $settings->signature_enabled ?? false);
|
||||
$this->storeSentMessage($settings, $recipients, $validated['subject'], $bodyWithSig, $request->cc);
|
||||
} catch (\Exception $e) {
|
||||
\Illuminate\Support\Facades\Log::error('Store sent message error', ['error' => $e->getMessage()]);
|
||||
return back()->with('error', 'Errore nel salvataggio: ' . $e->getMessage())->withInput();
|
||||
}
|
||||
|
||||
if (!$imapSuccess && $imapError) {
|
||||
return redirect()->route('email.index', 'sent')->with('warning', 'Email salvata in Invio ma non inviata via IMAP: ' . $imapError);
|
||||
return redirect()->route('email.index', 'sent')->with('warning', 'Email salvata in Invio ma non inviata via SMTP: ' . $imapError);
|
||||
}
|
||||
|
||||
return redirect()->route('email.index', 'sent')->with('success', 'Email inviata con successo.');
|
||||
}
|
||||
|
||||
private function sendWithSender(Request $request, array $validated, array $recipients)
|
||||
{
|
||||
$sender = SenderAccount::findOrFail($request->mittente_id);
|
||||
$attachmentPaths = $this->resolveAttachmentPaths($request);
|
||||
$ok = 0;
|
||||
$errors = [];
|
||||
|
||||
foreach ($recipients as $recipient) {
|
||||
try {
|
||||
$sender->sendEmail($recipient, $validated['subject'], $validated['body'], $attachmentPaths);
|
||||
$ok++;
|
||||
} catch (\Exception $e) {
|
||||
$errors[] = $recipient . ': ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
if ($sender->verify_email) {
|
||||
$reportSubject = '✅ Invio: ' . $validated['subject'] . ' - ' . $sender->email_address;
|
||||
$reportBody = "Report invio mittente: {$sender->email_address}\n"
|
||||
. "Destinatari totali: " . count($recipients) . "\n"
|
||||
. "Inviate con successo: {$ok}\n"
|
||||
. "Fallite: " . count($errors) . "\n";
|
||||
if (!empty($errors)) {
|
||||
$reportBody .= "\nDettaglio errori:\n" . implode("\n", $errors);
|
||||
}
|
||||
$sender->sendReport($reportSubject, $reportBody);
|
||||
}
|
||||
|
||||
if ($ok === 0 && !empty($errors)) {
|
||||
return redirect()->route('email.index', 'sent')->with('error', 'Invio fallito: ' . implode('; ', $errors));
|
||||
}
|
||||
|
||||
$message = $ok . ' email inviate con successo tramite "' . $sender->email_address . '".';
|
||||
if (!empty($errors)) {
|
||||
$message .= ' ' . count($errors) . ' fallite.';
|
||||
}
|
||||
|
||||
return redirect()->route('email.index', 'sent')->with('success', $message);
|
||||
}
|
||||
|
||||
public function storeDraft(Request $request)
|
||||
{
|
||||
$this->authorizeWrite('email');
|
||||
@@ -454,10 +501,8 @@ class EmailController extends Controller
|
||||
return $body . "\n\n---\n" . $signature;
|
||||
}
|
||||
|
||||
private function sendViaImap($settings, $to, $subject, $body, $cc = null)
|
||||
private function sendViaImap($settings, $to, $subject, $body, $cc = null, array $attachmentPaths = [])
|
||||
{
|
||||
$bodyWithSignature = $this->appendSignature($body, $settings->getSignature(), $settings->signature_enabled ?? false);
|
||||
|
||||
\Illuminate\Support\Facades\Log::info('sendViaSMTP start', ['to' => $to, 'subject' => $subject]);
|
||||
|
||||
$smtpPassword = $settings->getDecryptedSmtpPassword() ?? $settings->getDecryptedPassword();
|
||||
@@ -475,8 +520,6 @@ class EmailController extends Controller
|
||||
$encryption
|
||||
);
|
||||
|
||||
\Illuminate\Support\Facades\Log::info('SMTP DSN', ['dsn' => $dsn]);
|
||||
|
||||
try {
|
||||
$transport = \Symfony\Component\Mailer\Transport::fromDsn($dsn);
|
||||
$mailer = new \Symfony\Component\Mailer\Mailer($transport);
|
||||
@@ -498,11 +541,15 @@ class EmailController extends Controller
|
||||
->from($fromAddress)
|
||||
->to($to)
|
||||
->subject($subject)
|
||||
->text($bodyWithSignature);
|
||||
->text($body);
|
||||
|
||||
if ($cc) {
|
||||
$email->cc($cc);
|
||||
}
|
||||
|
||||
foreach ($attachmentPaths as $attachmentPath) {
|
||||
$email->attachFromPath($attachmentPath);
|
||||
}
|
||||
|
||||
$mailer->send($email);
|
||||
|
||||
@@ -514,6 +561,45 @@ class EmailController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
private function processAttachments(Request $request): array
|
||||
{
|
||||
$paths = [];
|
||||
if ($request->hasFile('allegati')) {
|
||||
foreach ($request->file('allegati') as $file) {
|
||||
if ($file->isValid()) {
|
||||
$paths[] = $file->getRealPath();
|
||||
}
|
||||
}
|
||||
}
|
||||
return $paths;
|
||||
}
|
||||
|
||||
private function resolveAttachmentPaths(Request $request): array
|
||||
{
|
||||
$paths = [];
|
||||
|
||||
if ($request->hasFile('allegati')) {
|
||||
foreach ($request->file('allegati') as $file) {
|
||||
if ($file->isValid()) {
|
||||
$paths[] = $file->getRealPath();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$documentIds = $request->input('documenti_selezionati');
|
||||
if (!empty($documentIds)) {
|
||||
$ids = is_array($documentIds) ? $documentIds : explode(',', $documentIds);
|
||||
$docs = Documento::whereIn('id', $ids)->get();
|
||||
foreach ($docs as $doc) {
|
||||
if ($doc->file_path && \Illuminate\Support\Facades\Storage::exists($doc->file_path)) {
|
||||
$paths[] = \Illuminate\Support\Facades\Storage::path($doc->file_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $paths;
|
||||
}
|
||||
|
||||
private function storeSentMessage($settings, $recipients, $subject, $body, $cc = null)
|
||||
{
|
||||
$sentFolder = EmailFolder::where('type', 'sent')->first();
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\MailingList;
|
||||
use App\Models\MailingContact;
|
||||
use App\Models\MailingMessaggio;
|
||||
use App\Models\SenderAccount;
|
||||
use App\Models\Individuo;
|
||||
use App\Models\Documento;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class MailingController extends Controller
|
||||
@@ -24,6 +29,7 @@ class MailingController extends Controller
|
||||
|
||||
$liste = MailingList::all();
|
||||
$documenti = Documento::orderBy('nome_file')->get();
|
||||
$senderAccounts = SenderAccount::active()->get();
|
||||
|
||||
$documentiSelezionati = collect();
|
||||
if ($request->has('documenti_selezionati')) {
|
||||
@@ -31,7 +37,7 @@ class MailingController extends Controller
|
||||
$documentiSelezionati = Documento::whereIn('id', $docIds)->get();
|
||||
}
|
||||
|
||||
return view('mailing.nuovo', compact('individui', 'liste', 'documenti', 'documentiSelezionati'));
|
||||
return view('mailing.nuovo', compact('individui', 'liste', 'documenti', 'documentiSelezionati', 'senderAccounts'));
|
||||
}
|
||||
|
||||
public function invia(Request $request)
|
||||
@@ -44,8 +50,14 @@ class MailingController extends Controller
|
||||
'destinatari_ids' => 'nullable|string',
|
||||
'documenti_selezionati' => 'nullable|string',
|
||||
'allegato' => 'nullable|file|max:10240',
|
||||
'mittente_id' => 'nullable|integer|exists:sender_accounts,id',
|
||||
]);
|
||||
|
||||
$sender = null;
|
||||
if (!empty($data['mittente_id'])) {
|
||||
$sender = SenderAccount::findOrFail($data['mittente_id']);
|
||||
}
|
||||
|
||||
$documentiAllegati = [];
|
||||
|
||||
if ($request->hasFile('allegato')) {
|
||||
@@ -72,13 +84,90 @@ class MailingController extends Controller
|
||||
$documentiAllegati = array_merge($documentiAllegati, $docIds);
|
||||
}
|
||||
|
||||
$destinatari = [];
|
||||
$destinatari = collect();
|
||||
if (!empty($data['destinatari_ids'])) {
|
||||
$ids = array_filter(array_map('trim', explode(',', $data['destinatari_ids'])));
|
||||
$destinatari = Individuo::with('contatti')->whereIn('id', $ids)->get();
|
||||
} elseif (!empty($data['lista_id'])) {
|
||||
$lista = MailingList::with('contatti.individuo.contatti')->find($data['lista_id']);
|
||||
if ($lista) {
|
||||
$destinatari = $lista->contatti->filter(fn($c) => $c->opt_in)->map(fn($c) => $c->individuo)->filter();
|
||||
}
|
||||
}
|
||||
|
||||
return back()->with('success', 'Messaggio preparato per ' . count($destinatari) . ' destinatari con ' . count($documentiAllegati) . ' allegato(i).');
|
||||
$emails = $destinatari->flatMap(fn($ind) => $ind->contatti->where('tipo', 'email')->pluck('valore'))
|
||||
->filter()->unique()->values();
|
||||
|
||||
if ($emails->isEmpty()) {
|
||||
return back()->with('error', 'Nessun destinatario email valido trovato.')->withInput();
|
||||
}
|
||||
|
||||
$attachmentPaths = $this->resolveMailingAttachmentPaths($documentiAllegati);
|
||||
|
||||
$messaggio = MailingMessaggio::create([
|
||||
'mailing_list_id' => $data['lista_id'] ?? null,
|
||||
'user_id' => auth()->id(),
|
||||
'oggetto' => $data['oggetto'],
|
||||
'corpo' => $data['corpo'],
|
||||
'stato' => 'in_coda',
|
||||
'totale_destinatari' => $emails->count(),
|
||||
'mittente_nome' => $sender?->email_name,
|
||||
'mittente_email' => $sender?->email_address,
|
||||
]);
|
||||
|
||||
$ok = 0;
|
||||
$fail = 0;
|
||||
$logFalliti = [];
|
||||
|
||||
foreach ($emails as $email) {
|
||||
try {
|
||||
if ($sender) {
|
||||
$sender->sendEmail($email, $data['oggetto'], $data['corpo'], $attachmentPaths);
|
||||
} else {
|
||||
$this->sendViaSystem($email, $data['oggetto'], $data['corpo'], $attachmentPaths);
|
||||
}
|
||||
$ok++;
|
||||
} catch (\Throwable $e) {
|
||||
$fail++;
|
||||
$logFalliti[] = ['email' => $email, 'errore' => $e->getMessage()];
|
||||
Log::error('Mailing send failed', ['email' => $email, 'error' => $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
$stato = $fail === 0 ? 'inviato' : ($ok > 0 ? 'parziale' : 'fallito');
|
||||
$messaggio->update([
|
||||
'stato' => $stato,
|
||||
'invii_ok' => $ok,
|
||||
'invii_falliti' => $fail,
|
||||
'log_falliti' => $logFalliti,
|
||||
'inviato_al' => now(),
|
||||
]);
|
||||
|
||||
$senderLabel = $sender ? '"' . $sender->email_address . '"' : 'sistema predefinito';
|
||||
|
||||
if ($sender && $sender->verify_email && ($ok > 0 || $stato === 'fallito')) {
|
||||
$reportSubject = '✅ Report Invio - ' . $data['oggetto'];
|
||||
$reportBody = "Report invio mailing\n"
|
||||
. "Mittente: {$sender->email_name} <{$sender->email_address}>\n"
|
||||
. "Oggetto: {$data['oggetto']}\n"
|
||||
. "Totale destinatari: {$emails->count()}\n"
|
||||
. "Inviate con successo: {$ok}\n"
|
||||
. "Fallite: {$fail}\n";
|
||||
if (!empty($logFalliti)) {
|
||||
$reportBody .= "\nDettaglio errori:\n";
|
||||
foreach ($logFalliti as $f) {
|
||||
$reportBody .= "- {$f['email']}: {$f['errore']}\n";
|
||||
}
|
||||
}
|
||||
$sender->sendReport($reportSubject, $reportBody);
|
||||
}
|
||||
|
||||
$msg = "Invio completato tramite {$senderLabel}: {$ok} inviate, {$fail} fallite.";
|
||||
if ($ok > 0) {
|
||||
return redirect('/mailing-liste')->with('success', $msg);
|
||||
}
|
||||
|
||||
return back()->with('error', $msg);
|
||||
}
|
||||
|
||||
public function invio(Request $request)
|
||||
@@ -86,8 +175,9 @@ class MailingController extends Controller
|
||||
$this->authorizeWrite('mailing');
|
||||
$liste = MailingList::where('attiva', true)->orderBy('nome')->get();
|
||||
$documenti = Documento::orderBy('nome_file')->get();
|
||||
$senderAccounts = SenderAccount::active()->get();
|
||||
|
||||
return view('mailing.invio', compact('liste', 'documenti'));
|
||||
return view('mailing.invio', compact('liste', 'documenti', 'senderAccounts'));
|
||||
}
|
||||
|
||||
public function invioElabora(Request $request)
|
||||
@@ -101,8 +191,14 @@ class MailingController extends Controller
|
||||
'documenti_selezionati' => 'nullable|array',
|
||||
'documenti_selezionati.*' => 'exists:documenti,id',
|
||||
'allegato' => 'nullable|file|max:10240',
|
||||
'mittente_id' => 'nullable|integer|exists:sender_accounts,id',
|
||||
]);
|
||||
|
||||
$sender = null;
|
||||
if (!empty($data['mittente_id'])) {
|
||||
$sender = SenderAccount::findOrFail($data['mittente_id']);
|
||||
}
|
||||
|
||||
$documentiAllegati = [];
|
||||
|
||||
if ($request->hasFile('allegato')) {
|
||||
@@ -133,10 +229,148 @@ class MailingController extends Controller
|
||||
->with('individuo.contatti')
|
||||
->get();
|
||||
|
||||
$destinatari = $contatti->map(function($c) {
|
||||
return $c->individuo->contatti->where('tipo', 'email')->first()?->valore;
|
||||
})->filter()->unique()->values();
|
||||
$emails = $contatti->map(fn($c) => $c->individuo?->contatti->where('tipo', 'email')->first()?->valore)
|
||||
->filter()->unique()->values();
|
||||
|
||||
return back()->with('success', 'Messaggio pronto per l\'invio a ' . $destinatari->count() . ' destinatari unici da ' . count($data['liste']) . ' lista(e).');
|
||||
if ($emails->isEmpty()) {
|
||||
return back()->with('error', 'Nessun destinatario email valido trovato nelle liste selezionate.');
|
||||
}
|
||||
|
||||
$attachmentPaths = $this->resolveMailingAttachmentPaths($documentiAllegati);
|
||||
|
||||
$listeNomi = MailingList::whereIn('id', $data['liste'])->pluck('nome')->implode(', ');
|
||||
|
||||
$messaggio = MailingMessaggio::create([
|
||||
'mailing_list_id' => null,
|
||||
'user_id' => auth()->id(),
|
||||
'oggetto' => $data['oggetto'],
|
||||
'corpo' => $data['corpo'],
|
||||
'stato' => 'in_coda',
|
||||
'totale_destinatari' => $emails->count(),
|
||||
'mittente_nome' => $sender?->email_name,
|
||||
'mittente_email' => $sender?->email_address,
|
||||
]);
|
||||
|
||||
$ok = 0;
|
||||
$fail = 0;
|
||||
$logFalliti = [];
|
||||
|
||||
foreach ($emails as $email) {
|
||||
try {
|
||||
if ($sender) {
|
||||
$sender->sendEmail($email, $data['oggetto'], $data['corpo'], $attachmentPaths);
|
||||
} else {
|
||||
$this->sendViaSystem($email, $data['oggetto'], $data['corpo'], $attachmentPaths);
|
||||
}
|
||||
$ok++;
|
||||
} catch (\Throwable $e) {
|
||||
$fail++;
|
||||
$logFalliti[] = ['email' => $email, 'errore' => $e->getMessage()];
|
||||
Log::error('Mailing bulk send failed', ['email' => $email, 'error' => $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
$stato = $fail === 0 ? 'inviato' : ($ok > 0 ? 'parziale' : 'fallito');
|
||||
$messaggio->update([
|
||||
'stato' => $stato,
|
||||
'invii_ok' => $ok,
|
||||
'invii_falliti' => $fail,
|
||||
'log_falliti' => $logFalliti,
|
||||
'inviato_al' => now(),
|
||||
]);
|
||||
|
||||
$senderLabel = $sender ? '"' . $sender->email_address . '"' : 'sistema predefinito';
|
||||
|
||||
if ($sender && $sender->verify_email && ($ok > 0 || $stato === 'fallito')) {
|
||||
$reportSubject = '✅ Report Invio Massivo - ' . $data['oggetto'];
|
||||
$reportBody = "Report invio massivo\n"
|
||||
. "Mittente: {$sender->email_name} <{$sender->email_address}>\n"
|
||||
. "Liste: {$listeNomi}\n"
|
||||
. "Oggetto: {$data['oggetto']}\n"
|
||||
. "Totale destinatari: {$emails->count()}\n"
|
||||
. "Inviate con successo: {$ok}\n"
|
||||
. "Fallite: {$fail}\n";
|
||||
if (!empty($logFalliti)) {
|
||||
$reportBody .= "\nDettaglio errori:\n";
|
||||
foreach ($logFalliti as $f) {
|
||||
$reportBody .= "- {$f['email']}: {$f['errore']}\n";
|
||||
}
|
||||
}
|
||||
$sender->sendReport($reportSubject, $reportBody);
|
||||
}
|
||||
|
||||
$msg = "Invio massivo completato tramite {$senderLabel}: {$ok} inviate, {$fail} fallite.";
|
||||
if ($ok > 0) {
|
||||
return redirect('/')->with('success', $msg);
|
||||
}
|
||||
|
||||
return back()->with('error', $msg);
|
||||
}
|
||||
}
|
||||
|
||||
private function sendViaSystem(string $to, string $subject, string $body, array $attachmentPaths = []): void
|
||||
{
|
||||
$settings = \App\Models\EmailSetting::getActive();
|
||||
if (!$settings) {
|
||||
throw new \Exception('Account email principale non configurato.');
|
||||
}
|
||||
|
||||
$smtpPassword = $settings->getDecryptedSmtpPassword() ?? $settings->getDecryptedPassword();
|
||||
$encryption = $settings->smtp_encryption ?? 'tls';
|
||||
$scheme = ($encryption === 'ssl') ? 'smtps' : 'smtp';
|
||||
|
||||
$dsn = sprintf(
|
||||
'%s://%s:%s@%s:%d?encryption=%s',
|
||||
$scheme,
|
||||
rawurlencode($settings->smtp_username ?? $settings->email_address),
|
||||
rawurlencode($smtpPassword),
|
||||
$settings->smtp_host ?? 'smtp.gmail.com',
|
||||
$settings->smtp_port ?? 587,
|
||||
$encryption
|
||||
);
|
||||
|
||||
$transport = \Symfony\Component\Mailer\Transport::fromDsn($dsn);
|
||||
$mailer = new \Symfony\Component\Mailer\Mailer($transport);
|
||||
|
||||
$fromName = $settings->email_name ?? '';
|
||||
if (filter_var($fromName, FILTER_VALIDATE_EMAIL)) {
|
||||
$fromName = 'Glastree';
|
||||
} else {
|
||||
$fromName = preg_replace('/[^\p{L}\p{N}\s]/u', '', $fromName);
|
||||
$fromName = trim($fromName);
|
||||
if (empty($fromName) || strlen($fromName) > 50) {
|
||||
$fromName = 'Glastree';
|
||||
}
|
||||
}
|
||||
|
||||
$fromAddress = \Symfony\Component\Mime\Address::create($settings->email_address, $fromName);
|
||||
|
||||
$email = (new \Symfony\Component\Mime\Email())
|
||||
->from($fromAddress)
|
||||
->to($to)
|
||||
->subject($subject)
|
||||
->text($body);
|
||||
|
||||
foreach ($attachmentPaths as $path) {
|
||||
$email->attachFromPath($path);
|
||||
}
|
||||
|
||||
$mailer->send($email);
|
||||
}
|
||||
|
||||
private function resolveMailingAttachmentPaths(array $documentIds): array
|
||||
{
|
||||
$paths = [];
|
||||
if (empty($documentIds)) {
|
||||
return $paths;
|
||||
}
|
||||
|
||||
$docs = Documento::whereIn('id', $documentIds)->get();
|
||||
foreach ($docs as $doc) {
|
||||
if ($doc->file_path && Storage::exists($doc->file_path)) {
|
||||
$paths[] = Storage::path($doc->file_path);
|
||||
}
|
||||
}
|
||||
|
||||
return $paths;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,11 +10,13 @@ class MailingMessaggio extends Model
|
||||
protected $table = 'mailing_messaggi';
|
||||
protected $fillable = [
|
||||
'tenant_id', 'mailing_list_id', 'user_id', 'oggetto', 'corpo',
|
||||
'canale', 'stato', 'inviato_al', 'totale_destinatari', 'invii_ok', 'invii_falliti'
|
||||
'canale', 'stato', 'inviato_al', 'totale_destinatari', 'invii_ok', 'invii_falliti',
|
||||
'log_falliti', 'mittente_nome', 'mittente_email',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'inviato_al' => 'datetime',
|
||||
'log_falliti' => 'array',
|
||||
];
|
||||
|
||||
public function tenant(): BelongsTo
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
|
||||
class SenderAccount extends Model
|
||||
{
|
||||
protected $table = 'sender_accounts';
|
||||
|
||||
protected $fillable = [
|
||||
'email_address',
|
||||
'email_name',
|
||||
'smtp_host',
|
||||
'smtp_port',
|
||||
'smtp_encryption',
|
||||
'smtp_username',
|
||||
'smtp_password',
|
||||
'reply_to',
|
||||
'verify_email',
|
||||
'note',
|
||||
'is_active',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'is_active' => 'boolean',
|
||||
'smtp_port' => 'integer',
|
||||
];
|
||||
|
||||
public function scopeActive(Builder $query): Builder
|
||||
{
|
||||
return $query->where('is_active', true);
|
||||
}
|
||||
|
||||
public function getDecryptedPassword(): string
|
||||
{
|
||||
if (empty($this->smtp_password)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
try {
|
||||
return Crypt::decryptString($this->smtp_password);
|
||||
} catch (\Exception $e) {
|
||||
return $this->smtp_password;
|
||||
}
|
||||
}
|
||||
|
||||
public function sanitizeName(): string
|
||||
{
|
||||
$name = $this->email_name ?? '';
|
||||
|
||||
if (filter_var($name, FILTER_VALIDATE_EMAIL)) {
|
||||
return 'Glastree';
|
||||
}
|
||||
|
||||
$name = preg_replace('/[^\p{L}\p{N}\s]/u', '', $name);
|
||||
$name = trim($name);
|
||||
|
||||
return empty($name) ? 'Glastree' : $name;
|
||||
}
|
||||
|
||||
public function getReplyTo(): ?string
|
||||
{
|
||||
if ($this->reply_to) {
|
||||
return $this->reply_to;
|
||||
}
|
||||
|
||||
if ($this->verify_email) {
|
||||
return $this->verify_email;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function buildDsn(): string
|
||||
{
|
||||
$password = $this->getDecryptedPassword();
|
||||
$username = $this->smtp_username ?? $this->email_address;
|
||||
$encryption = $this->smtp_encryption ?? 'tls';
|
||||
$scheme = ($encryption === 'ssl') ? 'smtps' : 'smtp';
|
||||
|
||||
return sprintf(
|
||||
'%s://%s:%s@%s:%d?encryption=%s',
|
||||
$scheme,
|
||||
rawurlencode($username),
|
||||
rawurlencode($password),
|
||||
$this->smtp_host ?? 'smtp.gmail.com',
|
||||
$this->smtp_port ?? 587,
|
||||
$encryption
|
||||
);
|
||||
}
|
||||
|
||||
public function buildSymfonyEmail(string $to, string $subject, string $body): \Symfony\Component\Mime\Email
|
||||
{
|
||||
$fromName = $this->sanitizeName();
|
||||
$fromAddress = \Symfony\Component\Mime\Address::create($this->email_address, $fromName);
|
||||
|
||||
$email = (new \Symfony\Component\Mime\Email())
|
||||
->from($fromAddress)
|
||||
->to($to)
|
||||
->subject($subject)
|
||||
->text($body);
|
||||
|
||||
$replyTo = $this->getReplyTo();
|
||||
if ($replyTo) {
|
||||
$email->replyTo($replyTo);
|
||||
}
|
||||
|
||||
return $email;
|
||||
}
|
||||
|
||||
public function sendEmail(string $to, string $subject, string $body, array $attachmentPaths = []): void
|
||||
{
|
||||
$email = $this->buildSymfonyEmail($to, $subject, $body);
|
||||
|
||||
foreach ($attachmentPaths as $path) {
|
||||
$email->attachFromPath($path);
|
||||
}
|
||||
|
||||
$transport = \Symfony\Component\Mailer\Transport::fromDsn($this->buildDsn());
|
||||
$mailer = new \Symfony\Component\Mailer\Mailer($transport);
|
||||
$mailer->send($email);
|
||||
}
|
||||
|
||||
public function sendReport(string $subject, string $body): bool
|
||||
{
|
||||
if (empty($this->verify_email)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->sendEmail($this->verify_email, $subject, $body);
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
\Illuminate\Support\Facades\Log::error('Sender report email failed', [
|
||||
'sender' => $this->email_address,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('sender_accounts', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('email_address')->unique();
|
||||
$table->string('email_name')->nullable();
|
||||
$table->string('smtp_host')->nullable();
|
||||
$table->integer('smtp_port')->default(587);
|
||||
$table->string('smtp_encryption')->default('tls');
|
||||
$table->string('smtp_username')->nullable();
|
||||
$table->text('smtp_password')->nullable();
|
||||
$table->string('reply_to')->nullable();
|
||||
$table->string('verify_email')->nullable();
|
||||
$table->text('note')->nullable();
|
||||
$table->boolean('is_active')->default(true);
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('sender_accounts');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('mailing_messaggi', function (Blueprint $table) {
|
||||
$table->json('log_falliti')->nullable()->after('invii_falliti');
|
||||
$table->string('mittente_nome')->nullable()->after('log_falliti');
|
||||
$table->string('mittente_email')->nullable()->after('mittente_nome');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('mailing_messaggi', function (Blueprint $table) {
|
||||
$table->dropColumn(['log_falliti', 'mittente_nome', 'mittente_email']);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -31,6 +31,9 @@
|
||||
<a href="#signature" class="list-group-item list-group-item-action" data-toggle="tab">
|
||||
<i class="nav-icon fas fa-signature"></i> Firma
|
||||
</a>
|
||||
<a href="#mittenti" class="list-group-item list-group-item-action" data-toggle="tab">
|
||||
<i class="nav-icon fas fa-user-plus"></i> Mittenti Aggiuntivi
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -278,6 +281,100 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane" id="mittenti">
|
||||
<div class="card card-primary">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Mittenti Aggiuntivi</h3>
|
||||
<div class="card-tools">
|
||||
<button type="button" class="btn btn-sm btn-success" data-toggle="modal" data-target="#senderModal" onclick="resetSenderForm()">
|
||||
<i class="fas fa-plus mr-1"></i> Nuovo Mittente
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@if(session('success'))
|
||||
<div class="alert alert-success">{{ session('success') }}</div>
|
||||
@endif
|
||||
@if(session('error'))
|
||||
<div class="alert alert-danger">{{ session('error') }}</div>
|
||||
@endif
|
||||
|
||||
@if($senderAccounts->count() > 0)
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-hover table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Email</th>
|
||||
<th>Nome</th>
|
||||
<th>SMTP</th>
|
||||
<th>Reply-To</th>
|
||||
<th>Verify</th>
|
||||
<th>Stato</th>
|
||||
<th style="width: 160px;">Azioni</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($senderAccounts as $sender)
|
||||
<tr>
|
||||
<td><strong>{{ $sender->email_address }}</strong></td>
|
||||
<td>{{ $sender->email_name ?: '-' }}</td>
|
||||
<td><small>{{ $sender->smtp_host ?: '-' }}:{{ $sender->smtp_port ?: '-' }}</small></td>
|
||||
<td><small class="text-muted">{{ $sender->reply_to ?: '-' }}</small></td>
|
||||
<td><small class="text-muted">{{ $sender->verify_email ?: '-' }}</small></td>
|
||||
<td>
|
||||
@if($sender->is_active)
|
||||
<span class="badge badge-success">Attivo</span>
|
||||
@else
|
||||
<span class="badge badge-secondary">Disattivo</span>
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-xs btn-info" onclick="editSender({{ $sender->id }})" title="Modifica">
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-xs btn-success" onclick="testSenderSmtp({{ $sender->id }})" title="Test SMTP">
|
||||
<i class="fas fa-paper-plane"></i>
|
||||
</button>
|
||||
<form action="{{ route('impostazioni.sender.destroy', $sender->id) }}" method="POST" class="d-inline" onsubmit="return confirm('Eliminare il mittente {{ $sender->email_address }}?')">
|
||||
@csrf @method('DELETE')
|
||||
<button type="submit" class="btn btn-xs btn-danger" title="Elimina">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@else
|
||||
<div class="text-center text-muted py-4">
|
||||
<i class="fas fa-user-plus fa-2x mb-2"></i>
|
||||
<p class="mb-0">Nessun mittente aggiuntivo configurato.</p>
|
||||
<p class="mb-0">Usa il pulsante "Nuovo Mittente" per aggiungere un account di invio dedicato (es. Newsletter).</p>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if($senderAccounts->count() > 0)
|
||||
<div class="card card-secondary">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title"><i class="fas fa-info-circle mr-2"></i>Come funziona</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<ul class="mb-0">
|
||||
<li>I mittenti aggiuntivi sono account <strong>solo invio</strong> (SMTP) senza IMAP.</li>
|
||||
<li>Puoi selezionarli nel <strong>compose email</strong> o negli <strong>invii mailing</strong>.</li>
|
||||
<li>Il campo <strong>Reply-To</strong> imposta l'indirizzo di risposta (es. <code>no-reply@parrocchia.it</code>).</li>
|
||||
<li>Il campo <strong>Verify Email</strong> riceve un report di riepilogo dopo ogni invio massivo con l'esito (ok/falliti).</li>
|
||||
<li>Se Reply-To non è impostato, viene usato Verify Email come fallback.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-footer">
|
||||
@@ -299,8 +396,180 @@
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
<div class="modal fade" id="senderModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<form id="senderForm" method="POST" action="{{ route('impostazioni.sender.store') }}">
|
||||
@csrf
|
||||
<input type="hidden" name="_method" id="senderMethod" value="POST">
|
||||
<input type="hidden" name="id" id="senderId" value="">
|
||||
<div class="modal-header bg-primary">
|
||||
<h5 class="modal-title" id="senderModalTitle"><i class="fas fa-user-plus mr-2"></i>Nuovo Mittente</h5>
|
||||
<button type="button" class="close text-white" data-dismiss="modal">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="sender_email">Email *</label>
|
||||
<input type="email" name="email_address" id="sender_email" class="form-control" required placeholder="newsletter@parrocchia.it">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="sender_name">Nome visualizzato</label>
|
||||
<input type="text" name="email_name" id="sender_name" class="form-control" placeholder="Newsletter Parrocchiale">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<h6>Configurazione SMTP</h6>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="sender_smtp_host">Host SMTP</label>
|
||||
<input type="text" name="smtp_host" id="sender_smtp_host" class="form-control" placeholder="smtp.gmail.com">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<label for="sender_smtp_port">Porta</label>
|
||||
<input type="number" name="smtp_port" id="sender_smtp_port" class="form-control" value="587">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<label for="sender_smtp_encryption">Crittografia</label>
|
||||
<select name="smtp_encryption" id="sender_smtp_encryption" class="form-control">
|
||||
<option value="tls">TLS</option>
|
||||
<option value="ssl">SSL</option>
|
||||
<option value="none">Nessuna</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="sender_smtp_username">Username SMTP</label>
|
||||
<input type="text" name="smtp_username" id="sender_smtp_username" class="form-control" placeholder="Lascia vuoto per usare l'email">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="sender_smtp_password">Password SMTP</label>
|
||||
<input type="password" name="smtp_password" id="sender_smtp_password" class="form-control" placeholder="......">
|
||||
<small class="text-muted">Lascia vuoto per non cambiare</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="sender_reply_to">Reply-To <small class="text-muted">(no-reply)</small></label>
|
||||
<input type="email" name="reply_to" id="sender_reply_to" class="form-control" placeholder="no-reply@parrocchia.it">
|
||||
<small class="text-muted">Indirizzo di risposta per le email inviate</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="sender_verify_email">Verify Email <small class="text-muted">(report)</small></label>
|
||||
<input type="email" name="verify_email" id="sender_verify_email" class="form-control" placeholder="verifica@parrocchia.it">
|
||||
<small class="text-muted">Riceve report riepilogo dopo invii massivi</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="sender_note">Nota interna</label>
|
||||
<textarea name="note" id="sender_note" class="form-control" rows="2" placeholder="Es. Newsletter settimanale"></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="custom-control custom-switch">
|
||||
<input type="checkbox" class="custom-control-input" id="sender_is_active" name="is_active" value="1" checked>
|
||||
<label class="custom-control-label" for="sender_is_active">Mittente attivo</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Annulla</button>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-save mr-1"></i> Salva Mittente
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section('scripts')
|
||||
<script>
|
||||
let senderAccounts = @json($senderAccounts);
|
||||
|
||||
function resetSenderForm() {
|
||||
document.getElementById('senderForm').action = '{{ route('impostazioni.sender.store') }}';
|
||||
document.getElementById('senderMethod').value = 'POST';
|
||||
document.getElementById('senderModalTitle').textContent = 'Nuovo Mittente';
|
||||
document.getElementById('senderForm').reset();
|
||||
document.getElementById('senderId').value = '';
|
||||
document.getElementById('sender_is_active').checked = true;
|
||||
document.getElementById('sender_smtp_password').placeholder = '';
|
||||
document.getElementById('sender_smtp_password').required = false;
|
||||
}
|
||||
|
||||
function editSender(id) {
|
||||
const sender = senderAccounts.find(s => s.id === id);
|
||||
if (!sender) return;
|
||||
|
||||
document.getElementById('senderForm').action = '{{ route('impostazioni.sender.update', '') }}/' + id;
|
||||
document.getElementById('senderMethod').value = 'PUT';
|
||||
document.getElementById('senderModalTitle').textContent = 'Modifica Mittente - ' + sender.email_address;
|
||||
document.getElementById('senderId').value = sender.id;
|
||||
document.getElementById('sender_email').value = sender.email_address;
|
||||
document.getElementById('sender_name').value = sender.email_name || '';
|
||||
document.getElementById('sender_smtp_host').value = sender.smtp_host || '';
|
||||
document.getElementById('sender_smtp_port').value = sender.smtp_port || 587;
|
||||
document.getElementById('sender_smtp_encryption').value = sender.smtp_encryption || 'tls';
|
||||
document.getElementById('sender_smtp_username').value = sender.smtp_username || '';
|
||||
document.getElementById('sender_smtp_password').value = '';
|
||||
document.getElementById('sender_smtp_password').placeholder = '......';
|
||||
document.getElementById('sender_smtp_password').required = false;
|
||||
document.getElementById('sender_reply_to').value = sender.reply_to || '';
|
||||
document.getElementById('sender_verify_email').value = sender.verify_email || '';
|
||||
document.getElementById('sender_note').value = sender.note || '';
|
||||
document.getElementById('sender_is_active').checked = sender.is_active;
|
||||
|
||||
$('#senderModal').modal('show');
|
||||
}
|
||||
|
||||
function testSenderSmtp(id) {
|
||||
const email = prompt('Inserisci l\'indirizzo email a cui inviare il test:');
|
||||
if (!email || !email.includes('@')) return;
|
||||
|
||||
const btn = event.target;
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
|
||||
|
||||
$.ajax({
|
||||
url: '{{ route('impostazioni.sender.test', '') }}/' + id,
|
||||
method: 'POST',
|
||||
headers: { 'X-CSRF-TOKEN': '{{ csrf_token() }}' },
|
||||
data: { email: email },
|
||||
timeout: 30000,
|
||||
success: function(response) {
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '<i class="fas fa-paper-plane"></i>';
|
||||
alert(response.success ? '✅ ' + response.message : '❌ ' + response.message);
|
||||
},
|
||||
error: function(xhr) {
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '<i class="fas fa-paper-plane"></i>';
|
||||
const msg = xhr.responseJSON?.message || 'Errore di connessione';
|
||||
alert('❌ ' + msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function testConnection() {
|
||||
$.ajax({
|
||||
url: '{{ route('impostazioni.email.test') }}',
|
||||
|
||||
@@ -10,9 +10,22 @@
|
||||
<h3 class="card-title"><i class="fas fa-edit mr-2"></i>Nuova Email</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="POST" action="{{ route('email.send') }}" id="emailForm">
|
||||
<form method="POST" action="{{ route('email.send') }}" id="emailForm" enctype="multipart/form-data">
|
||||
@csrf
|
||||
|
||||
@if($senderAccounts->count() > 0)
|
||||
<div class="form-group">
|
||||
<label for="mittente_id">Da (mittente)</label>
|
||||
<select name="mittente_id" id="mittente_id" class="form-control">
|
||||
<option value="0">Sistema — {{ optional(\App\Models\EmailSetting::getActive())->email_address ?? 'Configura email' }}</option>
|
||||
@foreach($senderAccounts as $sender)
|
||||
<option value="{{ $sender->id }}">{{ $sender->email_name ?: $sender->email_address }} <{{ $sender->email_address }}></option>
|
||||
@endforeach
|
||||
</select>
|
||||
<small class="text-muted">Seleziona un mittente alternativo per l'invio. Lascia "Sistema" per usare l'account predefinito.</small>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="form-group">
|
||||
<label>Destinatari *</label>
|
||||
<div class="mb-2">
|
||||
|
||||
@@ -13,6 +13,19 @@
|
||||
<form action="{{ route('mailing.invio.elabora') }}" method="POST" enctype="multipart/form-data">
|
||||
@csrf
|
||||
|
||||
@if(isset($senderAccounts) && $senderAccounts->count() > 0)
|
||||
<div class="form-group">
|
||||
<label for="mittente_id">Mittente</label>
|
||||
<select name="mittente_id" id="mittente_id" class="form-control">
|
||||
<option value="">Sistema predefinito</option>
|
||||
@foreach($senderAccounts as $sender)
|
||||
<option value="{{ $sender->id }}">{{ $sender->email_name ?: $sender->email_address }} <{{ $sender->email_address }}></option>
|
||||
@endforeach
|
||||
</select>
|
||||
<small class="text-muted">Seleziona un mittente per l'invio massivo.</small>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="form-group">
|
||||
<label>Liste Destinatari *</label>
|
||||
<select name="liste[]" id="liste" class="form-control" multiple required size="4">
|
||||
|
||||
@@ -14,6 +14,19 @@
|
||||
<form action="{{ route('mailing.invia') }}" method="POST" enctype="multipart/form-data">
|
||||
@csrf
|
||||
|
||||
@if(isset($senderAccounts) && $senderAccounts->count() > 0)
|
||||
<div class="form-group">
|
||||
<label for="mittente_id">Mittente</label>
|
||||
<select name="mittente_id" id="mittente_id" class="form-control">
|
||||
<option value="">Sistema predefinito</option>
|
||||
@foreach($senderAccounts as $sender)
|
||||
<option value="{{ $sender->id }}">{{ $sender->email_name ?: $sender->email_address }} <{{ $sender->email_address }}></option>
|
||||
@endforeach
|
||||
</select>
|
||||
<small class="text-muted">Seleziona un mittente per l'invio. Usa "Sistema predefinito" per inviare con l'account email principale.</small>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if($individui->count() > 0)
|
||||
<div class="form-group" id="destinatari-form-group">
|
||||
<label>Destinatari selezionati</label>
|
||||
|
||||
@@ -132,6 +132,8 @@ Route::delete('report/custom/{id}', [ReportController::class, 'destroyCustom'])-
|
||||
|
||||
Route::get('mailing/nuovo', [MailingController::class, 'nuovo'])->middleware('auth')->name('mailing.nuovo');
|
||||
Route::post('mailing/invia', [MailingController::class, 'invia'])->middleware('auth')->name('mailing.invia');
|
||||
Route::get('mailing/invio', [MailingController::class, 'invio'])->middleware('auth')->name('mailing.invio');
|
||||
Route::post('mailing/invio/elabora', [MailingController::class, 'invioElabora'])->middleware('auth')->name('mailing.invio.elabora');
|
||||
|
||||
Route::resource('mailing-liste', MailingListController::class)->middleware('auth');
|
||||
Route::post('mailing-liste/create-from-individui', [MailingListController::class, 'createFromIndividui'])->middleware('auth');
|
||||
@@ -159,6 +161,10 @@ Route::post('/impostazioni/email', [EmailSettingsController::class, 'save'])->mi
|
||||
Route::post('/impostazioni/email/test', [EmailSettingsController::class, 'testConnection'])->middleware('auth')->name('impostazioni.email.test');
|
||||
Route::post('/impostazioni/email/test-smtp', [EmailSettingsController::class, 'testSmtp'])->middleware('auth')->name('impostazioni.email.testSmtp');
|
||||
Route::get('/impostazioni/email/sync', [EmailSettingsController::class, 'syncNow'])->middleware('auth')->name('impostazioni.email.sync');
|
||||
Route::post('/impostazioni/sender', [EmailSettingsController::class, 'senderStore'])->middleware('auth')->name('impostazioni.sender.store');
|
||||
Route::put('/impostazioni/sender/{id}', [EmailSettingsController::class, 'senderUpdate'])->middleware('auth')->name('impostazioni.sender.update');
|
||||
Route::delete('/impostazioni/sender/{id}', [EmailSettingsController::class, 'senderDestroy'])->middleware('auth')->name('impostazioni.sender.destroy');
|
||||
Route::post('/impostazioni/sender/{id}/test', [EmailSettingsController::class, 'senderTestSmtp'])->middleware('auth')->name('impostazioni.sender.test');
|
||||
|
||||
Route::get('/email/{folder?}', [EmailController::class, 'index'])->middleware('auth')->name('email.index')->defaults('folder', 'inbox');
|
||||
Route::get('/email/{folder}/{id}', [EmailController::class, 'show'])->middleware('auth')->name('email.show');
|
||||
|
||||
@@ -12,6 +12,19 @@
|
||||
<form action="<?php echo e(route('mailing.invia')); ?>" method="POST" enctype="multipart/form-data">
|
||||
<?php echo csrf_field(); ?>
|
||||
|
||||
<?php if(isset($senderAccounts) && $senderAccounts->count() > 0): ?>
|
||||
<div class="form-group">
|
||||
<label for="mittente_id">Mittente</label>
|
||||
<select name="mittente_id" id="mittente_id" class="form-control">
|
||||
<option value="">Sistema predefinito</option>
|
||||
<?php $__currentLoopData = $senderAccounts; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $sender): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
|
||||
<option value="<?php echo e($sender->id); ?>"><?php echo e($sender->email_name ?: $sender->email_address); ?> <<?php echo e($sender->email_address); ?>></option>
|
||||
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?>
|
||||
</select>
|
||||
<small class="text-muted">Seleziona un mittente per l'invio. Usa "Sistema predefinito" per inviare con l'account email principale.</small>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if($individui->count() > 0): ?>
|
||||
<div class="form-group" id="destinatari-form-group">
|
||||
<label>Destinatari selezionati</label>
|
||||
|
||||
@@ -12,6 +12,19 @@
|
||||
<form action="<?php echo e(route('mailing.invio.elabora')); ?>" method="POST" enctype="multipart/form-data">
|
||||
<?php echo csrf_field(); ?>
|
||||
|
||||
<?php if(isset($senderAccounts) && $senderAccounts->count() > 0): ?>
|
||||
<div class="form-group">
|
||||
<label for="mittente_id">Mittente</label>
|
||||
<select name="mittente_id" id="mittente_id" class="form-control">
|
||||
<option value="">Sistema predefinito</option>
|
||||
<?php $__currentLoopData = $senderAccounts; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $sender): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
|
||||
<option value="<?php echo e($sender->id); ?>"><?php echo e($sender->email_name ?: $sender->email_address); ?> <<?php echo e($sender->email_address); ?>></option>
|
||||
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?>
|
||||
</select>
|
||||
<small class="text-muted">Seleziona un mittente per l'invio massivo.</small>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Liste Destinatari *</label>
|
||||
<select name="liste[]" id="liste" class="form-control" multiple required size="4">
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php if($paginator->hasPages()): ?>
|
||||
<ul class="pagination justify-content-center" role="navigation">
|
||||
|
||||
<?php if($paginator->onFirstPage()): ?>
|
||||
<li class="page-item disabled" aria-disabled="true" aria-label="<?php echo app('translator')->get('pagination.previous'); ?>">
|
||||
<span class="page-link" aria-hidden="true">‹</span>
|
||||
</li>
|
||||
<?php else: ?>
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="<?php echo e($paginator->previousPageUrl()); ?>" rel="prev" aria-label="<?php echo app('translator')->get('pagination.previous'); ?>">‹</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
|
||||
|
||||
<?php $__currentLoopData = $elements; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $element): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
|
||||
|
||||
<?php if(is_string($element)): ?>
|
||||
<li class="page-item disabled" aria-disabled="true"><span class="page-link"><?php echo e($element); ?></span></li>
|
||||
<?php endif; ?>
|
||||
|
||||
|
||||
<?php if(is_array($element)): ?>
|
||||
<?php $__currentLoopData = $element; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $page => $url): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
|
||||
<?php if($page == $paginator->currentPage()): ?>
|
||||
<li class="page-item active" aria-current="page"><span class="page-link"><?php echo e($page); ?></span></li>
|
||||
<?php elseif($page >= $paginator->currentPage() - 2 && $page <= $paginator->currentPage() + 2): ?>
|
||||
<li class="page-item"><a class="page-link" href="<?php echo e($url); ?>"><?php echo e($page); ?></a></li>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?>
|
||||
|
||||
|
||||
<?php if($paginator->hasMorePages()): ?>
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="<?php echo e($paginator->nextPageUrl()); ?>" rel="next" aria-label="<?php echo app('translator')->get('pagination.next'); ?>">›</a>
|
||||
</li>
|
||||
<?php else: ?>
|
||||
<li class="page-item disabled" aria-disabled="true" aria-label="<?php echo app('translator')->get('pagination.next'); ?>">
|
||||
<span class="page-link" aria-hidden="true">›</span>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
</ul>
|
||||
<?php endif; ?><?php /**PATH /var/www/html/glastree/resources/views/vendor/pagination/simple-bootstrap-4.blade.php ENDPATH**/ ?>
|
||||
@@ -30,6 +30,9 @@
|
||||
<a href="#signature" class="list-group-item list-group-item-action" data-toggle="tab">
|
||||
<i class="nav-icon fas fa-signature"></i> Firma
|
||||
</a>
|
||||
<a href="#mittenti" class="list-group-item list-group-item-action" data-toggle="tab">
|
||||
<i class="nav-icon fas fa-user-plus"></i> Mittenti Aggiuntivi
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -278,6 +281,100 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane" id="mittenti">
|
||||
<div class="card card-primary">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Mittenti Aggiuntivi</h3>
|
||||
<div class="card-tools">
|
||||
<button type="button" class="btn btn-sm btn-success" data-toggle="modal" data-target="#senderModal" onclick="resetSenderForm()">
|
||||
<i class="fas fa-plus mr-1"></i> Nuovo Mittente
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<?php if(session('success')): ?>
|
||||
<div class="alert alert-success"><?php echo e(session('success')); ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if(session('error')): ?>
|
||||
<div class="alert alert-danger"><?php echo e(session('error')); ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if($senderAccounts->count() > 0): ?>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-hover table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Email</th>
|
||||
<th>Nome</th>
|
||||
<th>SMTP</th>
|
||||
<th>Reply-To</th>
|
||||
<th>Verify</th>
|
||||
<th>Stato</th>
|
||||
<th style="width: 160px;">Azioni</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php $__currentLoopData = $senderAccounts; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $sender): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
|
||||
<tr>
|
||||
<td><strong><?php echo e($sender->email_address); ?></strong></td>
|
||||
<td><?php echo e($sender->email_name ?: '-'); ?></td>
|
||||
<td><small><?php echo e($sender->smtp_host ?: '-'); ?>:<?php echo e($sender->smtp_port ?: '-'); ?></small></td>
|
||||
<td><small class="text-muted"><?php echo e($sender->reply_to ?: '-'); ?></small></td>
|
||||
<td><small class="text-muted"><?php echo e($sender->verify_email ?: '-'); ?></small></td>
|
||||
<td>
|
||||
<?php if($sender->is_active): ?>
|
||||
<span class="badge badge-success">Attivo</span>
|
||||
<?php else: ?>
|
||||
<span class="badge badge-secondary">Disattivo</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-xs btn-info" onclick="editSender(<?php echo e($sender->id); ?>)" title="Modifica">
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-xs btn-success" onclick="testSenderSmtp(<?php echo e($sender->id); ?>)" title="Test SMTP">
|
||||
<i class="fas fa-paper-plane"></i>
|
||||
</button>
|
||||
<form action="<?php echo e(route('impostazioni.sender.destroy', $sender->id)); ?>" method="POST" class="d-inline" onsubmit="return confirm('Eliminare il mittente <?php echo e($sender->email_address); ?>?')">
|
||||
<?php echo csrf_field(); ?> <?php echo method_field('DELETE'); ?>
|
||||
<button type="submit" class="btn btn-xs btn-danger" title="Elimina">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="text-center text-muted py-4">
|
||||
<i class="fas fa-user-plus fa-2x mb-2"></i>
|
||||
<p class="mb-0">Nessun mittente aggiuntivo configurato.</p>
|
||||
<p class="mb-0">Usa il pulsante "Nuovo Mittente" per aggiungere un account di invio dedicato (es. Newsletter).</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if($senderAccounts->count() > 0): ?>
|
||||
<div class="card card-secondary">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title"><i class="fas fa-info-circle mr-2"></i>Come funziona</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<ul class="mb-0">
|
||||
<li>I mittenti aggiuntivi sono account <strong>solo invio</strong> (SMTP) senza IMAP.</li>
|
||||
<li>Puoi selezionarli nel <strong>compose email</strong> o negli <strong>invii mailing</strong>.</li>
|
||||
<li>Il campo <strong>Reply-To</strong> imposta l'indirizzo di risposta (es. <code>no-reply@parrocchia.it</code>).</li>
|
||||
<li>Il campo <strong>Verify Email</strong> riceve un report di riepilogo dopo ogni invio massivo con l'esito (ok/falliti).</li>
|
||||
<li>Se Reply-To non è impostato, viene usato Verify Email come fallback.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-footer">
|
||||
@@ -299,8 +396,180 @@
|
||||
</div>
|
||||
<?php $__env->stopSection(); ?>
|
||||
|
||||
<div class="modal fade" id="senderModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<form id="senderForm" method="POST" action="<?php echo e(route('impostazioni.sender.store')); ?>">
|
||||
<?php echo csrf_field(); ?>
|
||||
<input type="hidden" name="_method" id="senderMethod" value="POST">
|
||||
<input type="hidden" name="id" id="senderId" value="">
|
||||
<div class="modal-header bg-primary">
|
||||
<h5 class="modal-title" id="senderModalTitle"><i class="fas fa-user-plus mr-2"></i>Nuovo Mittente</h5>
|
||||
<button type="button" class="close text-white" data-dismiss="modal">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="sender_email">Email *</label>
|
||||
<input type="email" name="email_address" id="sender_email" class="form-control" required placeholder="newsletter@parrocchia.it">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="sender_name">Nome visualizzato</label>
|
||||
<input type="text" name="email_name" id="sender_name" class="form-control" placeholder="Newsletter Parrocchiale">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<h6>Configurazione SMTP</h6>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="sender_smtp_host">Host SMTP</label>
|
||||
<input type="text" name="smtp_host" id="sender_smtp_host" class="form-control" placeholder="smtp.gmail.com">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<label for="sender_smtp_port">Porta</label>
|
||||
<input type="number" name="smtp_port" id="sender_smtp_port" class="form-control" value="587">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<label for="sender_smtp_encryption">Crittografia</label>
|
||||
<select name="smtp_encryption" id="sender_smtp_encryption" class="form-control">
|
||||
<option value="tls">TLS</option>
|
||||
<option value="ssl">SSL</option>
|
||||
<option value="none">Nessuna</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="sender_smtp_username">Username SMTP</label>
|
||||
<input type="text" name="smtp_username" id="sender_smtp_username" class="form-control" placeholder="Lascia vuoto per usare l'email">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="sender_smtp_password">Password SMTP</label>
|
||||
<input type="password" name="smtp_password" id="sender_smtp_password" class="form-control" placeholder="......">
|
||||
<small class="text-muted">Lascia vuoto per non cambiare</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="sender_reply_to">Reply-To <small class="text-muted">(no-reply)</small></label>
|
||||
<input type="email" name="reply_to" id="sender_reply_to" class="form-control" placeholder="no-reply@parrocchia.it">
|
||||
<small class="text-muted">Indirizzo di risposta per le email inviate</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="sender_verify_email">Verify Email <small class="text-muted">(report)</small></label>
|
||||
<input type="email" name="verify_email" id="sender_verify_email" class="form-control" placeholder="verifica@parrocchia.it">
|
||||
<small class="text-muted">Riceve report riepilogo dopo invii massivi</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="sender_note">Nota interna</label>
|
||||
<textarea name="note" id="sender_note" class="form-control" rows="2" placeholder="Es. Newsletter settimanale"></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="custom-control custom-switch">
|
||||
<input type="checkbox" class="custom-control-input" id="sender_is_active" name="is_active" value="1" checked>
|
||||
<label class="custom-control-label" for="sender_is_active">Mittente attivo</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Annulla</button>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-save mr-1"></i> Salva Mittente
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php $__env->startSection('scripts'); ?>
|
||||
<script>
|
||||
let senderAccounts = <?php echo json_encode($senderAccounts, 15, 512) ?>;
|
||||
|
||||
function resetSenderForm() {
|
||||
document.getElementById('senderForm').action = '<?php echo e(route('impostazioni.sender.store')); ?>';
|
||||
document.getElementById('senderMethod').value = 'POST';
|
||||
document.getElementById('senderModalTitle').textContent = 'Nuovo Mittente';
|
||||
document.getElementById('senderForm').reset();
|
||||
document.getElementById('senderId').value = '';
|
||||
document.getElementById('sender_is_active').checked = true;
|
||||
document.getElementById('sender_smtp_password').placeholder = '';
|
||||
document.getElementById('sender_smtp_password').required = false;
|
||||
}
|
||||
|
||||
function editSender(id) {
|
||||
const sender = senderAccounts.find(s => s.id === id);
|
||||
if (!sender) return;
|
||||
|
||||
document.getElementById('senderForm').action = '<?php echo e(route('impostazioni.sender.update', '')); ?>/' + id;
|
||||
document.getElementById('senderMethod').value = 'PUT';
|
||||
document.getElementById('senderModalTitle').textContent = 'Modifica Mittente - ' + sender.email_address;
|
||||
document.getElementById('senderId').value = sender.id;
|
||||
document.getElementById('sender_email').value = sender.email_address;
|
||||
document.getElementById('sender_name').value = sender.email_name || '';
|
||||
document.getElementById('sender_smtp_host').value = sender.smtp_host || '';
|
||||
document.getElementById('sender_smtp_port').value = sender.smtp_port || 587;
|
||||
document.getElementById('sender_smtp_encryption').value = sender.smtp_encryption || 'tls';
|
||||
document.getElementById('sender_smtp_username').value = sender.smtp_username || '';
|
||||
document.getElementById('sender_smtp_password').value = '';
|
||||
document.getElementById('sender_smtp_password').placeholder = '......';
|
||||
document.getElementById('sender_smtp_password').required = false;
|
||||
document.getElementById('sender_reply_to').value = sender.reply_to || '';
|
||||
document.getElementById('sender_verify_email').value = sender.verify_email || '';
|
||||
document.getElementById('sender_note').value = sender.note || '';
|
||||
document.getElementById('sender_is_active').checked = sender.is_active;
|
||||
|
||||
$('#senderModal').modal('show');
|
||||
}
|
||||
|
||||
function testSenderSmtp(id) {
|
||||
const email = prompt('Inserisci l\'indirizzo email a cui inviare il test:');
|
||||
if (!email || !email.includes('@')) return;
|
||||
|
||||
const btn = event.target;
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
|
||||
|
||||
$.ajax({
|
||||
url: '<?php echo e(route('impostazioni.sender.test', '')); ?>/' + id,
|
||||
method: 'POST',
|
||||
headers: { 'X-CSRF-TOKEN': '<?php echo e(csrf_token()); ?>' },
|
||||
data: { email: email },
|
||||
timeout: 30000,
|
||||
success: function(response) {
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '<i class="fas fa-paper-plane"></i>';
|
||||
alert(response.success ? '✅ ' + response.message : '❌ ' + response.message);
|
||||
},
|
||||
error: function(xhr) {
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '<i class="fas fa-paper-plane"></i>';
|
||||
const msg = xhr.responseJSON?.message || 'Errore di connessione';
|
||||
alert('❌ ' + msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function testConnection() {
|
||||
$.ajax({
|
||||
url: '<?php echo e(route('impostazioni.email.test')); ?>',
|
||||
|
||||
@@ -9,9 +9,22 @@
|
||||
<h3 class="card-title"><i class="fas fa-edit mr-2"></i>Nuova Email</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="POST" action="<?php echo e(route('email.send')); ?>" id="emailForm">
|
||||
<form method="POST" action="<?php echo e(route('email.send')); ?>" id="emailForm" enctype="multipart/form-data">
|
||||
<?php echo csrf_field(); ?>
|
||||
|
||||
<?php if($senderAccounts->count() > 0): ?>
|
||||
<div class="form-group">
|
||||
<label for="mittente_id">Da (mittente)</label>
|
||||
<select name="mittente_id" id="mittente_id" class="form-control">
|
||||
<option value="0">Sistema — <?php echo e(optional(\App\Models\EmailSetting::getActive())->email_address ?? 'Configura email'); ?></option>
|
||||
<?php $__currentLoopData = $senderAccounts; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $sender): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
|
||||
<option value="<?php echo e($sender->id); ?>"><?php echo e($sender->email_name ?: $sender->email_address); ?> <<?php echo e($sender->email_address); ?>></option>
|
||||
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?>
|
||||
</select>
|
||||
<small class="text-muted">Seleziona un mittente alternativo per l'invio. Lascia "Sistema" per usare l'account predefinito.</small>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Destinatari *</label>
|
||||
<div class="mb-2">
|
||||
|
||||
Reference in New Issue
Block a user