Files
ship2net-www/traiter_alertes.php
T

220 lines
13 KiB
PHP

<?php
// ============================================================================
// traiter_alertes.php - Traitement des alertes d'un ou plusieurs bateaux (synthèse §51, étape D).
// Exécuté NATIVEMENT par ship2net-alertes.service, déclenché par ship2net-alertes.path dès qu'une demande apparaît
// dans /var/www/demandes/alertes (déposée par sonnette.php - motif "evenement" ou retour d'un bateau injoignable - ou
// par gardien.php - perte de connexion). Chaque demande (fichier nommé d'après le numéro du bateau) est consommée
// avant son traitement.
//
// Pour un bateau :
// 1. perte de connexion (Injoignable_depuis renseigné) : alerte "perte", une seule fois ;
// 2. si le bateau est joignable : lecture des déclenchements et fins d'alarmes sur le CM5 depuis le curseur
// (Curseur_historique) - alarmes survenues PENDANT une coupure : comptées, pas envoyées ; autres : envoyées sur les
// moyens prévus par l'event ET choisis par chaque destinataire, sauf event suspendu par le fusible anti-rafales ;
// 3. retour de connexion (signal reçu après la perte) : alerte "retour" (durée de la coupure, nombre d'alarmes
// pendant la coupure), puis Injoignable_depuis remis à NULL ;
// 4. si le bateau est joignable : traitement de ses archives (cron_archivage.php --bateau=N).
// Les alertes de connexion et de suspension partent sur tous les moyens choisis par chaque destinataire.
// ============================================================================
chdir("/var/www/html");
require_once __DIR__ . "/config/config.php";
require_once __DIR__ . "/fonctions_alertes.php";
const REPERTOIRE_DEMANDES_A_TRAITER_ALERTES = REPERTOIRE_DEMANDES_ALERTES;
function Html($texte) { return htmlspecialchars((string)$texte, ENT_QUOTES, 'UTF-8'); }
function Traiter_alertes_bateau($db, $id_bateau)
{
$stmt = $db->prepare("SELECT B.Nom, B.Date_dernier_signal, B.Injoignable_depuis, B.Curseur_historique,
(SELECT C.Adresse_netbird FROM TB_Affectations_bateaux A JOIN TB_Cartes C ON C.Adresse_mac = A.Adresse_mac
WHERE A.ID_Bateau = B.Cle_primaire AND A.Date_fin IS NULL)
FROM TB_Bateaux B WHERE B.Cle_primaire=? AND B.Validite=1");
$stmt->bind_param('i', $id_bateau);
$stmt->execute();
$b = $stmt->get_result()->fetch_row();
$stmt->close();
if (!$b) { echo "Bateau {$id_bateau} : inconnu ou dévalidé - ignoré.\n"; return; }
[$nom_bateau, $dernier_signal, $injoignable_depuis, $curseur, $adresse] = $b;
$fuseau = Reglage_texte($db, 'Fuseau_alertes', 'Europe/Paris');
$seuil_retard = Reglage_entier($db, 'Seuil_retard_alerte', 900);
$destinataires = Destinataires_alertes($db, $id_bateau);
$revenu = ($injoignable_depuis !== null && $dernier_signal !== null && $dernier_signal > $injoignable_depuis);
$joignable = ($injoignable_depuis === null || $revenu);
// 0. Reprise des alertes restées "à envoyer" lors d'un passage précédent interrompu :
$reprises = Reprendre_alertes_a_envoyer($db, $id_bateau, $destinataires);
if ($reprises > 0) echo "Bateau {$id_bateau} : {$reprises} alerte(s) interrompue(s) reprise(s).\n";
// 1. Perte de connexion (une seule alerte par perte, grâce à la clé unique) :
if ($injoignable_depuis !== null)
{
$texte = "Aucun signal du bateau ".$nom_bateau." depuis le ".Date_alerte($injoignable_depuis, $fuseau)." : connexion perdue.";
$id_alerte = Creer_alerte($db, $id_bateau, 0, null, 'perte_connexion', $injoignable_depuis, 'a_envoyer',
"[ShipHeart] ".$nom_bateau." : connexion perdue",
"<p>".Html($texte)."</p><p>Vous serez prévenu(e) dès le retour de la connexion.</p>", "ShipHeart - ".$texte, true, true);
if ($id_alerte > 0)
{
$vises = Envoyer_alerte($db, $id_alerte, $destinataires);
echo "Bateau {$id_bateau} : perte de connexion signalée ({$vises} destinataire(s)).\n";
}
}
// 2. Alarmes (bateau joignable seulement) :
$nb_pendant_coupure = null; // null = inconnu (CM5 non lu)
if ($joignable && filter_var((string)$adresse, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false)
{
if ($curseur === null)
{
// Première lecture pour ce bateau : on part de maintenant (pas d'envoi de l'historique antérieur au lot) :
$stmt = $db->prepare("UPDATE TB_Bateaux SET Curseur_historique=UTC_TIMESTAMP() WHERE Cle_primaire=?");
$stmt->bind_param('i', $id_bateau);
$stmt->execute();
$stmt->close();
echo "Bateau {$id_bateau} : curseur d'alarmes initialisé (aucun envoi de l'historique antérieur).\n";
// $nb_pendant_coupure reste inconnu (null) : l'historique n'a volontairement pas été lu.
}
else
{
$evenements = Lire_alarmes_cm5($adresse, $curseur);
if ($evenements === null) echo "Bateau {$id_bateau} : alarmes non lues (CM5 sans réponse, ou route /alertes absente).\n";
else
{
$nb_pendant_coupure = 0;
$nouveau_curseur = $curseur;
foreach ($evenements as $ev)
{
$date = (string)$ev['date'];
if (preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/', $date) !== 1) continue;
$type = ((int)$ev['type'] == 2) ? 'fin' : 'declenchement';
$id_event = (int)$ev['id_event'];
$nom_event = (string)$ev['nom'];
if ($date > $nouveau_curseur) $nouveau_curseur = $date;
// Pendant une coupure : comptée, pas envoyée.
if ($injoignable_depuis !== null && $date >= $injoignable_depuis && $date <= $dernier_signal)
{
if (Creer_alerte($db, $id_bateau, $id_event, $nom_event, $type, $date, 'pendant_coupure') > 0 && $type == 'declenchement') $nb_pendant_coupure++;
continue;
}
// Event suspendu par le fusible : enregistrée, pas envoyée.
if (Event_suspendu($db, $id_bateau, $id_event))
{
Creer_alerte($db, $id_bateau, $id_event, $nom_event, $type, $date, 'suspendue');
continue;
}
// Texte (fuseau des alertes ; mention du retard au-delà du seuil) :
$heure = Date_alerte($date, $fuseau);
if ($type == 'declenchement')
{
$base = "Alarme « ".$nom_event." » déclenchée le ".$heure." sur ".$nom_bateau.".";
$texte_email = (trim((string)($ev['texte_email'] ?? '')) !== '') ? trim($ev['texte_email']) : $base;
$texte_sms = (trim((string)($ev['texte_sms'] ?? '')) !== '') ? $nom_bateau." : ".trim($ev['texte_sms']) : $base;
$objet = "[ShipHeart] ".$nom_bateau." : ".$nom_event;
}
else
{
// Durée : depuis le déclenchement précédent de cet event
$stmt = $db->prepare("SELECT MAX(Date_evenement) FROM TB_Alertes WHERE ID_Bateau=? AND ID_Event=? AND Type='declenchement' AND Date_evenement <= ?");
$stmt->bind_param('iis', $id_bateau, $id_event, $date);
$stmt->execute();
$debut = $stmt->get_result()->fetch_row()[0];
$stmt->close();
$duree = ($debut !== null) ? " (durée ".Duree_alerte(strtotime($date.' UTC') - strtotime($debut.' UTC')).")" : "";
$base = "Fin d'alarme « ".$nom_event." » le ".$heure.$duree." sur ".$nom_bateau.".";
$texte_email = $base;
$texte_sms = $base;
$objet = "[ShipHeart] ".$nom_bateau." : fin d'alarme ".$nom_event;
}
$retard = time() - strtotime($date.' UTC');
if ($retard > $seuil_retard) $texte_sms = "(+".Duree_alerte($retard).") ".$texte_sms;
$html = "<p>".Html($texte_email)."</p>";
if ($texte_email !== $base) $html .= "<p>".Html($base)."</p>";
if ($retard > $seuil_retard) $html .= "<p><i>Reçue avec ".Html(Duree_alerte($retard))." de retard.</i></p>";
$id_alerte = Creer_alerte($db, $id_bateau, $id_event, $nom_event, $type, $date, 'a_envoyer', $objet, $html, $texte_sms,
!empty($ev['alarme_email']), !empty($ev['alarme_sms']));
if ($id_alerte == 0) continue; // Déjà traitée (lecture rejouée)
// Fusible : rafale détectée -> suspension, un dernier message à la place de l'alerte elle-même
$motif = Fusible_franchi($db, $id_bateau, $id_event, $date);
if ($motif !== null)
{
Statut_alerte($db, $id_alerte, 'suspendue');
$stmt = $db->prepare("INSERT IGNORE INTO TB_Suspensions_alertes (ID_Bateau, ID_Event, Nom_event, Date_suspension, Motif) VALUES (?, ?, ?, UTC_TIMESTAMP(), ?)");
$stmt->bind_param('iiss', $id_bateau, $id_event, $nom_event, $motif);
$stmt->execute();
$stmt->close();
$texte = "Alertes suspendues pour « ".$nom_event." » sur ".$nom_bateau." (".$motif.") : configuration à vérifier. Réactivation sur le portail Ship2net.";
$id_suspension = Creer_alerte($db, $id_bateau, $id_event, $nom_event, 'suspension', $date, 'a_envoyer',
"[ShipHeart] ".$nom_bateau." : alertes suspendues", "<p>".Html($texte)."</p>", $texte, true, true);
if ($id_suspension > 0) Envoyer_alerte($db, $id_suspension, $destinataires);
echo "Bateau {$id_bateau} : fusible - alertes de l'event {$id_event} suspendues ({$motif}).\n";
continue;
}
$vises = Envoyer_alerte($db, $id_alerte, $destinataires);
echo "Bateau {$id_bateau} : {$type} de « {$nom_event} » ({$date}) - {$vises} destinataire(s).\n";
}
$stmt = $db->prepare("UPDATE TB_Bateaux SET Curseur_historique=? WHERE Cle_primaire=?");
$stmt->bind_param('si', $nouveau_curseur, $id_bateau);
$stmt->execute();
$stmt->close();
}
}
}
// 3. Retour de connexion (une seule fois : Injoignable_depuis remis à NULL de façon conditionnelle) :
if ($revenu)
{
$stmt = $db->prepare("UPDATE TB_Bateaux SET Injoignable_depuis=NULL WHERE Cle_primaire=? AND Injoignable_depuis=?");
$stmt->bind_param('is', $id_bateau, $injoignable_depuis);
$stmt->execute();
$premier = ($stmt->affected_rows == 1);
$stmt->close();
if ($premier)
{
$coupure = Duree_alerte(strtotime($dernier_signal.' UTC') - strtotime($injoignable_depuis.' UTC'));
if ($nb_pendant_coupure === null) $bilan = "Les alarmes survenues pendant la coupure n'ont pas pu être lues : consultez le portail Ship2net.";
else if ($nb_pendant_coupure == 0) $bilan = "Aucune alarme pendant la coupure.";
else $bilan = $nb_pendant_coupure." alarme(s) pendant la coupure : consultez le portail Ship2net pour connaître l'état du bateau.";
$texte = "Bateau ".$nom_bateau." de nouveau joignable le ".Date_alerte($dernier_signal, $fuseau)." (coupure de ".$coupure."). ".$bilan;
$id_alerte = Creer_alerte($db, $id_bateau, 0, null, 'retour_connexion', $dernier_signal, 'a_envoyer',
"[ShipHeart] ".$nom_bateau." : connexion rétablie", "<p>".Html($texte)."</p>", "ShipHeart - ".$texte, true, true);
if ($id_alerte > 0)
{
$vises = Envoyer_alerte($db, $id_alerte, $destinataires);
echo "Bateau {$id_bateau} : retour de connexion signalé ({$vises} destinataire(s)).\n";
}
}
}
// 4. Archives (bateau joignable) :
return $joignable;
}
if (!is_dir(REPERTOIRE_DEMANDES_A_TRAITER_ALERTES)) { echo "Répertoire ".REPERTOIRE_DEMANDES_A_TRAITER_ALERTES." absent.\n"; exit(1); }
require_once __DIR__ . "/config/fonctions_mail.php";
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
foreach (scandir(REPERTOIRE_DEMANDES_A_TRAITER_ALERTES) as $nom)
{
if ($nom === '.' || $nom === '..') continue;
@unlink(REPERTOIRE_DEMANDES_A_TRAITER_ALERTES."/".$nom);
if (preg_match('/^[1-9][0-9]{0,9}$/', $nom) !== 1) { echo "Demande '{$nom}' invalide - supprimée.\n"; continue; }
try
{
$db = new mysqli("127.0.0.1", BDD_USER, BDD_PASSWORD, BDD_DATABASE, 3307);
$db->set_charset('utf8mb4');
$db->query("SET time_zone = '+00:00'");
$joignable = Traiter_alertes_bateau($db, (int)$nom);
$db->close();
if ($joignable) passthru("/usr/bin/php ".escapeshellarg(__DIR__."/cron_archivage.php")." --bateau=".(int)$nom);
}
catch (Throwable $e) { echo "Bateau {$nom} : erreur - ".$e->getMessage()."\n"; } // N'empêche pas les demandes suivantes
}