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", "

".Html($texte)."

Vous serez prévenu(e) dès le retour de la connexion.

", "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 = "

".Html($texte_email)."

"; if ($texte_email !== $base) $html .= "

".Html($base)."

"; if ($retard > $seuil_retard) $html .= "

Reçue avec ".Html(Duree_alerte($retard))." de retard.

"; $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", "

".Html($texte)."

", $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", "

".Html($texte)."

", "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 }