393 lines
14 KiB
PHP
393 lines
14 KiB
PHP
<?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
|
|
{
|
|
public function nuovo(Request $request)
|
|
{
|
|
$this->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;
|
|
}
|
|
}
|