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(); $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')); } public function invia(Request $request) { $this->authorizeWrite('mailing'); $data = $request->validate([ 'oggetto' => 'required|string|max:255', 'corpo' => 'required', 'lista_id' => 'nullable|exists:mailing_liste,id', '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')) { $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); $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) { $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', 'senderAccounts')); } public function invioElabora(Request $request) { $this->authorizeWrite('mailing'); $data = $request->validate([ 'liste' => 'required|array', 'liste.*' => 'exists:mailing_liste,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', ]); $sender = null; if (!empty($data['mittente_id'])) { $sender = SenderAccount::findOrFail($data['mittente_id']); } $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); $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; } }