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;
$subject = "[URGENTE] Early Warning Incidente {$incident['incident_code'] ?? ''} - {$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;
$subject = "[NIS2] Notifica Incidente 72h {$incident['incident_code'] ?? ''} - {$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;
$subject = "[NIS2] Report Finale Incidente {$incident['incident_code'] ?? ''} - {$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à:
- Effettuare la Gap Analysis rispetto ai requisiti NIS2
- Gestire il Risk Assessment con matrice di rischio
- Gestire gli incidenti di sicurezza con i flussi di notifica Art. 23
- Generare policy di sicurezza personalizzate
- Monitorare la supply chain e i fornitori critici
- Gestire la formazione del personale (Art. 20)
- Preparare audit e documentazione per le autorità
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);
}
// ═══════════════════════════════════════════════════════════════════════════
// 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',
'sent_at' => date('Y-m-d H:i:s'),
]);
} 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)),
};
}
}