authorizeWrite('mailing'); $individui = collect(); $individuiIds = $request->input('individui'); if ($individuiIds) { $ids = array_map('trim', explode(',', $individuiIds)); $individui = Individuo::with('contatti')->whereIn('id', $ids)->get(); } $liste = MailingList::all(); $documenti = Documento::orderBy('nome_file')->get(); $senderAccounts = SenderAccount::active()->get(); $settings = EmailSetting::getActive(); $firme = $settings?->firme ?? collect(); $documentiSelezionati = collect(); if ($request->has('documenti_selezionati')) { $docIds = array_filter(array_map('trim', explode(',', $request->input('documenti_selezionati')))); $documentiSelezionati = Documento::whereIn('id', $docIds)->get(); } return view('mailing.nuovo', compact('individui', 'liste', 'documenti', 'documentiSelezionati', 'senderAccounts', 'firme')); } public function invia(Request $request) { $this->authorizeWrite('mailing'); $data = $request->validate([ 'oggetto' => 'required|string|max:255', 'corpo' => 'required', 'lista_id' => 'nullable|exists:mailing_lists,id', 'destinatari_ids' => 'nullable|string', 'documenti_selezionati' => 'nullable|string', 'allegato' => 'nullable|file|max:10240', 'mittente_id' => 'nullable|integer|exists:sender_accounts,id', 'firma_id' => 'nullable|integer|exists:firme,id', ]); $sender = null; if (!empty($data['mittente_id'])) { $sender = SenderAccount::findOrFail($data['mittente_id']); } elseif (!empty($data['lista_id'])) { $lista = MailingList::with('senderAccount')->find($data['lista_id']); if ($lista && $lista->senderAccount) { $sender = $lista->senderAccount; } } $documentiAllegati = []; if ($request->hasFile('allegato')) { $file = $request->file('allegato'); $nomeOriginale = $file->getClientOriginalName(); $path = $file->store('documenti/allegati'); $documento = Documento::create([ 'nome_file' => $nomeOriginale, 'file_path' => $path, 'tipo' => 'allegato', 'tipologia' => 'allegato_email', 'visibilita' => 'pubblico', 'dimensione' => $file->getSize(), 'mime_type' => $file->getMimeType(), 'user_id' => auth()->id(), ]); $documentiAllegati[] = $documento->id; } if (!empty($data['documenti_selezionati'])) { $docIds = array_filter(array_map('trim', explode(',', $data['documenti_selezionati']))); $documentiAllegati = array_merge($documentiAllegati, $docIds); } $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(); } } $totaleDestinatari = $destinatari->count(); $emails = $destinatari->map(fn($ind) => $ind->contatti->where('tipo', 'email')->first()?->valore) ->filter()->unique()->values(); $skipped = $totaleDestinatari - $emails->count(); if ($skipped > 0) { Log::info("Mailing invia: {$skipped} contatti saltati perché senza email", [ 'lista_id' => $data['lista_id'] ?? null, ]); } if ($emails->isEmpty()) { return back()->with('error', 'Nessun destinatario email valido trovato.')->withInput(); } $attachmentPaths = $this->resolveMailingAttachmentPaths($documentiAllegati); $firmaId = $data['firma_id'] ?? null; if (!$firmaId && !empty($data['lista_id'])) { $lista = MailingList::with('firma')->find($data['lista_id']); $firmaId = $lista?->firma?->id; } $corpoConFirma = $data['corpo']; if ($firmaId) { $firma = Firma::find($firmaId); if ($firma && $firma->contenuto_html) { $corpoConFirma .= "\n\n---\n" . $firma->contenuto_html; } } $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, 'firma_id' => $firmaId, ]); $ok = 0; $fail = 0; $logFalliti = []; foreach ($emails as $email) { try { if ($sender) { $sender->sendEmail($email, $data['oggetto'], $corpoConFirma, $attachmentPaths); } else { $this->sendViaSystem($email, $data['oggetto'], $corpoConFirma, $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) { $this->authorizeWrite('mailing'); $liste = MailingList::with('firma')->where('attiva', true)->orderBy('nome')->get(); $documenti = Documento::orderBy('nome_file')->get(); $senderAccounts = SenderAccount::active()->get(); $settings = EmailSetting::getActive(); $firme = $settings?->firme ?? collect(); return view('mailing.invio', compact('liste', 'documenti', 'senderAccounts', 'firme')); } public function invioElabora(Request $request) { $this->authorizeWrite('mailing'); $data = $request->validate([ 'liste' => 'required|array', 'liste.*' => 'exists:mailing_lists,id', 'oggetto' => 'required|string|max:255', 'corpo' => 'required', 'documenti_selezionati' => 'nullable|array', 'documenti_selezionati.*' => 'exists:documenti,id', 'allegato' => 'nullable|file|max:10240', 'mittente_id' => 'nullable|integer|exists:sender_accounts,id', 'firma_id' => 'nullable|integer|exists:firme,id', ]); $listeModels = MailingList::with('firma', 'senderAccount')->whereIn('id', $data['liste'])->get(); $sender = null; if (!empty($data['mittente_id'])) { $sender = SenderAccount::findOrFail($data['mittente_id']); } elseif ($listeModels->count() === 1) { $sender = $listeModels->first()->senderAccount; } $documentiAllegati = []; if ($request->hasFile('allegato')) { $file = $request->file('allegato'); $nomeOriginale = $file->getClientOriginalName(); $path = $file->store('documenti/allegati'); $documento = Documento::create([ 'nome_file' => $nomeOriginale, 'file_path' => $path, 'tipo' => 'allegato', 'tipologia' => 'allegato_email', 'visibilita' => 'pubblico', 'dimensione' => $file->getSize(), 'mime_type' => $file->getMimeType(), 'user_id' => auth()->id(), ]); $documentiAllegati[] = $documento->id; } if (!empty($data['documenti_selezionati'])) { $documentiAllegati = array_merge($documentiAllegati, $data['documenti_selezionati']); } $contatti = MailingContact::whereIn('mailing_list_id', $data['liste']) ->where('opt_in', true) ->with('individuo.contatti') ->get(); $totaleContatti = $contatti->count(); $emails = $contatti->map(fn($c) => $c->individuo?->contatti->where('tipo', 'email')->first()?->valore) ->filter()->unique()->values(); $skipped = $totaleContatti - $emails->count(); if ($skipped > 0) { Log::info("Mailing invio elabora: {$skipped} contatti saltati perché senza email", [ 'liste_ids' => $data['liste'], ]); } if ($emails->isEmpty()) { return back()->with('error', 'Nessun destinatario email valido trovato nelle liste selezionate.'); } $attachmentPaths = $this->resolveMailingAttachmentPaths($documentiAllegati); $firmaId = $data['firma_id'] ?? null; if (!$firmaId && $listeModels->count() === 1) { $firmaId = $listeModels->first()->firma_id; } $corpoConFirma = $data['corpo']; if ($firmaId) { $firma = Firma::find($firmaId); if ($firma && $firma->contenuto_html) { $corpoConFirma .= "\n\n---\n" . $firma->contenuto_html; } } $listeNomi = $listeModels->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, 'firma_id' => $firmaId, ]); $ok = 0; $fail = 0; $logFalliti = []; foreach ($emails as $email) { try { if ($sender) { $sender->sendEmail($email, $data['oggetto'], $corpoConFirma, $attachmentPaths); } else { $this->sendViaSystem($email, $data['oggetto'], $corpoConFirma, $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); $fromAddress = \Symfony\Component\Mime\Address::create( $settings->getEffectiveFromAddress(), $settings->getEffectiveFromName() ); $email = (new \Symfony\Component\Mime\Email()) ->from($fromAddress) ->to($to) ->subject($subject) ->text($body); $replyTo = $settings->getEffectiveReplyTo(); if ($replyTo) { $email->replyTo($replyTo); } 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; } }