authorizeRead('email'); if ($folder === 'compose' || $folder === 'nuovo') { return $this->compose($request); } $folderModel = EmailFolder::where('type', $folder)->first(); if (!$folderModel) { $folderModel = EmailFolder::where('type', 'inbox')->first(); if (!$folderModel) { $this->ensureFoldersExist(); $folderModel = EmailFolder::where('type', 'inbox')->first(); } } $query = EmailMessage::where('email_folder_id', $folderModel->id); if ($request->search) { $query->where(function ($q) use ($request) { $q->where('subject', 'like', '%' . $request->search . '%') ->orWhere('from_email', 'like', '%' . $request->search . '%') ->orWhere('to_email', 'like', '%' . $request->search . '%') ->orWhere('body_text', 'like', '%' . $request->search . '%'); }); } $sortField = $request->sort ?? 'received_at'; $sortDir = $request->direction ?? 'desc'; $allowedSorts = ['subject', 'received_at', 'sent_at', 'from_email', 'to_email']; if (!in_array($sortField, $allowedSorts)) { $sortField = 'received_at'; } if (!in_array($sortDir, ['asc', 'desc'])) { $sortDir = 'desc'; } $query->orderBy($sortField === 'sent_at' ? 'received_at' : $sortField, $sortDir); $messages = $query->paginate(20)->withQueryString(); $folders = EmailFolder::orderBy('sort_order')->get(); $mailingLists = MailingList::where('attiva', true)->get(); $gruppi = Gruppo::orderBy('nome')->get(); $emailSettings = EmailSetting::getActive(); $syncInterval = $emailSettings ? $emailSettings->sync_interval_minutes : 0; return view('email.index', compact('messages', 'folders', 'folder', 'folderModel', 'mailingLists', 'gruppi', 'syncInterval')); } public function show($folder, $id) { $this->authorizeRead('email'); $message = EmailMessage::with('attachments')->findOrFail($id); if (!$message->is_read) { $message->update(['is_read' => true]); } $folders = EmailFolder::orderBy('sort_order')->get(); return view('email.show', compact('message', 'folders', 'folder')); } public function compose(Request $request) { $this->authorizeWrite('email'); $folders = EmailFolder::orderBy('sort_order')->get(); $mailingLists = MailingList::where('attiva', true)->get(); $gruppi = Gruppo::orderBy('nome')->get(); $individui = Individuo::orderBy('cognome')->orderBy('nome')->get(); $prefill = []; if ($request->reply_to) { $original = EmailMessage::find($request->reply_to); if ($original) { $prefill['to'] = $original->from_email; $prefill['subject'] = 'Re: ' . $original->subject; $prefill['body'] = "\n\n--- Messaggio originale ---\n" . ($original->body_text ?? $original->body_html); } } if ($request->forward) { $original = EmailMessage::find($request->forward); if ($original) { $prefill['subject'] = 'Fwd: ' . $original->subject; $prefill['body'] = "\n\n--- Messaggio inoltrato ---\n" . ($original->body_text ?? $original->body_html); } } return view('email.compose', compact('folders', 'mailingLists', 'gruppi', 'individui', 'prefill')); } public function send(Request $request) { $this->authorizeWrite('email'); $rules = [ 'subject' => 'required', 'body' => 'required', ]; if ($request->destinatario_tipo === 'manuale' || !$request->destinatario_tipo) { $rules['to'] = 'required'; } $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', [ 'recipients' => $recipients, '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, ]); 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(); } $imapSuccess = false; $imapError = null; foreach ($recipients as $recipient) { try { $this->sendViaImap($settings, $recipient, $validated['subject'], $validated['body'], $request->cc); $imapSuccess = true; } catch (\Exception $e) { $imapError = $e->getMessage(); \Illuminate\Support\Facades\Log::error('IMAP send error', ['error' => $e->getMessage()]); } } try { $this->storeSentMessage($settings, $recipients, $validated['subject'], $validated['body'], $request->cc); \Illuminate\Support\Facades\Log::info('Stored sent message'); } 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('success', 'Email inviata con successo.'); } public function storeDraft(Request $request) { $this->authorizeWrite('email'); $settings = EmailSetting::getActive(); $folder = EmailFolder::where('type', 'drafts')->first(); $message = EmailMessage::create([ 'email_folder_id' => $folder->id, 'subject' => $request->subject ?? '(senza oggetto)', 'body_text' => $request->body, 'to_email' => $request->to, 'is_draft' => true, 'from_email' => $settings->email_address ?? '', 'from_name' => $settings->email_name ?? '', ]); return response()->json(['success' => true, 'draft_id' => $message->id]); } public function bulkAction(Request $request) { try { $this->authorizeWrite('email'); } catch (\Symfony\Component\HttpKernel\Exception\HttpException $e) { return response()->json([ 'success' => false, 'message' => 'Non hai i permessi per questa azione' ], 403); } $ids = $request->input('ids', []); $action = $request->input('action'); if ($action === 'empty_trash') { $trashFolder = EmailFolder::where('type', 'trash')->first(); if ($trashFolder) { EmailMessage::where('email_folder_id', $trashFolder->id)->delete(); } return response()->json(['success' => true]); } if (empty($ids)) { return response()->json(['success' => false, 'message' => 'Nessuna email selezionata']); } $messages = EmailMessage::whereIn('id', $ids)->get(); switch ($action) { case 'mark_read': $messages->each->update(['is_read' => true]); break; case 'mark_unread': $messages->each->update(['is_read' => false]); break; case 'star': $messages->each->update(['is_starred' => true]); break; case 'unstar': $messages->each->update(['is_starred' => false]); break; case 'move_trash': $trashFolder = EmailFolder::where('type', 'trash')->first(); if ($trashFolder) { $messages->each->update(['email_folder_id' => $trashFolder->id]); } break; case 'delete_permanent': $messages->each->delete(); break; default: return response()->json(['success' => false, 'message' => 'Azione non valida']); } return response()->json(['success' => true]); } public function markRead($id) { try { $this->authorizeWrite('email'); } catch (\Symfony\Component\HttpKernel\Exception\HttpException $e) { return response()->json([ 'success' => false, 'message' => 'Non hai i permessi per questa azione' ], 403); } $message = EmailMessage::findOrFail($id); $message->update(['is_read' => true]); return response()->json(['success' => true]); } public function toggleStar($id) { try { $this->authorizeWrite('email'); } catch (\Symfony\Component\HttpKernel\Exception\HttpException $e) { return response()->json([ 'success' => false, 'message' => 'Non hai i permessi per questa azione' ], 403); } $message = EmailMessage::findOrFail($id); $message->update(['is_starred' => !$message->is_starred]); return response()->json(['success' => true, 'is_starred' => $message->is_starred]); } public function destroy($id) { $this->authorizeDelete('email'); $message = EmailMessage::findOrFail($id); $trashFolder = EmailFolder::where('type', 'trash')->first(); if ($trashFolder) { $message->update(['is_trash' => true, 'email_folder_id' => $trashFolder->id]); } else { $message->delete(); } return back()->with('success', 'Email spostata nel cestino.'); } public function downloadAttachment($id) { $this->authorizeRead('email'); $attachment = EmailAttachment::findOrFail($id); if (!Storage::exists($attachment->file_path)) { abort(404); } return Storage::download($attachment->file_path, $attachment->filename); } public function saveAttachmentToDocumenti(Request $request, $id, $attachmentId) { $this->authorizeWrite('email'); $this->authorizeWrite('documenti'); $attachment = EmailAttachment::findOrFail($attachmentId); $documento = Documento::create([ 'nome_file' => $attachment->filename, 'file_path' => $attachment->file_path, 'tipo' => 'upload', 'tipologia' => 'email_attachment', 'mime_type' => $attachment->mime_type, 'dimensione' => $attachment->size, 'visibilita' => null, 'note' => 'Allegato email: ' . ($attachment->message->subject ?? ''), ]); $attachment->update(['is_saved_to_documenti' => true, 'documenti_id' => $documento->id]); return back()->with('success', 'Allegato salvato nei documenti.'); } public function quickSync() { try { $this->syncEmails(); return response()->json(['success' => true, 'message' => 'Sincronizzazione completata']); } catch (\Exception $e) { return response()->json(['success' => false, 'message' => $e->getMessage()]); } } public function syncEmails() { $settings = EmailSetting::getActive(); if (!$settings) { throw new \Exception('Nessuna configurazione email attiva'); } $this->ensureFoldersExist(); $mailbox = $this->connectImap($settings); $folderMappings = [ 'INBOX' => 'inbox', 'Sent' => 'sent', '[Gmail]/Sent Mail' => 'sent', 'Sent Mail' => 'sent', 'Drafts' => 'drafts', '[Gmail]/Drafts' => 'drafts', 'Trash' => 'trash', '[Gmail]/Trash' => 'trash', ]; foreach (array_keys($folderMappings) as $imapFolderName) { try { $folder = $mailbox->folders()->find($imapFolderName); if (!$folder) continue; $messages = $folder->messages() ->withHeaders() ->withBody() ->withBodyStructure() ->limit(100) ->get(); foreach ($messages as $imapMessage) { $this->processImapMessage($imapMessage, $imapFolderName); } } catch (\Exception $e) { continue; } } return true; } private function resolveRecipients(Request $request) { $recipients = []; if ($request->destinatario_tipo === 'individui' && $request->individui) { $individui = Individuo::whereIn('id', $request->individui)->get(); foreach ($individui as $ind) { $emails = $ind->contatti()->where('tipo', 'email')->pluck('valore')->toArray(); $recipients = array_merge($recipients, $emails); } } elseif ($request->destinatario_tipo === 'gruppo' && $request->gruppo_id) { $gruppo = Gruppo::with('individui.contatti')->find($request->gruppo_id); foreach ($gruppo->individui as $ind) { $emails = $ind->contatti()->where('tipo', 'email')->pluck('valore')->toArray(); $recipients = array_merge($recipients, $emails); } } elseif ($request->destinatario_tipo === 'mailing_list' && $request->mailing_list_id) { $list = MailingList::with('contatti.individuo')->find($request->mailing_list_id); foreach ($list->contatti as $contact) { if ($contact->opt_in && $contact->individuo) { $emails = $contact->individuo->contatti()->where('tipo', 'email')->pluck('valore')->toArray(); $recipients = array_merge($recipients, $emails); } } } elseif ($request->to) { $recipients = is_array($request->to) ? $request->to : [$request->to]; } return array_filter(array_unique($recipients)); } private function appendSignature(string $body, ?string $signature, bool $enabled): string { if (empty($signature) || !$enabled) { return $body; } return $body . "\n\n---\n" . $signature; } private function sendViaImap($settings, $to, $subject, $body, $cc = null) { $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(); $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 ); \Illuminate\Support\Facades\Log::info('SMTP DSN', ['dsn' => $dsn]); 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); $email = (new \Symfony\Component\Mime\Email()) ->from($fromAddress) ->to($to) ->subject($subject) ->text($bodyWithSignature); if ($cc) { $email->cc($cc); } $mailer->send($email); \Illuminate\Support\Facades\Log::info('Email sent via SMTP successfully'); return true; } catch (\Exception $e) { \Illuminate\Support\Facades\Log::error('SMTP send error', ['error' => $e->getMessage()]); throw new \Exception('Errore invio SMTP: ' . $e->getMessage()); } } 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); $message = EmailMessage::create([ 'email_folder_id' => $sentFolder->id, 'subject' => $subject, 'body_text' => $bodyWithSignature, 'from_email' => $settings->email_address, 'from_name' => $settings->email_name, 'to_email' => implode(', ', $recipients), 'cc' => $cc, 'is_sent' => true, 'sent_at' => now(), 'is_read' => true, ]); return $message; } private function connectImap($settings) { if (!$settings->imap_host || !$settings->imap_username) { throw new \Exception('Configurazione IMAP non valida'); } $encryption = match ($settings->imap_encryption) { 'ssl' => 'ssl', 'tls' => 'tls', default => 'none', }; return new \DirectoryTree\ImapEngine\Mailbox([ 'host' => $settings->imap_host, 'port' => $settings->imap_port ?? 993, 'encryption' => $encryption, 'validate_cert' => false, 'username' => $settings->imap_username, 'password' => $settings->getDecryptedPassword(), ]); } private function processImapMessage($imapMessage, $imapFolder) { $folderType = match ($imapFolder) { 'INBOX', 'inbox' => 'inbox', 'Sent', 'sent', '[Gmail]/Sent Mail' => 'sent', 'Drafts', 'drafts', '[Gmail]/Drafts' => 'drafts', 'Trash', 'trash', '[Gmail]/Trash' => 'trash', 'Archive', 'archive', '[Gmail]/All Mail' => 'archive', 'Starred', 'starred', '[Gmail]/Starred' => 'starred', default => 'inbox', }; $folder = EmailFolder::where('type', $folderType)->first(); if (!$folder) return; $messageId = $imapMessage->messageId() ?? uniqid('msg_'); $existing = EmailMessage::where('message_id', $messageId)->first(); if ($existing) return; $from = $imapMessage->from(); $to = $imapMessage->to(); $message = EmailMessage::create([ 'email_folder_id' => $folder->id, 'message_id' => $messageId, 'subject' => $imapMessage->subject() ?? '(senza oggetto)', 'from_name' => $from?->name() ?? '', 'from_email' => $from?->email() ?? '', 'to_email' => count($to) > 0 ? $to[0]->email() : '', 'body_text' => $imapMessage->text() ?? '', 'body_html' => $imapMessage->html() ?? '', 'is_read' => $imapMessage->isSeen() ?? false, 'is_starred' => $imapMessage->isFlagged() ?? false, 'is_sent' => $folderType === 'sent', 'received_at' => $imapMessage->date() ?? now(), 'imap_uid' => $imapMessage->uid() ?? null, ]); $attachments = $imapMessage->attachments(); if ($attachments && count($attachments) > 0) { foreach ($attachments as $attachment) { $filename = $attachment->filename() ?? 'attachment'; $contents = $attachment->contents(); $path = 'email-attachments/' . Str::uuid() . '_' . $filename; Storage::put($path, $contents); EmailAttachment::create([ 'email_message_id' => $message->id, 'filename' => $filename, 'mime_type' => $attachment->contentType() ?? 'application/octet-stream', 'size' => strlen($contents), 'file_path' => $path, ]); } } } private function ensureFoldersExist() { $systemFolders = EmailFolder::getSystemFolders(); foreach ($systemFolders as $folder) { EmailFolder::updateOrCreate( ['type' => $folder['type']], $folder ); } } }