fix mailing an email

This commit is contained in:
2026-06-23 08:05:51 +02:00
parent c5127da750
commit def8d22544
2479 changed files with 325393 additions and 781 deletions
+11 -3
View File
@@ -28,16 +28,17 @@ class BackupRunCommand extends Command
{
$this->info('Avvio backup...');
$options = [];
if ($this->option('no-files')) {
$this->backupService->saveConfig(['backup_include_files' => false]);
$options['include_files'] = false;
$this->warn('Files esclusi dal backup.');
}
if ($this->option('no-env')) {
$this->backupService->saveConfig(['backup_include_env' => false]);
$options['include_env'] = false;
$this->warn('.env escluso dal backup.');
}
$result = $this->backupService->run();
$result = $this->backupService->run($options);
if ($result['success']) {
$this->info(' Backup completato con successo!');
@@ -45,6 +46,13 @@ class BackupRunCommand extends Command
$this->line('Dimensione: ' . $result['size_formatted']);
$this->line('Percorso: ' . storage_path('app/backups/' . $result['filename']));
if (!empty($result['steps'])) {
$this->line('Contenuto:');
foreach ($result['steps'] as $step) {
$this->line(' - ' . $step);
}
}
return Command::SUCCESS;
}
@@ -34,8 +34,9 @@ class BackupController extends Controller
$result = $this->backupService->run();
if ($result['success']) {
$steps = !empty($result['steps']) ? ' — ' . implode(', ', $result['steps']) : '';
return redirect()->route('admin.backup.index')
->with('success', 'Backup completato: ' . ($result['filename'] ?? '') . ' (' . ($result['size_formatted'] ?? '') . ')');
->with('success', 'Backup completato: ' . ($result['filename'] ?? '') . ' (' . ($result['size_formatted'] ?? '') . ')' . $steps);
}
return redirect()->route('admin.backup.index')
@@ -7,12 +7,15 @@ use App\Http\Controllers\Controller;
use App\Models\EmailAttachment;
use App\Models\EmailFolder;
use App\Models\EmailSetting;
use App\Models\Firma;
use App\Models\SenderAccount;
use App\Services\GoogleOAuthService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\Mime\Address;
use Symfony\Component\Mime\Email;
class EmailSettingsController extends Controller
{
@@ -22,7 +25,8 @@ class EmailSettingsController extends Controller
$settings = EmailSetting::first() ?? new EmailSetting();
$senderAccounts = SenderAccount::orderBy('email_address')->get();
$googleStatus = app(GoogleOAuthService::class)->getConnectionStatus();
return view('admin.email-settings.index', compact('settings', 'senderAccounts', 'googleStatus'));
$firme = $settings->exists ? $settings->firme : collect();
return view('admin.email-settings.index', compact('settings', 'senderAccounts', 'googleStatus', 'firme'));
}
public function save(Request $request)
@@ -41,6 +45,8 @@ class EmailSettingsController extends Controller
'smtp_password' => 'nullable|string',
'email_address' => 'required|email',
'email_name' => 'nullable|string|max:255',
'from_email' => 'nullable|email|max:255',
'from_name' => 'nullable|string|max:255',
'reply_to' => 'nullable|email',
'sync_interval_minutes' => 'nullable|integer|min:1|max:60',
'is_active' => 'boolean',
@@ -128,25 +134,20 @@ class EmailSettingsController extends Controller
return response()->json(['success' => false, 'message' => 'Impossibile creare il mailer SMTP. Verifica la configurazione.']);
}
$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';
}
}
$smtpUser = $settings->smtp_username ?: $settings->email_address;
$fromName = $settings->getEffectiveFromName();
$replyToEmail = $settings->from_email ?: $smtpUser;
$fromAddress = \Symfony\Component\Mime\Address::create($settings->email_address, $fromName);
$email = (new \Symfony\Component\Mime\Email())
->from($fromAddress)
$email = (new Email())
->from(Address::create($smtpUser, $fromName))
->to($request->email)
->subject('Test Configurazione SMTP - Glastree')
->text('Test email da Glastree - Configurazione SMTP funziona!');
if ($replyToEmail !== $smtpUser) {
$email->replyTo(Address::create($replyToEmail, $fromName));
}
$mailer->send($email);
\Illuminate\Support\Facades\Log::info('Test SMTP ok', ['to' => $request->email, 'from' => $settings->email_address]);
@@ -294,4 +295,66 @@ class EmailSettingsController extends Controller
return response()->json(['success' => false, 'message' => 'Errore invio: ' . $e->getMessage()]);
}
}
public function firmaStore(Request $request)
{
$this->authorizeWrite('settings');
$data = $request->validate([
'nome' => 'required|string|max:255',
'contenuto_html' => 'nullable|string',
'is_default' => 'boolean',
]);
$settings = EmailSetting::getActive();
if (!$settings) {
return redirect('/impostazioni/email')->with('error', 'Account email non configurato.');
}
$firma = $settings->firme()->create($data);
if ($data['is_default'] ?? false) {
$settings->firme()->where('id', '!=', $firma->id)->update(['is_default' => false]);
}
return redirect('/impostazioni/email#firme')->with('success', 'Firma "' . $firma->nome . '" creata.');
}
public function firmaUpdate(Request $request, int $id)
{
$this->authorizeWrite('settings');
$data = $request->validate([
'nome' => 'required|string|max:255',
'contenuto_html' => 'nullable|string',
'is_default' => 'boolean',
]);
$firma = Firma::findOrFail($id);
$firma->update($data);
if ($data['is_default'] ?? false) {
Firma::where('email_setting_id', $firma->email_setting_id)
->where('id', '!=', $firma->id)
->update(['is_default' => false]);
}
return redirect('/impostazioni/email#firme')->with('success', 'Firma "' . $firma->nome . '" aggiornata.');
}
public function firmaSetDefault(int $id)
{
$this->authorizeWrite('settings');
$firma = Firma::findOrFail($id);
Firma::where('email_setting_id', $firma->email_setting_id)->update(['is_default' => false]);
$firma->update(['is_default' => true]);
return redirect('/impostazioni/email#firme')->with('success', 'Firma "' . $firma->nome . '" impostata come predefinita.');
}
public function firmaDestroy(int $id)
{
$this->authorizeWrite('settings');
$firma = Firma::findOrFail($id);
$nome = $firma->nome;
$firma->delete();
return redirect('/impostazioni/email#firme')->with('success', 'Firma "' . $nome . '" eliminata.');
}
}
+42 -26
View File
@@ -11,6 +11,7 @@ use App\Models\MailingList;
use App\Models\Gruppo;
use App\Models\Individuo;
use App\Models\Documento;
use App\Models\Firma;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Storage;
@@ -96,6 +97,9 @@ class EmailController extends Controller
$documenti = Documento::orderBy('nome_file')->get();
$settings = EmailSetting::getActive();
$firme = $settings?->firme ?? collect();
$prefill = [];
if ($request->reply_to) {
$original = EmailMessage::find($request->reply_to);
@@ -113,7 +117,7 @@ class EmailController extends Controller
}
}
return view('email.compose', compact('folders', 'mailingLists', 'gruppi', 'individui', 'documenti', 'prefill', 'senderAccounts'));
return view('email.compose', compact('folders', 'mailingLists', 'gruppi', 'individui', 'documenti', 'prefill', 'senderAccounts', 'firme'));
}
public function send(Request $request)
@@ -128,6 +132,7 @@ class EmailController extends Controller
$rules['to'] = 'required';
}
$rules['firma_id'] = 'nullable|integer|exists:firme,id';
$validated = $request->validate($rules);
$recipients = $this->resolveRecipients($request);
@@ -159,9 +164,11 @@ class EmailController extends Controller
$imapError = null;
$attachments = $this->processAttachments($request);
$firmaId = $request->firma_id;
foreach ($recipients as $recipient) {
try {
$bodyWithSig = $this->appendSignature($validated['body'], $settings->getSignature(), $settings->signature_enabled ?? false);
$bodyWithSig = $this->appendSignature($validated['body'], $firmaId, $settings);
$this->sendViaImap($settings, $recipient, $validated['subject'], $bodyWithSig, $request->cc, $attachments);
$imapSuccess = true;
} catch (\Exception $e) {
@@ -171,7 +178,7 @@ class EmailController extends Controller
}
try {
$bodyWithSig = $this->appendSignature($validated['body'], $settings->getSignature(), $settings->signature_enabled ?? false);
$bodyWithSig = $this->appendSignature($validated['body'], $firmaId, $settings);
$this->storeSentMessage($settings, $recipients, $validated['subject'], $bodyWithSig, $request->cc);
} catch (\Exception $e) {
\Illuminate\Support\Facades\Log::error('Store sent message error', ['error' => $e->getMessage()]);
@@ -189,12 +196,16 @@ class EmailController extends Controller
{
$sender = SenderAccount::findOrFail($request->mittente_id);
$attachmentPaths = $this->resolveAttachmentPaths($request);
$settings = EmailSetting::getActive();
$body = $this->appendSignature($validated['body'], $request->firma_id, $settings);
$ok = 0;
$errors = [];
foreach ($recipients as $recipient) {
try {
$sender->sendEmail($recipient, $validated['subject'], $validated['body'], $attachmentPaths);
$sender->sendEmail($recipient, $validated['subject'], $body, $attachmentPaths);
$ok++;
} catch (\Exception $e) {
$errors[] = $recipient . ': ' . $e->getMessage();
@@ -237,8 +248,8 @@ class EmailController extends Controller
'body_text' => $request->body,
'to_email' => $request->to,
'is_draft' => true,
'from_email' => $settings->email_address ?? '',
'from_name' => $settings->email_name ?? '',
'from_email' => $settings->getEffectiveFromAddress(),
'from_name' => $settings->getEffectiveFromName(),
]);
return response()->json(['success' => true, 'draft_id' => $message->id]);
@@ -496,12 +507,20 @@ class EmailController extends Controller
return array_filter(array_unique($recipients));
}
private function appendSignature(string $body, ?string $signature, bool $enabled): string
private function appendSignature(string $body, int|string|null $firmaId = null, ?EmailSetting $settings = null): string
{
if (empty($signature) || !$enabled) {
return $body;
if ($firmaId) {
$firma = Firma::find($firmaId);
if ($firma && $firma->contenuto_html) {
return $body . "\n\n---\n" . $firma->contenuto_html;
}
}
return $body . "\n\n---\n" . $signature;
if ($settings && $settings->signature && ($settings->signature_enabled ?? false)) {
return $body . "\n\n---\n" . $settings->signature;
}
return $body;
}
private function sendViaImap($settings, $to, $subject, $body, $cc = null, array $attachmentPaths = [])
@@ -526,26 +545,23 @@ class EmailController extends Controller
try {
$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)) {
$fromName = 'Glastree';
}
}
$fromAddress = \Symfony\Component\Mime\Address::create($settings->email_address, $fromName);
$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);
}
if ($cc) {
$email->cc($cc);
}
@@ -619,14 +635,14 @@ class EmailController extends Controller
private function storeSentMessage($settings, $recipients, $subject, $body, $cc = null)
{
$sentFolder = EmailFolder::where('type', 'sent')->first();
$bodyWithSignature = $this->appendSignature($body, $settings->getSignature(), $settings->signature_enabled ?? false);
$bodyWithSignature = $body;
$message = EmailMessage::create([
'email_folder_id' => $sentFolder->id,
'subject' => $subject,
'body_text' => $bodyWithSignature,
'from_email' => $settings->email_address,
'from_name' => $settings->email_name,
'from_email' => $settings->getEffectiveFromAddress(),
'from_name' => $settings->getEffectiveFromName(),
'to_email' => implode(', ', $recipients),
'cc' => $cc,
'is_sent' => true,
+54 -8
View File
@@ -66,7 +66,7 @@ class GruppoController extends Controller
$entityType = 'gruppi';
$columnWidths = $vista && $vista->colonne_larghezze ? $vista->colonne_larghezze : [];
$gruppiQuery = Gruppo::with(['diocesi', 'parent', 'children', 'individui', 'avatar', 'tags']);
$gruppiQuery = Gruppo::with(['diocesi', 'gruppoContatti', 'parent', 'children', 'individui', 'avatar', 'tags']);
if (request()->filled('tag')) {
$tagSlugs = (array) request()->input('tag');
@@ -142,7 +142,7 @@ class GruppoController extends Controller
public function create(Request $request)
{
$this->authorizeWrite('gruppi');
$diocesi = Diocesi::orderBy('nome')->get();
$diocesi = Diocesi::orderBy('id')->get();
$allGruppi = Gruppo::orderBy('nome')->get();
$selectedParent = $request->query('parent_id') ? Gruppo::find($request->query('parent_id')) : null;
$individui = Individuo::orderBy('cognome')->orderBy('nome')->get();
@@ -157,7 +157,8 @@ class GruppoController extends Controller
'nome' => 'required|string|max:255',
'descrizione' => 'nullable|string',
'parent_id' => 'nullable|exists:gruppi,id',
'diocesi_id' => 'nullable|exists:diocesi,id',
'diocesi_ids' => 'nullable|array',
'diocesi_ids.*' => 'exists:diocesi,id',
'responsabile_ids' => 'nullable|array',
'responsabile_ids.*' => 'exists:individui,id',
'indirizzo_incontro' => 'nullable|string|max:500',
@@ -165,6 +166,11 @@ class GruppoController extends Controller
'città_incontro' => 'nullable|string|max:255',
'sigla_provincia_incontro' => 'nullable|string|max:2',
'mappa_posizione' => 'nullable|string',
'contatti' => 'nullable|array',
'contatti.*.tipo' => 'required|string|in:email,telefono,cellulare',
'contatti.*.valore' => 'required|string|max:255',
'contatti.*.etichetta' => 'nullable|string|max:255',
'contatti.*.is_primary' => 'nullable|boolean',
'tags' => 'nullable|array',
'tags.*' => 'exists:tags,id',
]);
@@ -175,6 +181,21 @@ class GruppoController extends Controller
$gruppo = Gruppo::create($data);
if (!empty($data['diocesi_ids'])) {
$gruppo->diocesi()->sync($data['diocesi_ids']);
}
if (!empty($data['contatti'])) {
foreach ($data['contatti'] as $contattoData) {
$gruppo->gruppoContatti()->create([
'tipo' => $contattoData['tipo'],
'valore' => $contattoData['valore'],
'etichetta' => $contattoData['etichetta'] ?? null,
'is_primary' => $contattoData['is_primary'] ?? false,
]);
}
}
if ($request->has('tags')) {
$gruppo->tags()->sync($request->tags);
}
@@ -196,7 +217,7 @@ class GruppoController extends Controller
public function show($id)
{
$this->authorizeRead('gruppi');
$gruppo = Gruppo::with(['parent', 'children', 'diocesi', 'individui.contatti', 'individui.documenti', 'individui' => function ($q) {
$gruppo = Gruppo::with(['parent', 'children', 'diocesi', 'gruppoContatti', 'individui.contatti', 'individui.documenti', 'individui' => function ($q) {
$q->withPivot('ruolo_ids', 'ruolo_nel_gruppo', 'data_adesione');
}, 'avatar', 'tags', 'eventi' => function ($q) {
$q->where('is_incontro_gruppo', true);
@@ -207,13 +228,14 @@ class GruppoController extends Controller
public function edit($id)
{
$this->authorizeWrite('gruppi');
$gruppo = Gruppo::with(['individui.contatti', 'individui.documenti', 'avatar', 'tags'])->findOrFail($id);
$diocesi = Diocesi::orderBy('nome')->get();
$gruppo = Gruppo::with(['individui.contatti', 'individui.documenti', 'gruppoContatti', 'avatar', 'tags'])->findOrFail($id);
$diocesi = Diocesi::orderBy('id')->get();
$gruppi = Gruppo::where('id', '!=', $gruppo->id)->orderBy('nome')->get();
$membri = $gruppo->individui()->withPivot('ruolo_ids', 'ruolo_nel_gruppo', 'data_adesione')->orderBy('cognome')->orderBy('nome')->get();
$tags = Tag::orderBy('name')->get();
$selectedTags = $gruppo->tags->pluck('id')->toArray();
return view('gruppi.edit', compact('gruppo', 'diocesi', 'gruppi', 'membri', 'tags', 'selectedTags'));
$selectedDiocesiIds = $gruppo->diocesi()->pluck('diocesi.id')->toArray();
return view('gruppi.edit', compact('gruppo', 'diocesi', 'gruppi', 'membri', 'tags', 'selectedTags', 'selectedDiocesiIds'));
}
public function update(Request $request, $id)
@@ -224,7 +246,8 @@ class GruppoController extends Controller
'nome' => 'required|string|max:255',
'descrizione' => 'nullable|string',
'parent_id' => 'nullable|exists:gruppi,id',
'diocesi_id' => 'nullable|exists:diocesi,id',
'diocesi_ids' => 'nullable|array',
'diocesi_ids.*' => 'exists:diocesi,id',
'responsabile_ids' => 'nullable|array',
'responsabile_ids.*' => 'exists:individui,id',
'indirizzo_incontro' => 'nullable|string|max:500',
@@ -232,6 +255,11 @@ class GruppoController extends Controller
'città_incontro' => 'nullable|string|max:255',
'sigla_provincia_incontro' => 'nullable|string|max:2',
'mappa_posizione' => 'nullable|string',
'contatti' => 'nullable|array',
'contatti.*.tipo' => 'required|string|in:email,telefono,cellulare',
'contatti.*.valore' => 'required|string|max:255',
'contatti.*.etichetta' => 'nullable|string|max:255',
'contatti.*.is_primary' => 'nullable|boolean',
'tags' => 'nullable|array',
'tags.*' => 'exists:tags,id',
]);
@@ -244,6 +272,24 @@ class GruppoController extends Controller
$gruppo->update($data);
if ($request->has('diocesi_ids')) {
$gruppo->diocesi()->sync($data['diocesi_ids'] ?? []);
} else {
$gruppo->diocesi()->detach();
}
if ($request->has('contatti')) {
$gruppo->gruppoContatti()->delete();
foreach ($data['contatti'] as $contattoData) {
$gruppo->gruppoContatti()->create([
'tipo' => $contattoData['tipo'],
'valore' => $contattoData['valore'],
'etichetta' => $contattoData['etichetta'] ?? null,
'is_primary' => $contattoData['is_primary'] ?? false,
]);
}
}
if ($request->has('tags')) {
$gruppo->tags()->sync($request->tags);
} else {
+1 -1
View File
@@ -152,7 +152,7 @@ public function create()
{
$this->authorizeWrite('individui');
$comuni = Comune::orderBy('nome')->get();
$diocesi = Diocesi::orderBy('nome')->get();
$diocesi = Diocesi::orderBy('id')->get();
$tags = Tag::orderBy('name')->get();
return view('individui.create', compact('comuni', 'diocesi', 'tags'));
}
+65 -22
View File
@@ -10,6 +10,8 @@ use App\Models\MailingMessaggio;
use App\Models\SenderAccount;
use App\Models\Individuo;
use App\Models\Documento;
use App\Models\EmailSetting;
use App\Models\Firma;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
@@ -30,6 +32,8 @@ class MailingController extends Controller
$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')) {
@@ -37,7 +41,7 @@ class MailingController extends Controller
$documentiSelezionati = Documento::whereIn('id', $docIds)->get();
}
return view('mailing.nuovo', compact('individui', 'liste', 'documenti', 'documentiSelezionati', 'senderAccounts'));
return view('mailing.nuovo', compact('individui', 'liste', 'documenti', 'documentiSelezionati', 'senderAccounts', 'firme'));
}
public function invia(Request $request)
@@ -46,16 +50,22 @@ class MailingController extends Controller
$data = $request->validate([
'oggetto' => 'required|string|max:255',
'corpo' => 'required',
'lista_id' => 'nullable|exists:mailing_liste,id',
'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 = [];
@@ -112,6 +122,20 @@ class MailingController extends Controller
$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(),
@@ -121,6 +145,7 @@ class MailingController extends Controller
'totale_destinatari' => $emails->count(),
'mittente_nome' => $sender?->email_name,
'mittente_email' => $sender?->email_address,
'firma_id' => $firmaId,
]);
$ok = 0;
@@ -130,9 +155,9 @@ class MailingController extends Controller
foreach ($emails as $email) {
try {
if ($sender) {
$sender->sendEmail($email, $data['oggetto'], $data['corpo'], $attachmentPaths);
$sender->sendEmail($email, $data['oggetto'], $corpoConFirma, $attachmentPaths);
} else {
$this->sendViaSystem($email, $data['oggetto'], $data['corpo'], $attachmentPaths);
$this->sendViaSystem($email, $data['oggetto'], $corpoConFirma, $attachmentPaths);
}
$ok++;
} catch (\Throwable $e) {
@@ -181,11 +206,13 @@ class MailingController extends Controller
public function invio(Request $request)
{
$this->authorizeWrite('mailing');
$liste = MailingList::where('attiva', true)->orderBy('nome')->get();
$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'));
return view('mailing.invio', compact('liste', 'documenti', 'senderAccounts', 'firme'));
}
public function invioElabora(Request $request)
@@ -193,18 +220,23 @@ class MailingController extends Controller
$this->authorizeWrite('mailing');
$data = $request->validate([
'liste' => 'required|array',
'liste.*' => 'exists:mailing_liste,id',
'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 = [];
@@ -254,7 +286,20 @@ class MailingController extends Controller
$attachmentPaths = $this->resolveMailingAttachmentPaths($documentiAllegati);
$listeNomi = MailingList::whereIn('id', $data['liste'])->pluck('nome')->implode(', ');
$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,
@@ -265,6 +310,7 @@ class MailingController extends Controller
'totale_destinatari' => $emails->count(),
'mittente_nome' => $sender?->email_name,
'mittente_email' => $sender?->email_address,
'firma_id' => $firmaId,
]);
$ok = 0;
@@ -274,9 +320,9 @@ class MailingController extends Controller
foreach ($emails as $email) {
try {
if ($sender) {
$sender->sendEmail($email, $data['oggetto'], $data['corpo'], $attachmentPaths);
$sender->sendEmail($email, $data['oggetto'], $corpoConFirma, $attachmentPaths);
} else {
$this->sendViaSystem($email, $data['oggetto'], $data['corpo'], $attachmentPaths);
$this->sendViaSystem($email, $data['oggetto'], $corpoConFirma, $attachmentPaths);
}
$ok++;
} catch (\Throwable $e) {
@@ -347,18 +393,10 @@ class MailingController extends Controller
$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);
$fromAddress = \Symfony\Component\Mime\Address::create(
$settings->getEffectiveFromAddress(),
$settings->getEffectiveFromName()
);
$email = (new \Symfony\Component\Mime\Email())
->from($fromAddress)
@@ -366,6 +404,11 @@ class MailingController extends Controller
->subject($subject)
->text($body);
$replyTo = $settings->getEffectiveReplyTo();
if ($replyTo) {
$email->replyTo($replyTo);
}
foreach ($attachmentPaths as $path) {
$email->attachFromPath($path);
}
+18 -3
View File
@@ -3,6 +3,7 @@
namespace App\Http\Controllers;
use App\Models\MailingList;
use App\Models\SenderAccount;
use App\Models\Tag;
use Illuminate\Http\Request;
@@ -60,7 +61,10 @@ class MailingListController extends Controller
{
$this->authorizeWrite('mailing');
$tags = Tag::orderBy('name')->get();
return view('mailing-liste.create', compact('tags'));
$settings = \App\Models\EmailSetting::getActive();
$firme = $settings?->firme ?? collect();
$senderAccounts = SenderAccount::active()->get();
return view('mailing-liste.create', compact('tags', 'firme', 'senderAccounts'));
}
public function store(Request $request)
@@ -73,6 +77,8 @@ class MailingListController extends Controller
'contatti_json' => 'nullable|string',
'tags' => 'nullable|array',
'tags.*' => 'exists:tags,id',
'firma_id' => 'nullable|integer|exists:firme,id',
'sender_account_id' => 'nullable|integer|exists:sender_accounts,id',
]);
$data['user_id'] = auth()->id();
@@ -83,6 +89,8 @@ class MailingListController extends Controller
'descrizione' => $data['descrizione'] ?? null,
'attiva' => $data['attiva'],
'user_id' => $data['user_id'],
'firma_id' => $data['firma_id'] ?? null,
'sender_account_id' => $data['sender_account_id'] ?? null,
]);
if ($request->has('tags')) {
@@ -117,10 +125,13 @@ class MailingListController extends Controller
public function edit($mailingList)
{
$this->authorizeWrite('mailing');
$mailingList = MailingList::with(['contatti.individuo.contatti', 'tags'])->findOrFail($mailingList);
$mailingList = MailingList::with(['contatti.individuo.contatti', 'tags', 'firma', 'senderAccount'])->findOrFail($mailingList);
$tags = Tag::orderBy('name')->get();
$selectedTags = $mailingList->tags->pluck('id')->toArray();
return view('mailing-liste.edit', compact('mailingList', 'tags', 'selectedTags'));
$settings = \App\Models\EmailSetting::getActive();
$firme = $settings?->firme ?? collect();
$senderAccounts = SenderAccount::active()->get();
return view('mailing-liste.edit', compact('mailingList', 'tags', 'selectedTags', 'firme', 'senderAccounts'));
}
public function update(Request $request, $mailingList)
@@ -135,6 +146,8 @@ class MailingListController extends Controller
'contatti_json' => 'nullable|string',
'tags' => 'nullable|array',
'tags.*' => 'exists:tags,id',
'firma_id' => 'nullable|integer|exists:firme,id',
'sender_account_id' => 'nullable|integer|exists:sender_accounts,id',
]);
$data['attiva'] = $data['attiva'] ?? false;
@@ -143,6 +156,8 @@ class MailingListController extends Controller
'nome' => $data['nome'],
'descrizione' => $data['descrizione'] ?? null,
'attiva' => $data['attiva'],
'firma_id' => $data['firma_id'] ?? null,
'sender_account_id' => $data['sender_account_id'] ?? null,
]);
if ($request->has('tags')) {
+4 -4
View File
@@ -515,7 +515,7 @@ class ReportController extends Controller
$rows[] = [
'livello' => 0,
'nome' => $gruppo->nome,
'diocesi' => $gruppo->diocesi?->nome ?? '-',
'diocesi' => $gruppo->diocesi->count() > 0 ? $gruppo->diocesi->pluck('nome')->implode(', ') : '-',
'membri' => $gruppo->individui()->count(),
'padre' => '-',
];
@@ -538,7 +538,7 @@ class ReportController extends Controller
$rows[] = [
'livello' => $level,
'nome' => str_repeat(' ', $level) . $child->nome,
'diocesi' => $child->diocesi?->nome ?? '-',
'diocesi' => $child->diocesi->count() > 0 ? $child->diocesi->pluck('nome')->implode(', ') : '-',
'membri' => $child->individui()->count(),
'padre' => $parent?->nome ?? '-',
];
@@ -554,7 +554,7 @@ class ReportController extends Controller
$parent = $g->parent;
return [
'nome' => $g->full_path,
'diocesi' => $g->diocesi?->nome ?? '-',
'diocesi' => $g->diocesi->count() > 0 ? $g->diocesi->pluck('nome')->implode(', ') : '-',
'membri' => $g->individui_count,
'padre' => $parent?->nome ?? '-',
'responsabili' => $g->getResponsabili()->pluck('cognome')->implode(', ') ?: '-',
@@ -895,7 +895,7 @@ class ReportController extends Controller
return [
'nome' => $g->nome,
'descrizione' => $g->descrizione ?? '-',
'diocesi' => $g->diocesi?->nome ?? '-',
'diocesi' => $g->diocesi->count() > 0 ? $g->diocesi->pluck('nome')->implode(', ') : '-',
'livello' => $g->parent_id ? (count($g->getAncestors()) + 1) : 0,
'membri' => $g->individui_count,
'padre' => $parent?->nome ?? '-',
+3 -3
View File
@@ -3,15 +3,15 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class Diocesi extends Model
{
protected $table = 'diocesi';
protected $fillable = ['nome', 'regione'];
public function gruppi(): HasMany
public function gruppi(): BelongsToMany
{
return $this->hasMany(Gruppo::class);
return $this->belongsToMany(Gruppo::class, 'diocesi_gruppo');
}
}
+36 -1
View File
@@ -15,7 +15,8 @@ class EmailSetting extends Model
protected $fillable = [
'imap_host', 'imap_port', 'imap_encryption', 'imap_username', 'imap_password',
'smtp_host', 'smtp_port', 'smtp_encryption', 'smtp_username', 'smtp_password',
'email_address', 'email_name', 'reply_to', 'sync_interval_minutes',
'email_address', 'email_name', 'from_email', 'from_name', 'reply_to',
'sync_interval_minutes',
'last_sync_at', 'is_active', 'signature', 'signature_enabled',
'auth_method', 'google_oauth_connection_id',
];
@@ -247,4 +248,38 @@ class EmailSetting extends Model
{
return $this->signature;
}
public function getEffectiveFromAddress(): string
{
return $this->from_email ?: $this->email_address;
}
public function getEffectiveFromName(): string
{
$name = $this->from_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 getEffectiveReplyTo(): ?string
{
if ($this->from_email) {
return $this->from_email;
}
return $this->reply_to ?: null;
}
public function firme(): HasMany
{
return $this->hasMany(Firma::class);
}
public function firmaPredefinita(): ?Firma
{
return $this->firme()->where('is_default', true)->first() ?? $this->firme()->first();
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Firma extends Model
{
protected $table = 'firme';
protected $fillable = [
'email_setting_id',
'nome',
'contenuto_html',
'is_default',
];
protected $casts = [
'is_default' => 'boolean',
];
public function emailSetting(): BelongsTo
{
return $this->belongsTo(EmailSetting::class);
}
}
+21 -2
View File
@@ -24,6 +24,8 @@ class Gruppo extends Model
'responsabile_ids' => 'array',
];
protected $appends = ['email_primaria', 'telefono_primario'];
public function tenant(): BelongsTo
{
return $this->belongsTo(Tenant::class);
@@ -39,9 +41,14 @@ class Gruppo extends Model
return $this->hasMany(Gruppo::class, 'parent_id');
}
public function diocesi(): BelongsTo
public function diocesi(): BelongsToMany
{
return $this->belongsTo(Diocesi::class);
return $this->belongsToMany(Diocesi::class, 'diocesi_gruppo');
}
public function gruppoContatti(): HasMany
{
return $this->hasMany(GruppoContatto::class, 'gruppo_id');
}
public function individui(): BelongsToMany
@@ -66,6 +73,18 @@ class Gruppo extends Model
return null;
}
public function getEmailPrimariaAttribute(): ?string
{
return $this->gruppoContatti()->where('tipo', 'email')->where('is_primary', true)->first()?->valore
?? $this->gruppoContatti()->where('tipo', 'email')->first()?->valore;
}
public function getTelefonoPrimarioAttribute(): ?string
{
return $this->gruppoContatti()->whereIn('tipo', ['telefono', 'cellulare'])->where('is_primary', true)->first()?->valore
?? $this->gruppoContatti()->whereIn('tipo', ['telefono', 'cellulare'])->first()?->valore;
}
public function getResponsabili()
{
if (empty($this->responsabile_ids)) {
+23
View File
@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class GruppoContatto extends Model
{
protected $table = 'gruppo_contatti';
protected $fillable = ['gruppo_id', 'tipo', 'valore', 'etichetta', 'is_primary'];
protected $casts = [
'is_primary' => 'boolean',
];
public function gruppo(): BelongsTo
{
return $this->belongsTo(Gruppo::class);
}
}
+11 -1
View File
@@ -12,7 +12,7 @@ class MailingList extends Model
use HasTagsLight;
protected $table = 'mailing_lists';
protected $fillable = ['tenant_id', 'user_id', 'nome', 'descrizione', 'attiva'];
protected $fillable = ['tenant_id', 'user_id', 'nome', 'descrizione', 'attiva', 'firma_id', 'sender_account_id'];
protected $casts = ['attiva' => 'boolean'];
@@ -36,6 +36,16 @@ class MailingList extends Model
return $this->hasMany(MailingMessaggio::class);
}
public function firma(): BelongsTo
{
return $this->belongsTo(Firma::class);
}
public function senderAccount(): BelongsTo
{
return $this->belongsTo(SenderAccount::class);
}
public function getIndividui()
{
return Individuo::whereHas('mailingContacts', function ($q) {
+1 -1
View File
@@ -11,7 +11,7 @@ class MailingMessaggio extends Model
protected $fillable = [
'tenant_id', 'mailing_list_id', 'user_id', 'oggetto', 'corpo',
'canale', 'stato', 'inviato_al', 'totale_destinatari', 'invii_ok', 'invii_falliti',
'log_falliti', 'mittente_nome', 'mittente_email',
'log_falliti', 'mittente_nome', 'mittente_email', 'firma_id',
];
protected $casts = [
+5 -4
View File
@@ -197,12 +197,13 @@ class User extends Authenticatable implements CanResetPasswordContract
$resetUrl = url('/password/reset/' . $token);
$fromName = $settings->email_name ?? config('app.name');
$fromName = preg_replace('/[^\p{L}\p{N}\s]/u', '', $fromName);
$fromName = trim($fromName) ?: config('app.name');
$fromAddress = new Address(
$settings->getEffectiveFromAddress(),
$settings->getEffectiveFromName()
);
$email = (new Email())
->from(new Address($settings->email_address, $fromName))
->from($fromAddress)
->to($this->email)
->subject('Reset della password - ' . config('app.name'))
->html(view('auth.emails.reset-password', [
+29 -10
View File
@@ -60,7 +60,7 @@ class BackupService
return $backups;
}
public function run(): array
public function run(array $options = []): array
{
$result = ['success' => true, 'message' => '', 'filename' => ''];
@@ -74,12 +74,28 @@ class BackupService
$filename = $appName . '_' . $timestamp . '.zip';
$filepath = $this->backupDir . '/' . $filename;
$includeFiles = $options['include_files'] ?? (bool) AppSetting::getSetting('backup_include_files', true);
$includeEnv = $options['include_env'] ?? (bool) AppSetting::getSetting('backup_include_env', true);
$steps = [];
$includedComponents = [];
$steps[] = $this->backupDatabase();
$steps[] = $this->backupEnv();
$steps[] = $this->backupFiles();
$this->writeManifest($timestamp);
$includedComponents[] = 'database';
$stepEnv = $this->backupEnv($includeEnv);
$steps[] = $stepEnv;
if ($includeEnv) {
$includedComponents[] = 'env';
}
$stepFiles = $this->backupFiles($includeFiles);
$steps[] = $stepFiles;
if ($includeFiles) {
$includedComponents[] = 'files';
}
$this->writeManifest($timestamp, $includedComponents);
$zipResult = $this->createZip($filepath);
if (!$zipResult) {
@@ -90,7 +106,7 @@ class BackupService
$size = file_exists($filepath) ? filesize($filepath) : 0;
Log::info("Backup completato: {$filename} (" . $this->formatBytes($size) . ")");
Log::info("Backup completato: {$filename} (" . $this->formatBytes($size) . ')');
$this->cleanupOldBackups();
@@ -100,6 +116,7 @@ class BackupService
'filename' => $filename,
'size' => $size,
'size_formatted' => $this->formatBytes($size),
'steps' => $steps,
];
} catch (\Exception $e) {
$this->cleanTemp();
@@ -109,6 +126,7 @@ class BackupService
'success' => false,
'message' => 'Backup fallito: ' . $e->getMessage(),
'filename' => '',
'steps' => [],
];
}
}
@@ -181,9 +199,9 @@ class BackupService
return 'database.sql (' . $this->formatBytes(filesize($sqlFile)) . ')';
}
private function backupEnv(): string
private function backupEnv(bool $includeEnv = true): string
{
if (!AppSetting::getSetting('backup_include_env', true)) {
if (!$includeEnv) {
return '.env (escluso)';
}
@@ -200,9 +218,9 @@ class BackupService
return '.env copiato';
}
private function backupFiles(): string
private function backupFiles(bool $includeFiles = true): string
{
if (!AppSetting::getSetting('backup_include_files', true)) {
if (!$includeFiles) {
return 'files (esclusi)';
}
@@ -220,7 +238,7 @@ class BackupService
return 'files copiati';
}
private function writeManifest(string $timestamp): void
private function writeManifest(string $timestamp, array $includedComponents = ['database']): void
{
$manifest = [
'created_at' => $timestamp,
@@ -230,6 +248,7 @@ class BackupService
'db_name' => config('database.connections.mysql.database'),
'php_version' => PHP_VERSION,
'laravel_version' => app()->version(),
'included_components' => $includedComponents,
];
File::put($this->tempDir . '/manifest.json', json_encode($manifest, JSON_PRETTY_PRINT));