appName = APP_NAME; $this->appUrl = APP_URL; $this->defaultFrom = 'noreply@' . parse_url($this->appUrl, PHP_URL_HOST); } // ═══════════════════════════════════════════════════════════════════════════ // INVIO EMAIL // ═══════════════════════════════════════════════════════════════════════════ /** * Invia una email HTML con template professionale * * @param string $to Indirizzo destinatario * @param string $subject Oggetto email * @param string $htmlBody Contenuto HTML (inserito nel template) * @param string|null $from Indirizzo mittente (opzionale) * @return bool true se mail() ha accettato il messaggio */ public function send(string $to, string $subject, string $htmlBody, ?string $from = null): bool { $from = $from ?? $this->defaultFrom; $headers = implode("\r\n", [ 'From: ' . $this->appName . ' <' . $from . '>', 'Reply-To: ' . $from, 'MIME-Version: 1.0', 'Content-Type: text/html; charset=UTF-8', 'X-Mailer: NIS2 Agile', ]); $fullHtml = $this->wrapInTemplate($subject, $htmlBody); $result = @mail($to, '=?UTF-8?B?' . base64_encode($subject) . '?=', $fullHtml, $headers); $this->logEmail($to, $subject, $result); return $result; } // ═══════════════════════════════════════════════════════════════════════════ // NOTIFICHE INCIDENTI (Art. 23 NIS2) // ═══════════════════════════════════════════════════════════════════════════ /** * Notifica early warning 24h (Art. 23 par. 4 lett. a) * * @param array $incident Dati incidente (title, incident_code, severity, detected_at, classification) * @param array $organization Dati organizzazione (name, sector) * @param array $recipients Lista di indirizzi email destinatari */ public function sendIncidentEarlyWarning(array $incident, array $organization, array $recipients): void { $severityLabel = $this->translateSeverity($incident['severity'] ?? 'medium'); $detectedAt = $this->formatDateTime($incident['detected_at'] ?? ''); $deadline = $this->formatDateTime($incident['early_warning_due'] ?? ''); $html = << EARLY WARNING - Preallarme 24 ore
Ai sensi dell'Art. 23, par. 4, lett. a) della Direttiva NIS2

Si comunica che l'organizzazione {$this->esc($organization['name'])} ha rilevato un incidente significativo che richiede la notifica obbligatoria al CSIRT nazionale (ACN).

Codice Incidente {$this->esc($incident['incident_code'] ?? '-')}
Titolo {$this->esc($incident['title'] ?? '-')}
Classificazione {$this->esc($incident['classification'] ?? '-')}
Gravità {$this->esc($severityLabel)}
Rilevato il {$this->esc($detectedAt)}
Scadenza Early Warning {$this->esc($deadline)}
Azione richiesta: Inviare il preallarme al CSIRT nazionale (ACN) entro 24 ore dalla rilevazione dell'incidente, indicando se l'incidente sia sospettato di essere causato da atti illegittimi o malevoli e se possa avere un impatto transfrontaliero.

Gestisci Incidente

HTML; $incidentCode = $incident['incident_code'] ?? ''; $subject = "[URGENTE] Early Warning Incidente {$incidentCode} - {$organization['name']}"; foreach ($recipients as $email) { $this->send($email, $subject, $html); } } /** * Notifica incidente 72h (Art. 23 par. 4 lett. b) * * @param array $incident Dati incidente * @param array $organization Dati organizzazione * @param array $recipients Lista indirizzi email */ public function sendIncidentNotification(array $incident, array $organization, array $recipients): void { $severityLabel = $this->translateSeverity($incident['severity'] ?? 'medium'); $detectedAt = $this->formatDateTime($incident['detected_at'] ?? ''); $deadline = $this->formatDateTime($incident['notification_due'] ?? ''); $html = << NOTIFICA INCIDENTE - Termine 72 ore
Ai sensi dell'Art. 23, par. 4, lett. b) della Direttiva NIS2

Si comunica la necessità di inviare la notifica formale dell'incidente significativo al CSIRT nazionale (ACN) per l'organizzazione {$this->esc($organization['name'])}.

Codice Incidente {$this->esc($incident['incident_code'] ?? '-')}
Titolo {$this->esc($incident['title'] ?? '-')}
Gravità {$this->esc($severityLabel)}
Rilevato il {$this->esc($detectedAt)}
Servizi Interessati {$this->esc($incident['affected_services'] ?? 'Non specificati')}
Scadenza Notifica {$this->esc($deadline)}
Azione richiesta: Aggiornare la notifica al CSIRT nazionale (ACN) entro 72 ore dalla rilevazione, fornendo una valutazione iniziale dell'incidente, compresa la sua gravità e il suo impatto, nonché gli indicatori di compromissione, ove disponibili.

Gestisci Incidente

HTML; $incidentCode = $incident['incident_code'] ?? ''; $subject = "[NIS2] Notifica Incidente 72h {$incidentCode} - {$organization['name']}"; foreach ($recipients as $email) { $this->send($email, $subject, $html); } } /** * Notifica report finale 30 giorni (Art. 23 par. 4 lett. d) * * @param array $incident Dati incidente * @param array $organization Dati organizzazione * @param array $recipients Lista indirizzi email */ public function sendIncidentFinalReport(array $incident, array $organization, array $recipients): void { $detectedAt = $this->formatDateTime($incident['detected_at'] ?? ''); $deadline = $this->formatDateTime($incident['final_report_due'] ?? ''); $html = << REPORT FINALE - Termine 30 giorni
Ai sensi dell'Art. 23, par. 4, lett. d) della Direttiva NIS2

È necessario predisporre e trasmettere il report finale dell'incidente al CSIRT nazionale (ACN) per l'organizzazione {$this->esc($organization['name'])}.

Codice Incidente {$this->esc($incident['incident_code'] ?? '-')}
Titolo {$this->esc($incident['title'] ?? '-')}
Rilevato il {$this->esc($detectedAt)}
Causa Radice {$this->esc($incident['root_cause'] ?? 'Da determinare')}
Azioni di Rimedio {$this->esc($incident['remediation_actions'] ?? 'Da documentare')}
Scadenza Report Finale {$this->esc($deadline)}
Azione richiesta: Predisporre il report finale entro 30 giorni dalla notifica dell'incidente, contenente: descrizione dettagliata dell'incidente, tipo di minaccia o causa radice, misure di attenuazione applicate e in corso, eventuale impatto transfrontaliero.

Prepara Report Finale

HTML; $incidentCode = $incident['incident_code'] ?? ''; $subject = "[NIS2] Report Finale Incidente {$incidentCode} - {$organization['name']}"; foreach ($recipients as $email) { $this->send($email, $subject, $html); } } // ═══════════════════════════════════════════════════════════════════════════ // NOTIFICHE FORMAZIONE (Art. 20 NIS2) // ═══════════════════════════════════════════════════════════════════════════ /** * Notifica assegnazione corso di formazione * * @param array $assignment Dati assegnazione (due_date) * @param array $user Dati utente (full_name, email) * @param array $course Dati corso (title, description, duration_minutes, is_mandatory) */ public function sendTrainingAssignment(array $assignment, array $user, array $course): void { $dueDate = $this->formatDate($assignment['due_date'] ?? ''); $duration = (int) ($course['duration_minutes'] ?? 0); $mandatoryText = !empty($course['is_mandatory']) ? 'Obbligatorio' : 'Facoltativo'; $html = << Nuovo corso di formazione assegnato
Formazione cybersecurity ai sensi dell'Art. 20 della Direttiva NIS2

Gentile {$this->esc($user['full_name'])},

Le è stato assegnato un nuovo corso di formazione sulla sicurezza informatica nell'ambito del programma di conformità NIS2 della Sua organizzazione.

Corso {$this->esc($course['title'] ?? '-')}
Descrizione {$this->esc($course['description'] ?? '-')}
Durata Stimata {$duration} minuti
Tipo {$this->esc($mandatoryText)}
Scadenza {$this->esc($dueDate)}

Inizia il Corso

HTML; $subject = "Formazione assegnata: {$course['title']}"; $this->send($user['email'], $subject, $html); } /** * Promemoria scadenza formazione * * @param array $assignment Dati assegnazione (due_date) * @param array $user Dati utente (full_name, email) * @param array $course Dati corso (title, is_mandatory) */ public function sendTrainingReminder(array $assignment, array $user, array $course): void { $dueDate = $this->formatDate($assignment['due_date'] ?? ''); $daysLeft = $this->daysUntil($assignment['due_date'] ?? ''); $urgencyColor = match (true) { $daysLeft <= 1 => '#dc2626', $daysLeft <= 3 => '#ea580c', $daysLeft <= 7 => '#f59e0b', default => '#1e40af', }; $daysText = match (true) { $daysLeft < 0 => 'La scadenza è stata superata', $daysLeft === 0 => 'La scadenza è oggi', $daysLeft === 1 => 'Manca 1 giorno alla scadenza', default => "Mancano {$daysLeft} giorni alla scadenza", }; $html = << Promemoria Formazione
{$daysText}

Gentile {$this->esc($user['full_name'])},

Le ricordiamo che il corso di formazione {$this->esc($course['title'])} deve essere completato entro il {$this->esc($dueDate)}.

Il completamento della formazione sulla sicurezza informatica è un requisito previsto dall'Art. 20 della Direttiva NIS2 e dal programma di compliance della Sua organizzazione.

Completa il Corso

HTML; $subject = "Promemoria: {$course['title']} - scadenza {$dueDate}"; $this->send($user['email'], $subject, $html); } // ═══════════════════════════════════════════════════════════════════════════ // AVVISI SCADENZE // ═══════════════════════════════════════════════════════════════════════════ /** * Avviso generico di scadenza * * @param string $type Tipo scadenza (assessment, policy_review, training, incident_report, audit, ecc.) * @param string $title Titolo descrittivo della scadenza * @param string $dueDate Data di scadenza (Y-m-d o Y-m-d H:i:s) * @param array $recipients Lista indirizzi email */ public function sendDeadlineAlert(string $type, string $title, string $dueDate, array $recipients): void { $formattedDate = $this->formatDate($dueDate); $daysLeft = $this->daysUntil($dueDate); $typeLabel = $this->translateDeadlineType($type); $urgencyColor = match (true) { $daysLeft <= 1 => '#dc2626', $daysLeft <= 3 => '#ea580c', $daysLeft <= 7 => '#f59e0b', default => '#1e40af', }; $daysText = match (true) { $daysLeft < 0 => 'Scadenza superata', $daysLeft === 0 => 'Scade oggi', $daysLeft === 1 => 'Scade domani', default => "Scade tra {$daysLeft} giorni", }; $html = << Avviso Scadenza - {$this->esc($typeLabel)}
{$daysText}

Si segnala la seguente scadenza relativa al programma di conformità NIS2:

Tipo {$this->esc($typeLabel)}
Descrizione {$this->esc($title)}
Scadenza {$this->esc($formattedDate)}

Vai alla Dashboard

HTML; $subject = "[Scadenza] {$typeLabel}: {$title} - {$formattedDate}"; foreach ($recipients as $email) { $this->send($email, $subject, $html); } } // ═══════════════════════════════════════════════════════════════════════════ // BENVENUTO E INVITI // ═══════════════════════════════════════════════════════════════════════════ /** * Email di benvenuto dopo la registrazione * * @param array $user Dati utente (full_name, email) */ public function sendWelcome(array $user): void { $html = <<
🔒

Benvenuto in {$this->esc($this->appName)}!

Gentile {$this->esc($user['full_name'])},

La Sua registrazione su {$this->esc($this->appName)} è stata completata con successo.

{$this->esc($this->appName)} è la piattaforma per la gestione della conformità alla Direttiva NIS2 (EU 2022/2555) e al D.Lgs. 138/2024. Con questa piattaforma potrà:

Accedi alla Piattaforma

Se non ha effettuato questa registrazione, può ignorare questa email.

HTML; $subject = "Benvenuto in {$this->appName}"; $this->send($user['email'], $subject, $html); } /** * Invito a unirsi a un'organizzazione * * @param array $user Dati utente invitato (full_name, email) * @param array $organization Dati organizzazione (name) * @param string $role Ruolo assegnato */ public function sendMemberInvite(array $user, array $organization, string $role): void { $roleLabel = $this->translateRole($role); $html = << Invito a collaborare
{$this->esc($organization['name'])}

Gentile {$this->esc($user['full_name'])},

È stata invitata/o a unirsi all'organizzazione {$this->esc($organization['name'])} sulla piattaforma {$this->esc($this->appName)} per la gestione della conformità NIS2.

Organizzazione {$this->esc($organization['name'])}
Ruolo Assegnato {$this->esc($roleLabel)}

Accetta Invito

Se non riconosce questa richiesta, può ignorare questa email.

HTML; $subject = "Invito: unisciti a {$organization['name']} su {$this->appName}"; $this->send($user['email'], $subject, $html); } // ═══════════════════════════════════════════════════════════════════════════ // FEEDBACK & SEGNALAZIONI // ═══════════════════════════════════════════════════════════════════════════ /** * Broadcast risoluzione segnalazione a un membro dell'org * * @param string $to Email destinatario * @param string $reportTitle Titolo breve della segnalazione * @param string $resolution Testo risoluzione (nota admin o risposta AI) */ public function sendFeedbackResolved(string $to, string $reportTitle, string $resolution): bool { $html = << ✓ Segnalazione Risolta
Il problema segnalato è stato risolto

Una segnalazione nella tua organizzazione è stata risolta:

Segnalazione {$this->esc($reportTitle)}
Risoluzione {$this->esc($resolution)}

Vai alla Dashboard

HTML; $subject = "✓ Risolto: {$reportTitle}"; return $this->send($to, $subject, $html); } // ═══════════════════════════════════════════════════════════════════════════ // TEMPLATE HTML // ═══════════════════════════════════════════════════════════════════════════ /** * Avvolge il contenuto HTML nel template email professionale */ private function wrapInTemplate(string $subject, string $bodyHtml): string { $year = date('Y'); return << {$this->esc($subject)}

{$this->esc($this->appName)}

Piattaforma di Conformità NIS2

{$bodyHtml}

Questa è una notifica automatica inviata da {$this->esc($this->appName)}.
Piattaforma di gestione della conformità alla Direttiva NIS2 (EU 2022/2555).

{$this->esc($this->appUrl)}


Questa email e i suoi allegati sono riservati e destinati esclusivamente ai destinatari indicati. Se ha ricevuto questa comunicazione per errore, La preghiamo di contattare il mittente e cancellare il messaggio. La informiamo che il trattamento dei dati personali avviene in conformità al Regolamento (UE) 2016/679 (GDPR).

© {$year} {$this->esc($this->appName)} - Tutti i diritti riservati

HTML; } // ═══════════════════════════════════════════════════════════════════════════ // LOGGING // ═══════════════════════════════════════════════════════════════════════════ /** * Registra l'invio email nel database per audit */ private function logEmail(string $to, string $subject, bool $success): void { try { Database::insert('email_log', [ 'recipient' => $to, 'subject' => mb_substr($subject, 0, 255), 'status' => $success ? 'sent' : 'failed', ]); } catch (\Throwable $e) { error_log('[EmailService] Errore log email: ' . $e->getMessage()); } } // ═══════════════════════════════════════════════════════════════════════════ // UTILITA' // ═══════════════════════════════════════════════════════════════════════════ /** * Escape HTML per prevenire XSS nei contenuti dinamici */ private function esc(string $value): string { return htmlspecialchars($value, ENT_QUOTES | ENT_HTML5, 'UTF-8'); } /** * Formatta data e ora in formato italiano */ private function formatDateTime(string $datetime): string { if (empty($datetime)) { return '-'; } $ts = strtotime($datetime); if ($ts === false) { return $datetime; } return date('d/m/Y H:i', $ts); } /** * Formatta solo la data in formato italiano */ private function formatDate(string $date): string { if (empty($date)) { return '-'; } $ts = strtotime($date); if ($ts === false) { return $date; } return date('d/m/Y', $ts); } /** * Calcola i giorni rimanenti fino a una data */ private function daysUntil(string $date): int { if (empty($date)) { return 0; } $target = strtotime(date('Y-m-d', strtotime($date))); $today = strtotime(date('Y-m-d')); if ($target === false) { return 0; } return (int) (($target - $today) / 86400); } /** * Traduce il livello di gravita' in italiano */ private function translateSeverity(string $severity): string { return match ($severity) { 'low' => 'Bassa', 'medium' => 'Media', 'high' => 'Alta', 'critical' => 'Critica', default => ucfirst($severity), }; } /** * Traduce il tipo di scadenza in italiano */ private function translateDeadlineType(string $type): string { return match ($type) { 'assessment' => 'Gap Analysis', 'policy_review' => 'Revisione Policy', 'training' => 'Formazione', 'incident_report' => 'Report Incidente', 'audit' => 'Audit', 'risk_review' => 'Revisione Rischi', 'supply_chain' => 'Valutazione Supply Chain', default => ucfirst(str_replace('_', ' ', $type)), }; } /** * Traduce il ruolo organizzativo in italiano */ private function translateRole(string $role): string { return match ($role) { 'org_admin' => 'Amministratore Organizzazione', 'compliance_manager' => 'Compliance Manager', 'risk_manager' => 'Risk Manager', 'it_manager' => 'IT Manager', 'employee' => 'Collaboratore', 'auditor' => 'Auditor', 'viewer' => 'Osservatore', default => ucfirst(str_replace('_', ' ', $role)), }; } }