middleware('auth')->except(['trackEmailOpen', 'trackDownload', 'unsubscribe']); $this->middleware('permission:stampe_rate_history_view')->only(['index', 'dashboard', 'emailTracking']); $this->middleware('permission:stampe_rate_reports_view')->only(['reports', 'monthlyReport', 'yearlyReport']); $this->middleware('permission:stampe_rate_export')->only(['export', 'exportCSV', 'exportExcel']); $this->middleware('permission:stampe_rate_admin')->only(['cleanup', 'archive']); $this->hookManager = app(HookManager::class); } /** * Dashboard cronologia principale */ public function index(Request $request) { try { $this->hookManager->execute('stampe_rate.history.before_index', [$request]); // Filtri cronologia $query = DB::table('stampe_rate_history as h') ->leftJoin('stampe_rate as s', 'h.stampa_id', '=', 's.id') ->leftJoin('users as u', 'h.created_by', '=', 'u.id') ->select([ 'h.*', 's.nome as stampa_nome', 's.tipo_documento', 'u.name as utente_nome' ]) ->where('h.deleted_at', null) ->orderBy('h.created_at', 'desc'); // Filtri if ($request->filled('date_from')) { $query->where('h.created_at', '>=', $request->get('date_from')); } if ($request->filled('date_to')) { $query->where('h.created_at', '<=', $request->get('date_to') . ' 23:59:59'); } if ($request->filled('action')) { $query->where('h.action', $request->get('action')); } if ($request->filled('user_id')) { $query->where('h.created_by', $request->get('user_id')); } if ($request->filled('search')) { $search = $request->get('search'); $query->where(function ($q) use ($search) { $q->where('s.nome', 'like', "%{$search}%") ->orWhere('h.dettagli', 'like', "%{$search}%") ->orWhere('u.name', 'like', "%{$search}%"); }); } $cronologia = $query->paginate(50); // Statistiche rapide $stats = $this->getStatsRapide(); // Lista utenti per filtro $utenti = DB::table('users') ->whereIn('id', function ($query) { $query->select('created_by') ->from('stampe_rate_history') ->whereNotNull('created_by') ->distinct(); }) ->select('id', 'name') ->orderBy('name') ->get(); $this->hookManager->execute('stampe_rate.history.after_index', [$cronologia, $stats]); return view('modules.stampe-rate.history.index', compact('cronologia', 'stats', 'utenti')); } catch (\Exception $e) { Log::error("Errore cronologia stampe: " . $e->getMessage()); return back()->with('error', 'Errore nel caricamento della cronologia: ' . $e->getMessage()); } } /** * Dashboard statistiche avanzate */ public function dashboard() { try { $this->hookManager->execute('stampe_rate.history.before_dashboard'); // Statistiche generali $statsGenerali = [ 'stampe_totali' => DB::table('stampe_rate')->where('deleted_at', null)->count(), 'stampe_oggi' => DB::table('stampe_rate')->whereDate('created_at', today())->count(), 'stampe_settimana' => DB::table('stampe_rate')->whereBetween('created_at', [now()->startOfWeek(), now()->endOfWeek()])->count(), 'stampe_mese' => DB::table('stampe_rate')->whereMonth('created_at', now()->month)->whereYear('created_at', now()->year)->count() ]; // Email statistics $emailStats = [ 'inviate_totali' => DB::table('stampe_rate_email_log')->count(), 'inviate_oggi' => DB::table('stampe_rate_email_log')->whereDate('sent_at', today())->count(), 'aperte_totali' => DB::table('stampe_rate_email_log')->whereNotNull('opened_at')->count(), 'click_totali' => DB::table('stampe_rate_email_log')->whereNotNull('clicked_at')->count() ]; // Grafici dati ultimi 30 giorni $graficoStampe = $this->getGraficoStampeUltimi30Giorni(); $graficoEmail = $this->getGraficoEmailUltimi30Giorni(); // Top template utilizzati $topTemplate = DB::table('stampe_rate as s') ->leftJoin('stampe_templates as t', 's.template_id', '=', 't.id') ->select('t.nome', DB::raw('COUNT(*) as utilizzi')) ->where('s.deleted_at', null) ->groupBy('t.id', 't.nome') ->orderBy('utilizzi', 'desc') ->limit(10) ->get(); // Errori recenti $erroriRecenti = DB::table('stampe_rate_history') ->where('action', 'error') ->where('created_at', '>=', now()->subDays(7)) ->orderBy('created_at', 'desc') ->limit(10) ->get(); return view('modules.stampe-rate.history.dashboard', compact( 'statsGenerali', 'emailStats', 'graficoStampe', 'graficoEmail', 'topTemplate', 'erroriRecenti' )); } catch (\Exception $e) { Log::error("Errore dashboard history: " . $e->getMessage()); return back()->with('error', 'Errore nel caricamento della dashboard: ' . $e->getMessage()); } } /** * Tracking email inviate */ public function emailTracking(Request $request) { try { $query = DB::table('stampe_rate_email_log as e') ->leftJoin('stampe_rate as s', 'e.stampa_id', '=', 's.id') ->leftJoin('users as u', 'e.sent_by', '=', 'u.id') ->select([ 'e.*', 's.nome as stampa_nome', 'u.name as inviato_da' ]) ->orderBy('e.sent_at', 'desc'); // Filtri if ($request->filled('status')) { $status = $request->get('status'); switch ($status) { case 'sent': $query->whereNotNull('e.sent_at'); break; case 'opened': $query->whereNotNull('e.opened_at'); break; case 'clicked': $query->whereNotNull('e.clicked_at'); break; case 'failed': $query->whereNotNull('e.failed_at'); break; } } if ($request->filled('date_from')) { $query->where('e.sent_at', '>=', $request->get('date_from')); } if ($request->filled('date_to')) { $query->where('e.sent_at', '<=', $request->get('date_to') . ' 23:59:59'); } if ($request->filled('search')) { $search = $request->get('search'); $query->where(function ($q) use ($search) { $q->where('e.recipient_email', 'like', "%{$search}%") ->orWhere('e.recipient_name', 'like', "%{$search}%") ->orWhere('s.nome', 'like', "%{$search}%"); }); } $emailLog = $query->paginate(50); // Statistiche email $emailStats = [ 'totali' => DB::table('stampe_rate_email_log')->count(), 'inviate' => DB::table('stampe_rate_email_log')->whereNotNull('sent_at')->count(), 'aperte' => DB::table('stampe_rate_email_log')->whereNotNull('opened_at')->count(), 'cliccate' => DB::table('stampe_rate_email_log')->whereNotNull('clicked_at')->count(), 'fallite' => DB::table('stampe_rate_email_log')->whereNotNull('failed_at')->count(), 'tasso_apertura' => $this->calcolaTassoApertura(), 'tasso_click' => $this->calcolaTassoClick() ]; return view('modules.stampe-rate.history.email-tracking', compact('emailLog', 'emailStats')); } catch (\Exception $e) { Log::error("Errore tracking email: " . $e->getMessage()); return back()->with('error', 'Errore nel caricamento del tracking email: ' . $e->getMessage()); } } /** * Dettaglio tracking singola email */ public function emailTrackingDetail($invioId) { try { $invio = DB::table('stampe_rate_email_log as e') ->leftJoin('stampe_rate as s', 'e.stampa_id', '=', 's.id') ->leftJoin('users as u', 'e.sent_by', '=', 'u.id') ->select([ 'e.*', 's.nome as stampa_nome', 's.tipo_documento', 'u.name as inviato_da' ]) ->where('e.id', $invioId) ->first(); if (!$invio) { return redirect()->route('stampeRate.history.email.tracking') ->with('error', 'Invio email non trovato'); } // Cronologia tracking per questa email $trackingHistory = DB::table('stampe_rate_email_tracking') ->where('email_log_id', $invioId) ->orderBy('tracked_at', 'desc') ->get(); // Allegati inviati $allegati = DB::table('stampe_rate_email_attachments') ->where('email_log_id', $invioId) ->get(); return view('modules.stampe-rate.history.email-detail', compact('invio', 'trackingHistory', 'allegati')); } catch (\Exception $e) { Log::error("Errore dettaglio email: " . $e->getMessage()); return back()->with('error', 'Errore nel caricamento del dettaglio: ' . $e->getMessage()); } } /** * Report e statistiche avanzate */ public function reports(Request $request) { try { $tipoReport = $request->get('tipo', 'mensile'); $dataInizio = $request->get('data_inizio', now()->startOfMonth()->format('Y-m-d')); $dataFine = $request->get('data_fine', now()->endOfMonth()->format('Y-m-d')); $reportData = $this->generaReport($tipoReport, $dataInizio, $dataFine); return view('modules.stampe-rate.history.reports', compact('reportData', 'tipoReport', 'dataInizio', 'dataFine')); } catch (\Exception $e) { Log::error("Errore report: " . $e->getMessage()); return back()->with('error', 'Errore nella generazione del report: ' . $e->getMessage()); } } /** * Report mensile */ public function monthlyReport(Request $request) { $mese = $request->get('mese', now()->month); $anno = $request->get('anno', now()->year); return $this->generaReportMensile($mese, $anno); } /** * Report annuale */ public function yearlyReport(Request $request) { $anno = $request->get('anno', now()->year); return $this->generaReportAnnuale($anno); } /** * Report personalizzato */ public function customReport(Request $request) { try { $parametri = $request->validate([ 'data_inizio' => 'required|date', 'data_fine' => 'required|date|after:data_inizio', 'metriche' => 'required|array', 'formato' => 'required|in:html,pdf,excel,csv' ]); $reportData = $this->generaReportPersonalizzato($parametri); if ($parametri['formato'] === 'html') { return view('modules.stampe-rate.history.custom-report', compact('reportData', 'parametri')); } else { return $this->esportaReport($reportData, $parametri['formato']); } } catch (\Exception $e) { Log::error("Errore report personalizzato: " . $e->getMessage()); return back()->with('error', 'Errore nella generazione del report: ' . $e->getMessage()); } } /** * Export cronologia */ public function export(Request $request) { try { $formato = $request->get('formato', 'csv'); $filtri = $request->only(['date_from', 'date_to', 'action', 'user_id']); $dati = $this->getCronologiaPerExport($filtri); switch ($formato) { case 'csv': return $this->exportCSV($dati); case 'excel': return $this->exportExcel($dati); case 'pdf': return $this->exportPDF($dati); default: throw new \Exception('Formato non supportato'); } } catch (\Exception $e) { Log::error("Errore export cronologia: " . $e->getMessage()); return back()->with('error', 'Errore nell\'export: ' . $e->getMessage()); } } /** * Export CSV */ public function exportCSV($dati = null) { if (!$dati) { $dati = $this->getCronologiaPerExport(); } $filename = 'cronologia_stampe_' . date('Y-m-d_H-i-s') . '.csv'; $headers = [ 'Content-Type' => 'text/csv', 'Content-Disposition' => 'attachment; filename="' . $filename . '"' ]; return response()->stream(function () use ($dati) { $handle = fopen('php://output', 'w'); // Header CSV fputcsv($handle, [ 'ID', 'Data/Ora', 'Azione', 'Stampa', 'Utente', 'Dettagli', 'IP' ]); // Dati foreach ($dati as $record) { fputcsv($handle, [ $record->id, $record->created_at, $record->action, $record->stampa_nome ?? 'N/A', $record->utente_nome ?? 'Sistema', $record->dettagli, $record->ip_address ]); } fclose($handle); }, 200, $headers); } /** * Export Excel */ public function exportExcel($dati = null) { // Implementazione Excel con PhpSpreadsheet se necessario return $this->exportCSV($dati); // Fallback a CSV per ora } /** * Cleanup cronologia vecchia */ public function cleanup(Request $request) { try { $giorni = $request->get('giorni', 90); $dataLimite = now()->subDays($giorni); $this->hookManager->execute('stampe_rate.history.before_cleanup', [$giorni]); $eliminati = DB::table('stampe_rate_history') ->where('created_at', '<', $dataLimite) ->delete(); // Cleanup email log $emailEliminati = DB::table('stampe_rate_email_log') ->where('sent_at', '<', $dataLimite) ->delete(); $this->hookManager->execute('stampe_rate.history.after_cleanup', [$eliminati, $emailEliminati]); // Invalida cache Cache::tags(['stampe_rate_history'])->flush(); return response()->json([ 'success' => true, 'message' => "Eliminati {$eliminati} record di cronologia e {$emailEliminati} log email" ]); } catch (\Exception $e) { Log::error("Errore cleanup cronologia: " . $e->getMessage()); return response()->json(['error' => 'Errore nel cleanup: ' . $e->getMessage()], 500); } } /** * Archivia cronologia vecchia */ public function archive(Request $request) { try { $giorni = $request->get('giorni', 365); $dataLimite = now()->subDays($giorni); // Esporta prima di archiviare $datiDaArchiviare = DB::table('stampe_rate_history') ->where('created_at', '<', $dataLimite) ->get(); if ($datiDaArchiviare->count() > 0) { $archiveFile = 'archive_' . date('Y-m-d_H-i-s') . '.json'; $archivePath = 'archives/stampe_rate/' . $archiveFile; Storage::disk('local')->put($archivePath, json_encode($datiDaArchiviare)); // Elimina i record archiviati $eliminati = DB::table('stampe_rate_history') ->where('created_at', '<', $dataLimite) ->delete(); return response()->json([ 'success' => true, 'message' => "Archiviati {$eliminati} record in {$archiveFile}" ]); } else { return response()->json([ 'info' => 'Nessun record da archiviare' ]); } } catch (\Exception $e) { Log::error("Errore archiviazione: " . $e->getMessage()); return response()->json(['error' => 'Errore nell\'archiviazione: ' . $e->getMessage()], 500); } } /** * Tracking apertura email (route pubblica) */ public function trackEmailOpen($trackingId) { try { // Trova l'email log $emailLog = DB::table('stampe_rate_email_log') ->where('tracking_id', $trackingId) ->first(); if ($emailLog && !$emailLog->opened_at) { // Aggiorna apertura DB::table('stampe_rate_email_log') ->where('id', $emailLog->id) ->update([ 'opened_at' => now(), 'opened_ip' => request()->ip(), 'opened_user_agent' => request()->userAgent() ]); // Log tracking DB::table('stampe_rate_email_tracking')->insert([ 'email_log_id' => $emailLog->id, 'action' => 'opened', 'tracked_at' => now(), 'ip_address' => request()->ip(), 'user_agent' => request()->userAgent() ]); $this->hookManager->execute('stampe_rate.email.opened', [$emailLog->id]); } // Ritorna pixel 1x1 trasparente return response(base64_decode('R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7')) ->header('Content-Type', 'image/gif') ->header('Cache-Control', 'no-cache, no-store, must-revalidate') ->header('Pragma', 'no-cache') ->header('Expires', '0'); } catch (\Exception $e) { Log::error("Errore tracking email: " . $e->getMessage()); return response('', 200); } } /** * Tracking download allegati */ public function trackDownload($trackingId) { try { // Implementa tracking download DB::table('stampe_rate_email_tracking')->insert([ 'tracking_id' => $trackingId, 'action' => 'download', 'tracked_at' => now(), 'ip_address' => request()->ip(), 'user_agent' => request()->userAgent() ]); return response()->json(['tracked' => true]); } catch (\Exception $e) { Log::error("Errore tracking download: " . $e->getMessage()); return response()->json(['error' => false], 200); } } /** * Unsubscribe link */ public function unsubscribe($token) { try { // Trova email dal token $emailLog = DB::table('stampe_rate_email_log') ->where('unsubscribe_token', $token) ->first(); if ($emailLog) { // Aggiorna stato unsubscribe DB::table('stampe_rate_email_log') ->where('id', $emailLog->id) ->update(['unsubscribed_at' => now()]); // Aggiungi a blacklist se necessario DB::table('email_blacklist')->insertOrIgnore([ 'email' => $emailLog->recipient_email, 'reason' => 'unsubscribed', 'created_at' => now() ]); return view('modules.stampe-rate.unsubscribe-success'); } else { return view('modules.stampe-rate.unsubscribe-error'); } } catch (\Exception $e) { Log::error("Errore unsubscribe: " . $e->getMessage()); return view('modules.stampe-rate.unsubscribe-error'); } } /** * Webhook per status email esterni */ public function webhookEmailStatus(Request $request) { try { $data = $request->all(); // Log webhook per debugging Log::info("Webhook email status ricevuto", $data); // Processa webhook in base al provider if (isset($data['provider'])) { switch ($data['provider']) { case 'sendgrid': $this->processaSendGridWebhook($data); break; case 'mailchimp': $this->processaMailChimpWebhook($data); break; default: Log::warning("Provider webhook sconosciuto: " . $data['provider']); } } return response()->json(['status' => 'ok']); } catch (\Exception $e) { Log::error("Errore webhook email: " . $e->getMessage()); return response()->json(['error' => 'Webhook processing failed'], 500); } } // Metodi di supporto privati private function getStatsRapide() { return [ 'azioni_oggi' => DB::table('stampe_rate_history')->whereDate('created_at', today())->count(), 'azioni_settimana' => DB::table('stampe_rate_history')->whereBetween('created_at', [now()->startOfWeek(), now()->endOfWeek()])->count(), 'errori_settimana' => DB::table('stampe_rate_history')->where('action', 'error')->whereBetween('created_at', [now()->startOfWeek(), now()->endOfWeek()])->count(), 'email_inviate_oggi' => DB::table('stampe_rate_email_log')->whereDate('sent_at', today())->count() ]; } private function getGraficoStampeUltimi30Giorni() { return DB::table('stampe_rate') ->selectRaw('DATE(created_at) as data, COUNT(*) as count') ->where('created_at', '>=', now()->subDays(30)) ->groupBy('data') ->orderBy('data') ->get(); } private function getGraficoEmailUltimi30Giorni() { return DB::table('stampe_rate_email_log') ->selectRaw('DATE(sent_at) as data, COUNT(*) as inviate, COUNT(opened_at) as aperte') ->where('sent_at', '>=', now()->subDays(30)) ->groupBy('data') ->orderBy('data') ->get(); } private function calcolaTassoApertura() { $totali = DB::table('stampe_rate_email_log')->whereNotNull('sent_at')->count(); $aperte = DB::table('stampe_rate_email_log')->whereNotNull('opened_at')->count(); return $totali > 0 ? round(($aperte / $totali) * 100, 2) : 0; } private function calcolaTassoClick() { $totali = DB::table('stampe_rate_email_log')->whereNotNull('sent_at')->count(); $cliccate = DB::table('stampe_rate_email_log')->whereNotNull('clicked_at')->count(); return $totali > 0 ? round(($cliccate / $totali) * 100, 2) : 0; } private function generaReport($tipo, $dataInizio, $dataFine) { // Implementa generazione report base return [ 'periodo' => "{$dataInizio} - {$dataFine}", 'stampe_create' => DB::table('stampe_rate')->whereBetween('created_at', [$dataInizio, $dataFine])->count(), 'email_inviate' => DB::table('stampe_rate_email_log')->whereBetween('sent_at', [$dataInizio, $dataFine])->count() ]; } private function getCronologiaPerExport($filtri = []) { $query = DB::table('stampe_rate_history as h') ->leftJoin('stampe_rate as s', 'h.stampa_id', '=', 's.id') ->leftJoin('users as u', 'h.created_by', '=', 'u.id') ->select([ 'h.*', 's.nome as stampa_nome', 'u.name as utente_nome' ]); // Applica filtri foreach ($filtri as $campo => $valore) { if ($valore) { switch ($campo) { case 'date_from': $query->where('h.created_at', '>=', $valore); break; case 'date_to': $query->where('h.created_at', '<=', $valore . ' 23:59:59'); break; case 'action': $query->where('h.action', $valore); break; case 'user_id': $query->where('h.created_by', $valore); break; } } } return $query->orderBy('h.created_at', 'desc')->get(); } private function processaSendGridWebhook($data) { // Implementa processing SendGrid webhook Log::info("Processing SendGrid webhook", $data); } private function processaMailChimpWebhook($data) { // Implementa processing MailChimp webhook Log::info("Processing MailChimp webhook", $data); } /** * API: Ottiene info tracking per AJAX */ public function getTrackingInfo($invioId) { try { $info = DB::table('stampe_rate_email_log') ->where('id', $invioId) ->select(['sent_at', 'opened_at', 'clicked_at', 'failed_at']) ->first(); return response()->json(['tracking' => $info]); } catch (\Exception $e) { Log::error("Errore API tracking: " . $e->getMessage()); return response()->json(['error' => 'Errore nel caricamento tracking'], 500); } } }