redirection -> GET, après une action sur les accès : un
// rafraîchissement de la page ne rejoue jamais l'action) :
if (isset($_SESSION['Bateaux']['flash_information'])) { $message_information .= $_SESSION['Bateaux']['flash_information']; unset($_SESSION['Bateaux']['flash_information']); }
if (isset($_SESSION['Bateaux']['flash_erreur'])) { $message_erreur .= $_SESSION['Bateaux']['flash_erreur']; unset($_SESSION['Bateaux']['flash_erreur']); }
Ouverture_bdd(); // Connexion à MySql
//************************************************************************************************************************************
// Traitement des variables d'entrées
//************************************************************************************************************************************
// Variables de SESSION :
if (!isset($_SESSION['Bateaux']['page'])) $_SESSION['Bateaux']['page'] = 'Accueil';
if (!isset($_SESSION['Bateaux']['ancre'])) $_SESSION['Bateaux']['ancre'] = "";
if (!isset($_SESSION['Bateaux']['origine'])) $_SESSION['Bateaux']['origine'] = "";
if (!isset($_SESSION['Bateaux']['tri_liste'])) $_SESSION['Bateaux']['tri_liste'] = "Flotte";
if (!isset($_SESSION['Bateaux']['sens_tri_liste'])) $_SESSION['Bateaux']['sens_tri_liste'] = "ASC";
if (!isset($_SESSION['Bateaux']['type_details_bateau'])) $_SESSION['Bateaux']['type_details_bateau'] = "resume"; // 'resume', 'gerer_les_acces' ou 'alertes'
if (!isset($_SESSION['Bateaux']['bateau']['Cle_primaire'])) $_SESSION['Bateaux']['bateau']['Cle_primaire'] = 0;
if (!isset($_SESSION['Bateaux']['bateau']['Nom'])) $_SESSION['Bateaux']['bateau']['Nom'] = "";
if (!isset($_SESSION['Bateaux']['bateau']['ID_Flotte'])) $_SESSION['Bateaux']['bateau']['ID_Flotte'] = 0;
if (!isset($_SESSION['Bateaux']['bateau']['Date_mes'])) $_SESSION['Bateaux']['bateau']['Date_mes'] = date("Y-m-d");
if (!isset($_SESSION['Bateaux']['bateau']['Commentaires'])) $_SESSION['Bateaux']['bateau']['Commentaires'] = "";
//---------------------------------------------------------------------------
// Contrôles PRÉALABLES à toute action (synthèse §48) - auparavant, les actions GET et POST s'exécutaient avant tout
// contrôle : n'importe quel utilisateur connecté pouvait créer, modifier ou dévalider un bateau.
//---------------------------------------------------------------------------
if (!$_SESSION['acces_valide'])
{
Fermeture_bdd();
header('Location:shipmembers.php?action=navigation&destination=Login');
exit();
}
$est_administrateur = ($_SESSION['shipmember']['Niveau_acces'] >= NIVEAU_ACCES_MINI_ADMINISTRATEUR);
// Toute requête POST doit porter le jeton de la session (protection contre les formulaires envoyés depuis un autre site,
// en complément de SameSite=Lax) :
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !Jeton_csrf_valide())
{
$_SESSION['Bateaux']['flash_erreur'] = t("Formulaire expiré ou invalide : merci de recommencer.");
Fermeture_bdd();
header('Location: bateaux.php');
exit();
}
// Gestion de la flotte (liste, création, modification, dévalidation d'un bateau) : réservée aux administrateurs :
$actions_flotte = array('tri_liste', 'creation_bateau', 'modification_bateau', 'demande_suppression_bateau');
if (!$est_administrateur &&
((isset($_GET['action']) && in_array($_GET['action'], $actions_flotte, true))
|| isset($_POST['validation_creation_bateau']) || isset($_POST['validation_modification_bateau'])
|| isset($_POST['confirmation_suppression_bateau'])))
{
$_SESSION['Bateaux']['flash_erreur'] = t("Action réservée aux administrateurs.");
Fermeture_bdd();
header('Location: bateaux.php');
exit();
}
// Variables GET :
if (isset($_GET['ancre'])) $_SESSION['Bateaux']['ancre'] = $_GET['ancre'];
if (isset($_GET['action']))
{
switch ($_GET['action'])
{
//===================================
case "navigation":
{
// Seules les pages consultables par simple navigation ; création, modification et suppression passent par leurs
// actions dédiées (contrôlées ci-dessus) :
if (isset($_GET['destination']) && in_array($_GET['destination'], array('Accueil', 'Details_bateau'), true))
{
$_SESSION['Bateaux']['page'] = $_GET['destination'];
}
if (isset($_GET['ancre']))
{
$_SESSION['Bateaux']['ancre'] = $_GET['ancre'];
}
break;
}
//===================================
case "changement_langue":
{
if (isset($_GET['langue']))
{
$_SESSION['langue'] = $_GET['langue'];
chargement_langue();
}
break;
}
//===================================
case "tri_liste":
{
if (isset($_GET['colonne']))
{
if ($_SESSION['Bateaux']['tri_liste'] == $_GET['colonne'])
{
if ($_SESSION['Bateaux']['sens_tri_liste'] == "ASC") $_SESSION['Bateaux']['sens_tri_liste'] = "DESC";
else $_SESSION['Bateaux']['sens_tri_liste'] = "ASC";
}
else
{
$_SESSION['Bateaux']['tri_liste'] = $_GET['colonne'];
$_SESSION['Bateaux']['sens_tri_liste'] = "ASC";
}
}
break;
}
//===================================
case "details_bateau":
{
if (isset($_GET['id_bateau']))
{
$_SESSION['Bateaux']['bateau']['Cle_primaire'] = (int)$_GET['id_bateau'];
$_SESSION['Bateaux']['type_details_bateau'] = "resume";
$_SESSION['Bateaux']['generation'] = 0; // Nouveau bateau : génération active
$_SESSION['Bateaux']['page'] = "Details_bateau";
if (isset($_GET['origine'])) $_SESSION['Bateaux']['origine'] = $_GET['origine'];
else $_SESSION['Bateaux']['origine'] = "";
}
break;
}
//===================================
case "type_details_bateau":
{
if (isset($_GET['type']) && in_array($_GET['type'], array('resume', 'gerer_les_acces', 'alertes'), true)) $_SESSION['Bateaux']['type_details_bateau'] = $_GET['type'];
$_SESSION['Bateaux']['ancre'] = "";
break;
}
//===================================
// Génération consultée (synthèse §50) : 0 = génération active. La valeur n'est utilisée qu'après vérification
// qu'elle appartient bien au bateau affiché (préparation de la page).
case "choix_generation":
{
$_SESSION['Bateaux']['generation'] = isset($_GET['generation']) ? (int)$_GET['generation'] : 0;
$_SESSION['Bateaux']['type_details_bateau'] = "resume";
break;
}
//===================================
case "modification_bateau":
{
if (isset($_GET['id_bateau']))
{
$_SESSION['Bateaux']['bateau']['Cle_primaire'] = (int)$_GET['id_bateau'];
$_SESSION['Bateaux']['page'] = "Modification_bateau";
$_SESSION['Bateaux']['charger_bateau'] = true; // Valeurs lues en base à l'affichage du formulaire
}
break;
}
//===================================
case "creation_bateau":
{
$_SESSION['Bateaux']['page'] = "Creation_bateau";
// Formulaire vierge - réinitialisé ICI et non plus à chaque affichage : après une erreur de saisie, le formulaire
// réaffiché garde les valeurs saisies :
$_SESSION['Bateaux']['bateau'] = array('Cle_primaire' => 0, 'Nom' => "", 'ID_Flotte' => 0, 'Date_mes' => date("Y-m-d"), 'Commentaires' => "");
break;
}
//===================================
case "demande_suppression_bateau":
{
if (isset($_GET['id_bateau']))
{
$_SESSION['Bateaux']['bateau']['Cle_primaire'] = (int)$_GET['id_bateau'];
$_SESSION['Bateaux']['page'] = "Confirmation_suppression_bateau";
}
break;
}
// NB : la confirmation de suppression n'est plus une action GET (un simple lien suffisait à dévalider un bateau) :
// elle est envoyée en POST, avec le jeton de session (voir plus bas).
}
Fermeture_bdd();
if ($_SESSION['Bateaux']['ancre'] == "") header('Location:bateaux.php'); // Permet de "vider" la barre d'URL des champs GET et donc de doubler le traitement en cas de raffraichissement de page...
else header('Location:bateaux.php#'.$_SESSION['Bateaux']['ancre'].'');
exit();
}
// Variables POST :
//---------------------------------------------------------------------------
// Création/Modification Bateau (administrateurs - contrôlé plus haut) :
// NB : l'affectation d'un module ShipBase à un bateau ne se fait plus ici mais via TB_Affectations_bateaux
// (administration de la flotte, lot 4 - synthèse §44.10). Les anciens champs ID_Carte/Date_carte et les
// répertoires download/upload/archives du modèle CM4 ont été retirés (synthèse §47).
// Valeurs enregistrées TELLES QUELLES (requêtes préparées) : l'ancien addslashes() les stockait avec des barres
// obliques (d\'Artagnan), retirées ensuite à l'affichage par stripcslashes() (synthèse §48).
if (isset($_POST['validation_creation_bateau']) OR isset($_POST['validation_modification_bateau']))
{
// Mémorisation des valeurs saisies pour les réafficher en cas d'erreur :
$_SESSION['Bateaux']['bateau']['Nom'] = trim((string)($_POST['Nom'] ?? ""));
$_SESSION['Bateaux']['bateau']['ID_Flotte'] = (int)($_POST['ID_Flotte'] ?? 0);
$_SESSION['Bateaux']['bateau']['Date_mes'] = trim((string)($_POST['Date_mes'] ?? ""));
$_SESSION['Bateaux']['bateau']['Commentaires'] = (string)($_POST['Commentaires'] ?? "");
// Vérifications :
$longueur_nom = mb_strlen($_SESSION['Bateaux']['bateau']['Nom']);
if ($longueur_nom < 1 || $longueur_nom > 50) $message_erreur .= t("Le nom du bateau doit comporter de 1 à 50 caractères.")." ";
$date = date_create_from_format('!Y-m-d', $_SESSION['Bateaux']['bateau']['Date_mes']);
if ($date === false || $date->format('Y-m-d') !== $_SESSION['Bateaux']['bateau']['Date_mes']) $message_erreur .= t("Date de mise en service invalide (format AAAA-MM-JJ).")." ";
if ($_SESSION['Bateaux']['bateau']['ID_Flotte'] != 0)
{
$reponse = Requete_preparee_bdd("SELECT COUNT(*) AS nb FROM TB_Flottes WHERE Cle_primaire=? AND Validite=1",
array(array('i', $_SESSION['Bateaux']['bateau']['ID_Flotte'])), __FILE__, __LINE__);
$donnees = Recherche_ligne_bdd($reponse);
Liberation_jeu_de_resultat_bdd($reponse);
if ($donnees['nb'] == 0) $message_erreur .= t("Flotte inconnue.")." ";
}
if ($message_erreur == "") // Sinon : le formulaire est réaffiché avec les valeurs saisies et le message d'erreur
{
if (isset($_POST['validation_creation_bateau']))
{
$requete = 'INSERT INTO TB_Bateaux (Nom, ID_Flotte, Date_mes, Commentaires) VALUES (?, ?, ?, ?)';
Requete_preparee_bdd($requete,
array(
array('s', $_SESSION['Bateaux']['bateau']['Nom']),
array('i', $_SESSION['Bateaux']['bateau']['ID_Flotte']),
array('s', $_SESSION['Bateaux']['bateau']['Date_mes']),
array('s', $_SESSION['Bateaux']['bateau']['Commentaires'])),
__FILE__, __LINE__);
$_SESSION['Bateaux']['bateau']['Cle_primaire'] = Recuperation_id_cree_bdd();
$_SESSION['Bateaux']['flash_information'] = str_replace("[[Nom]]", htmlspecialchars($_SESSION['Bateaux']['bateau']['Nom']), t("Le bateau [[Nom]] vient d'être créé."));
}
else
{
$requete = 'UPDATE TB_Bateaux SET Nom=?, ID_Flotte=?, Date_mes=?, Commentaires=? WHERE Cle_primaire=? AND Validite=1';
Requete_preparee_bdd($requete,
array(
array('s', $_SESSION['Bateaux']['bateau']['Nom']),
array('i', $_SESSION['Bateaux']['bateau']['ID_Flotte']),
array('s', $_SESSION['Bateaux']['bateau']['Date_mes']),
array('s', $_SESSION['Bateaux']['bateau']['Commentaires']),
array('i', $_SESSION['Bateaux']['bateau']['Cle_primaire'])),
__FILE__, __LINE__);
$_SESSION['Bateaux']['flash_information'] = str_replace("[[Nom]]", htmlspecialchars($_SESSION['Bateaux']['bateau']['Nom']), t("Le bateau [[Nom]] vient d'être modifié."));
}
$_SESSION['Bateaux']['page'] = "Accueil";
Fermeture_bdd();
header('Location: bateaux.php');
exit();
}
}
//---------------------------------------------------------------------------
// Dévalidation d'un bateau (administrateurs - contrôlé plus haut ; POST avec jeton) :
if (isset($_POST['confirmation_suppression_bateau']))
{
$id_bateau = (int)$_POST['confirmation_suppression_bateau'];
Requete_preparee_bdd('UPDATE TB_Bateaux SET Validite=0 WHERE Cle_primaire=? AND Validite=1', array(array('i', $id_bateau)), __FILE__, __LINE__);
if (Nb_lignes_modifiees() == 1) $_SESSION['Bateaux']['flash_information'] = t("Le bateau a été dévalidé dans la base de données.");
else $_SESSION['Bateaux']['flash_erreur'] = t("Ce bateau n'existe pas ou a déjà été dévalidé.");
$_SESSION['Bateaux']['page'] = "Accueil";
Fermeture_bdd();
header('Location: bateaux.php');
exit();
}
//---------------------------------------------------------------------------
// NB : les actions sur les accès et invitations (création, suspension, réactivation, révocation, modification, PDF)
// sont traitées plus bas, dans la page Details_bateau, APRÈS le calcul des droits de l'utilisateur sur le bateau
// (elles étaient auparavant exécutées ici, avant tout contrôle - synthèse §47).
//************************************************************************************************************************************
// Traitement des données
//************************************************************************************************************************************
// Protection contre les accès directs... :
$flag_sortie = true;
do // Dummy loop :
{
if (!$_SESSION['acces_valide']) break; // Si on n'est pas identifié, on ne peut pas accéder quoi qu'il arrive...
if ($_SESSION['shipmember']['Niveau_acces'] >= NIVEAU_ACCES_MINI_ADMINISTRATEUR)
{
$flag_sortie = false; // Si on est administrateur, on a toujours accès....
break;
}
// Recherche des droits d'accès à un bateau en particulier :
if ($_SESSION['Bateaux']['page'] == "Details_bateau")
{
$requete = "SELECT * FROM TB_Lien_bateau_shipmember WHERE Statut='actif' AND Fin_validite >= NOW() AND ID_SHIPMEMBER=? AND ID_BATEAU=? ";
$reponse = Requete_preparee_bdd($requete,
array(
array('i', $_SESSION['shipmember']['Cle_primaire']),
array('i', $_SESSION['Bateaux']['bateau']['Cle_primaire'])),
__FILE__, __LINE__);
while ($donnees = Recherche_ligne_bdd($reponse))
{
$flag_sortie = false;
break;
}
Liberation_jeu_de_resultat_bdd($reponse);
}
} while (0);
if ($flag_sortie)
{
// exit indispensable : sans lui, le reste de la page (actions comprises) s'exécutait malgré la redirection (synthèse §47)
Fermeture_bdd();
header('Location:shipmembers.php?action=navigation&destination=Login');
exit();
}
switch ($_SESSION['Bateaux']['page'])
{
//****************************************************************************************************************************
case "Accueil":
{
//--------------------------------------------------
// Récupération des informations sur les bateaux :
$nb_bateaux = 0;
switch($_SESSION['Bateaux']['tri_liste'])
{
case "Nom": { $tri_liste = " ORDER By Nom ".$_SESSION['Bateaux']['sens_tri_liste']; break; }
case "Flotte" : { $tri_liste = " ORDER By nom_flotte ".$_SESSION['Bateaux']['sens_tri_liste'].", Nom"; break; }
case "Adresse_mac": { $tri_liste = " ORDER By adresse_carte ".$_SESSION['Bateaux']['sens_tri_liste']; break; }
case "Version": { $tri_liste = " ORDER By version ".$_SESSION['Bateaux']['sens_tri_liste'].", Nom"; break; }
case "Date_maj": { $tri_liste = " ORDER By Date_derniere_maj ".$_SESSION['Bateaux']['sens_tri_liste'].", Nom"; break; }
default : { $tri_liste = " ORDER By nom_flotte, Nom ".$_SESSION['Bateaux']['sens_tri_liste']; break; }
}
$requete = 'SELECT TB_Bateaux.Cle_primaire AS Cle_primaire, ';
$requete .= ' TB_Bateaux.Nom AS Nom, ';
$requete .= ' (SELECT Nom FROM TB_Flottes WHERE Validite=1 AND Cle_primaire=TB_Bateaux.ID_Flotte) AS nom_flotte, ';
$requete .= ' (SELECT Adresse_mac FROM TB_Affectations_bateaux WHERE ID_Bateau=TB_Bateaux.Cle_primaire AND Date_fin IS NULL) AS adresse_carte, ';
$requete .= ' (SELECT Version_soft FROM TB_Generations_bateaux WHERE ID_Bateau=TB_Bateaux.Cle_primaire AND Date_fin IS NULL) AS version, ';
$requete .= ' TB_Bateaux.Date_derniere_maj AS Date_derniere_maj, ';
$requete .= ' TB_Bateaux.Commentaires AS Commentaires ';
$requete .= ' FROM `TB_Bateaux` ';
$requete .= ' WHERE Validite=1 ';
$requete .= $tri_liste;
//echo "Requête = [ ".$requete."; ] ";
$reponse = Requete_preparee_bdd($requete,
array(),
__FILE__, __LINE__);
while ($donnees = Recherche_ligne_bdd($reponse))
{
$bateaux[$nb_bateaux] = $donnees;
// Mise en forme de certaines variables :
$bateaux[$nb_bateaux]['version'] = ($donnees['version'] == "") ? 'inconnue' : htmlspecialchars($donnees['version']);
if ($donnees['Date_derniere_maj'] < "2020-01-02") $bateaux[$nb_bateaux]['Date_derniere_maj'] = "Jamais";
else $bateaux[$nb_bateaux]['Date_derniere_maj'] = ''.htmlspecialchars($donnees['Date_derniere_maj']).' UTC';
$nb_bateaux ++;
}
Liberation_jeu_de_resultat_bdd($reponse);
break;
}
//****************************************************************************************************************************
case "Details_bateau":
{
//--------------------------------------------------
// Récupération des informations sur le bateau :
$nb_bateaux = 0;
$requete = 'SELECT *, ';
$requete .= ' (SELECT Adresse_mac FROM TB_Affectations_bateaux WHERE ID_Bateau=TB_Bateaux.Cle_primaire AND Date_fin IS NULL) AS Adresse_mac, ';
$requete .= ' (SELECT Version_soft FROM TB_Generations_bateaux WHERE ID_Bateau=TB_Bateaux.Cle_primaire AND Date_fin IS NULL) AS version, ';
// Adresse NetBird du module affecté (Live distant possible seulement si elle est connue - synthèse §49) :
$requete .= ' (SELECT C.Adresse_netbird FROM TB_Affectations_bateaux A JOIN TB_Cartes C ON C.Adresse_mac=A.Adresse_mac WHERE A.ID_Bateau=TB_Bateaux.Cle_primaire AND A.Date_fin IS NULL) AS Adresse_netbird ';
$requete .= ' FROM TB_Bateaux WHERE Cle_primaire=? ';
//echo "Requête = [ ".$requete."; ] ";
$reponse = Requete_preparee_bdd($requete,
array(
array('i', $_SESSION['Bateaux']['bateau']['Cle_primaire'])),
__FILE__, __LINE__);
while ($donnees = Recherche_ligne_bdd($reponse))
{
$bateau = $donnees;
$bateau['Date_derniere_maj_utc'] = $donnees['Date_derniere_maj']; // Brute (UTC), pour l'indicateur de fraîcheur
// Mise en forme de textes :
if ($donnees['Date_derniere_maj'] < "2020-01-02") $bateau['Date_derniere_maj'] = "".t("Jamais")."";
else {
$bateau['Date_derniere_maj'] = Formatage_date($donnees['Date_derniere_maj']);
$_SESSION['Bateaux']['derniere_maj'] = $donnees['Date_derniere_maj'];
}
// $bateau['Commentaires'] = htmlspecialchars(stripcslashes($bateau['Commentaires']));
}
Liberation_jeu_de_resultat_bdd($reponse);
//---------------------------------------------
// Recherche des droits d'accès au bateau :
//---------------------------------------------
if ($_SESSION['shipmember']['Niveau_acces'] >= NIVEAU_ACCES_MINI_ADMINISTRATEUR) // Dans le cas des administrateurs, on accorde tous les droits, indépendamment des paramètres du bateau
{
$acces_profil['Creation_profils'] = 1;
$acces_profil['Devalidation_profils'] = 2;
$acces_profil['Consultation_donnees'] = 2;
$acces_profil['Modification_acces'] = 1;
}
else
{
// Valeurs par défaut :
$id_type_acces = 0;
$acces_profil['Creation_profils'] = 0;
$acces_profil['Devalidation_profils'] = 0;
$acces_profil['Consultation_donnees'] = 0;
$acces_profil['Modification_acces'] = 0;
// Recherche de l'ID de l'accès le plus élevé pour ce bateau et ce shipmember :
// NB : "ORDER BY ... ASC" - plus la clé du type est petite, plus il donne de droits (2 Propriétaire, 3 Installateur,
// 4 Skipper). L'ancien "DESC" retenait l'accès le plus FAIBLE quand un SM en avait plusieurs (synthèse §47).
$requete = "SELECT ID_Type_acces_bateau_shipmember FROM TB_Lien_bateau_shipmember WHERE Statut='actif' AND ID_Shipmember=? AND ID_Bateau=? AND Fin_validite>=? ORDER BY ID_Type_acces_bateau_shipmember ASC LIMIT 1";
$reponse = Requete_preparee_bdd($requete,
array(
array('i', $_SESSION['shipmember']['Cle_primaire']),
array('i', $_SESSION['Bateaux']['bateau']['Cle_primaire']),
array('s', date_format(date_create(), 'Y-m-d H:i:s'))),
__FILE__, __LINE__);
while ($donnees = Recherche_ligne_bdd($reponse))
{
$id_type_acces = $donnees['ID_Type_acces_bateau_shipmember'];
}
Liberation_jeu_de_resultat_bdd($reponse);
// Recherche des caractérirtiques de l'accès le plus élevé trouvé :
if ($id_type_acces > 0)
{
$requete = 'SELECT * FROM TB_Types_acces_bateau_shipmember WHERE Validite=1 AND Cle_primaire=?';
$reponse = Requete_preparee_bdd($requete,
array(
array('i', $id_type_acces)),
__FILE__, __LINE__);
while ($donnees = Recherche_ligne_bdd($reponse))
{
$acces_profil = $donnees;
}
Liberation_jeu_de_resultat_bdd($reponse);
}
$acces_profil['Modification_acces'] = 0; // Modification d'un accès (type, fin) : réservée aux administrateurs
}
// Récupération des types d'accès possibles :
$requete = 'SELECT * FROM TB_Types_acces_bateau_shipmember WHERE Validite=1 ';
$reponse = Requete_bdd($requete, __FILE__, __LINE__);
$type_acces_nb = 0;
while($donnees = Recherche_ligne_bdd($reponse)){
if ($_SESSION['langue'] == "EN") $donnees['Nom'] = $donnees['Nom_en'];
else $donnees['Nom'] = $donnees['Nom_fr'];
$type_acces[$type_acces_nb++] = $donnees;
}
Liberation_jeu_de_resultat_bdd($reponse);
//------------------------------------------------------------------------------------------------------------
// Actions sur les accès et invitations de CE bateau (synthèse §47). Traitées ici, APRÈS le calcul des droits :
// - chaque action vérifie que l'accès visé appartient bien au bateau affiché ;
// - créer une invitation, télécharger son PDF : administrateur, ou Creation_profils = 1 ;
// - suspendre, réactiver, révoquer : administrateur, ou Devalidation_profils = 2 (tous types), ou = 1 (tous
// sauf Propriétaire) ; une invitation en attente peut aussi être révoquée par qui peut en créer ;
// - modifier le type ou la fin d'un accès : administrateur.
// Après chaque action : redirection vers la page (motif POST -> redirection -> GET).
//------------------------------------------------------------------------------------------------------------
$est_administrateur = ($_SESSION['shipmember']['Niveau_acces'] >= NIVEAU_ACCES_MINI_ADMINISTRATEUR);
$peut_inviter = ($est_administrateur || $acces_profil['Creation_profils'] == 1);
//------------------------------------------------------------------------------------------------------------
// Live distant (synthèse §49) : création d'un ticket (aléatoire, 60 s, usage unique, lié à l'utilisateur et au
// bateau) puis redirection vers https://b.ship2net.fr/_auth?ticket=... - le vérificateur du VPS l'échange
// contre une session (cookie propre au sous-domaine du bateau). Seule l'empreinte du ticket est enregistrée.
// Droit : arriver jusqu'ici suffit (administrateur, ou accès actif à CE bateau - contrôlé plus haut) ; le
// vérificateur revérifie de toute façon à l'ouverture de la session puis à chaque requête.
$live_possible = ($bateau['Adresse_netbird'] ?? '') != '';
if (isset($_POST['ouvrir_live']))
{
if (!$live_possible)
{
$_SESSION['Bateaux']['flash_erreur'] = t("Aucun module ShipBase joignable n'est affecté à ce bateau : le Live est indisponible.");
Fermeture_bdd();
header('Location: bateaux.php');
exit();
}
$ticket = bin2hex(random_bytes(32));
Requete_preparee_bdd("INSERT INTO TB_Tickets_live (Empreinte, ID_Shipmember, ID_Bateau, Expiration) VALUES (?, ?, ?, NOW() + INTERVAL 60 SECOND)",
array(array('s', hash('sha256', $ticket)), array('i', $_SESSION['shipmember']['Cle_primaire']), array('i', $bateau['Cle_primaire'])),
__FILE__, __LINE__);
Fermeture_bdd();
header('Location: https://b'.(int)$bateau['Cle_primaire'].'.'.URL_SITE.'/_auth?ticket='.$ticket, true, 303);
exit();
}
//------------------------------------------------------------------------------------------------------------
// Alertes (synthèse §51, étape E) : chacun ne règle que SES préférences et SON numéro ; réactivation d'une
// suspension : administrateur, ou droit d'inviter sur ce bateau (Creation_profils). Redirection après action.
if (isset($_POST['enregistrer_preferences_alertes']) || isset($_POST['enregistrer_telephone']) || isset($_POST['envoyer_code_telephone'])
|| isset($_POST['verifier_code_telephone']) || isset($_POST['reactiver_suspension']))
{
$id_sm = (int)$_SESSION['shipmember']['Cle_primaire'];
if (isset($_POST['enregistrer_preferences_alertes']))
{
$sms = !empty($_POST['alertes_sms']);
Enregistrer_preferences_alertes($id_sm, $bateau['Cle_primaire'], !empty($_POST['alertes_email']), $sms);
$_SESSION['Bateaux']['flash_information'] = t("Vos préférences d'alerte sont enregistrées.");
if ($sms && !Telephone_shipmember($id_sm)['verifie'])
$_SESSION['Bateaux']['flash_information'] .= ' '.t("Les SMS ne partiront qu'une fois votre numéro vérifié (ci-dessous).");
}
else if (isset($_POST['enregistrer_telephone']))
{
$erreur = Enregistrer_telephone($id_sm, (string)($_POST['telephone'] ?? ''), Telephone_shipmember($id_sm)['id_pays']);
if ($erreur == '') $_SESSION['Bateaux']['flash_information'] = t("Numéro enregistré.");
else $_SESSION['Bateaux']['flash_erreur'] = $erreur;
}
else if (isset($_POST['envoyer_code_telephone']))
{
$resultat = Envoyer_code_telephone($id_sm);
$_SESSION['Bateaux'][$resultat['ok'] ? 'flash_information' : 'flash_erreur'] = $resultat['message'];
}
else if (isset($_POST['verifier_code_telephone']))
{
$erreur = Verifier_code_telephone($id_sm, (string)($_POST['code_telephone'] ?? ''));
if ($erreur == '') $_SESSION['Bateaux']['flash_information'] = t("Votre numéro est vérifié : vous recevrez les alertes par SMS.");
else $_SESSION['Bateaux']['flash_erreur'] = $erreur;
}
else
{
if ($est_administrateur || (int)($acces_profil['Creation_profils'] ?? 0) == 1)
{
if (Reactiver_suspension((int)$_POST['reactiver_suspension'], $bateau['Cle_primaire'], $id_sm)) $_SESSION['Bateaux']['flash_information'] = t("Alertes réactivées pour tous les destinataires.");
else $_SESSION['Bateaux']['flash_erreur'] = t("Cette suspension n'existe pas ou a déjà été levée.");
}
else $_SESSION['Bateaux']['flash_erreur'] = t("Votre type d'accès ne permet pas de réactiver les alertes.");
}
$_SESSION['Bateaux']['type_details_bateau'] = "alertes";
Fermeture_bdd();
header('Location: bateaux.php');
exit();
}
if (isset($_POST['creer_invitation']))
{
if (!$peut_inviter) $_SESSION['Bateaux']['flash_erreur'] = t("Vous n'avez pas le droit d'inviter sur ce bateau.");
else
{
$resultat = Creer_invitation($bateau['Cle_primaire'], $_POST['email_invite'] ?? '', $_POST['prenom_invite'] ?? '',
$_POST['nom_invite'] ?? '', (int)($_POST['type_acces'] ?? 0), $_POST['fin_acces'] ?? '',
$_SESSION['shipmember']['Cle_primaire']);
if ($resultat['ok']) $_SESSION['Bateaux']['flash_information'] = str_replace("[[Code]]", $resultat['code'],
t("Invitation créée (code [[Code]]). Téléchargez son PDF dans la liste ci-dessous."));
else $_SESSION['Bateaux']['flash_erreur'] = $resultat['raison'];
}
Fermeture_bdd();
header('Location: bateaux.php');
exit();
}
if (isset($_POST['action_acces']) && isset($_POST['ID']))
{
$lien = Lien_du_bateau((int)$_POST['ID'], $bateau['Cle_primaire']);
$action = (string)$_POST['action_acces'];
if ($lien === null) $_SESSION['Bateaux']['flash_erreur'] = t("Accès introuvable pour ce bateau.");
else if ($action == 'pdf')
{
if (!$peut_inviter || $lien['Statut'] != 'en_attente') $_SESSION['Bateaux']['flash_erreur'] = t("Action non autorisée.");
else
{
$invitation = Invitation_par_code($lien['Code']);
$invitation['Code'] = $lien['Code'];
$invitation['Expiration_invitation'] = $lien['Expiration_invitation'];
Fermeture_bdd();
code_acces_pdf($invitation);
exit();
}
}
else
{
$autorise = Peut_devalider_acces($acces_profil, $est_administrateur, $lien['ID_Type_acces_bateau_shipmember'])
|| ($action == 'revoquer' && $lien['Statut'] == 'en_attente' && $peut_inviter);
$transitions = array( 'suspendre' => array("'actif'", 'suspendu'),
'reactiver' => array("'suspendu'", 'actif'),
'revoquer' => array("'en_attente','actif','suspendu'", 'revoque'));
if (!$autorise || !isset($transitions[$action])) $_SESSION['Bateaux']['flash_erreur'] = t("Action non autorisée.");
else
{
$requete = "UPDATE TB_Lien_bateau_shipmember SET Statut=? WHERE ID=? AND ID_Bateau=? AND Statut IN (".$transitions[$action][0].")";
Requete_preparee_bdd($requete, array(
array('s', $transitions[$action][1]), array('i', $lien['ID']), array('i', $bateau['Cle_primaire'])),
__FILE__, __LINE__);
if (Nb_lignes_modifiees() == 1) $_SESSION['Bateaux']['flash_information'] = t("Accès mis à jour.");
else $_SESSION['Bateaux']['flash_erreur'] = t("Cet accès a déjà changé d'état.");
}
}
Fermeture_bdd();
header('Location: bateaux.php');
exit();
}
if (isset($_POST['modifierAcces']) && isset($_POST['ID']))
{
$lien = Lien_du_bateau((int)$_POST['ID'], $bateau['Cle_primaire']);
$date = date_create_from_format('Y-m-d', (string)($_POST['fin_validite'] ?? ''));
if (!$est_administrateur || $lien === null || $lien['Statut'] == 'revoque') $_SESSION['Bateaux']['flash_erreur'] = t("Action non autorisée.");
else if ($date === false) $_SESSION['Bateaux']['flash_erreur'] = t("Date de fin d'accès invalide.");
else
{
$requete = "UPDATE TB_Lien_bateau_shipmember SET Fin_validite=?, ID_Type_acces_bateau_shipmember=?
WHERE ID=? AND ID_Bateau=? AND EXISTS (SELECT 1 FROM TB_Types_acces_bateau_shipmember WHERE Cle_primaire=? AND Validite=1)";
Requete_preparee_bdd($requete, array(
array('s', $date->format('Y-m-d').' 23:59:59'), array('i', (int)$_POST['type_profil']),
array('i', $lien['ID']), array('i', $bateau['Cle_primaire']), array('i', (int)$_POST['type_profil'])),
__FILE__, __LINE__);
if (Nb_lignes_modifiees() == 1) $_SESSION['Bateaux']['flash_information'] = t("Accès mis à jour.");
else $_SESSION['Bateaux']['flash_erreur'] = t("Aucune modification enregistrée (type d'accès invalide, ou valeurs identiques).");
}
Fermeture_bdd();
header('Location: bateaux.php');
exit();
}
//-----------------------------------------------------------
// Recherche des informations complémentaires sur le bateau :
switch($_SESSION['Bateaux']['type_details_bateau'])
{
//=========================
case "gerer_les_acces":
{
// Accès et invitations du bateau, hors révoqués (synthèse §47) - une seule requête au lieu de 3 par ligne :
$colonne_type = ($_SESSION['langue'] == "EN") ? 'Nom_en' : 'Nom_fr';
$requete = "SELECT L.*, S.Prenom AS sm_Prenom, S.Nom AS sm_Nom, S.Email AS sm_Email,
C.Prenom AS cree_par_Prenom, C.Nom AS cree_par_Nom, T.".$colonne_type." AS Niveau_acces_nom,
(L.Expiration_invitation IS NOT NULL AND L.Expiration_invitation <= NOW()) AS Expiree
FROM TB_Lien_bateau_shipmember L
LEFT JOIN TB_Shipmembers S ON S.ID = L.ID_Shipmember
LEFT JOIN TB_Shipmembers C ON C.ID = L.Cree_par
LEFT JOIN TB_Types_acces_bateau_shipmember T ON T.Cle_primaire = L.ID_Type_acces_bateau_shipmember
WHERE L.ID_Bateau=? AND L.Statut <> 'revoque'
ORDER BY FIELD(L.Statut, 'actif', 'suspendu', 'en_attente'), L.Date_creation";
$reponse = Requete_preparee_bdd($requete, array(array('i', $bateau['Cle_primaire'])), __FILE__, __LINE__);
$utilisateur_acces_nb = 0;
while ($donnees = Recherche_ligne_bdd($reponse, MYSQLI_ASSOC))
{
// Destinataire affiché : le ShipMember s'il a accepté, sinon la personne invitée :
if ($donnees['Statut'] == 'en_attente') $donnees['Destinataire'] = trim($donnees['Prenom_invite'].' '.$donnees['Nom_invite']).' ['.$donnees['Email_invite'].']';
else $donnees['Destinataire'] = trim($donnees['sm_Prenom'].' '.$donnees['sm_Nom']).' ['.$donnees['sm_Email'].']';
$donnees['Cree_par_nom'] = trim($donnees['cree_par_Prenom'].' '.$donnees['cree_par_Nom']);
unset($donnees['Code']); // Jamais envoyé au navigateur (le JSON de chaque ligne alimente le popup)
$utilisateur_acces[$utilisateur_acces_nb++] = $donnees;
}
Liberation_jeu_de_resultat_bdd($reponse);
break;
}
//-------------------------------------------------------------------------------------------------------
// Alertes (synthèse §51, étape E) :
case "alertes":
{
$vue_alertes = array(
'preferences' => Preferences_alertes($_SESSION['shipmember']['Cle_primaire'], $bateau['Cle_primaire']),
'telephone' => Telephone_shipmember($_SESSION['shipmember']['Cle_primaire']),
'alertes' => Alertes_bateau($bateau['Cle_primaire'], 30),
'suspensions' => Suspensions_actives($bateau['Cle_primaire']),
'peut_reactiver' => ($est_administrateur || (int)($acces_profil['Creation_profils'] ?? 0) == 1));
break;
}
//-------------------------------------------------------------------------------------------------------
// Données du bateau (synthèse §50) : lues dans la base de la génération consultée (active par défaut), par
// une connexion dédiée - la connexion principale du site n'est pas touchée.
default:
{
$consultation = array('autorisee' => ($est_administrateur || (int)($acces_profil['Consultation_donnees'] ?? 0) >= 1),
'generations' => array(), 'generation' => null, 'domaines' => array(), 'erreur' => null,
'periode_archivage' => CONSULTATION_PERIODE_ARCHIVAGE_DEFAUT);
if (!$consultation['autorisee']) break;
$consultation['generations'] = Generations_bateau($bateau['Cle_primaire']);
foreach ($consultation['generations'] as $generation)
{
// Génération demandée si elle appartient bien à ce bateau, sinon la génération active :
if ((int)$generation['Cle_primaire'] == (int)($_SESSION['Bateaux']['generation'] ?? 0)) { $consultation['generation'] = $generation; break; }
if ($generation['Date_fin'] === null && $consultation['generation'] === null) $consultation['generation'] = $generation;
}
if ($consultation['generation'] === null) break; // Aucune archive reçue pour ce bateau
$db_generation = Connexion_generation($consultation['generation']['Nom_base']);
if ($db_generation === null) { $consultation['erreur'] = t("Les données de ce bateau sont momentanément inaccessibles."); break; }
$consultation['periode_archivage'] = Periode_archivage_bateau($db_generation);
$consultation['alarmes'] = Episodes_alarmes($db_generation, 20);
foreach (array('circuits' => t("Circuits"), 'capteurs' => t("Capteurs"), 'batteries' => t("Batteries"), 'reservoirs' => t("Réservoirs")) as $domaine => $titre)
{
$consultation['domaines'][$domaine] = array('titre' => $titre, 'familles' => Elements_domaine($db_generation, $domaine));
}
$db_generation->close();
// Génération active : demande d'archive immédiate (si les données ne sont pas déjà récentes - étape 3d) :
$consultation['demande'] = ($consultation['generation']['Date_fin'] === null)
? Demander_archive_si_besoin($bateau['Cle_primaire'], $bateau['Date_derniere_maj_utc']) : null;
$consultation['demande_archive'] = (($consultation['demande']['statut'] ?? '') == 'demandee');
break;
}
} // Fin du switch 'type_details_bateau'
break;
}
//****************************************************************************************************************************
case "Creation_bateau":
case "Modification_bateau":
{
//--------------------------------------------------
// Préparation des variables du bateau. Création : formulaire réinitialisé par l'action "creation_bateau".
// Modification : valeurs lues en base à l'ouverture du formulaire seulement (après une erreur de saisie, les
// valeurs saisies sont conservées) :
if ($_SESSION['Bateaux']['page'] == "Modification_bateau" && !empty($_SESSION['Bateaux']['charger_bateau']))
{
unset($_SESSION['Bateaux']['charger_bateau']);
$requete = 'SELECT Cle_primaire, Nom, ID_Flotte, Date_mes, Commentaires FROM TB_Bateaux WHERE Cle_primaire=? AND Validite=1';
$reponse = Requete_preparee_bdd($requete,
array(
array('i', $_SESSION['Bateaux']['bateau']['Cle_primaire'])),
__FILE__, __LINE__);
$donnees = Recherche_ligne_bdd($reponse);
Liberation_jeu_de_resultat_bdd($reponse);
if ($donnees) $_SESSION['Bateaux']['bateau'] = $donnees;
else $_SESSION['Bateaux']['page'] = "Accueil";
}
//--------------------------------------------------
// Recherche des flottes existantes :
$nb_flottes = 0;
$flottes[$nb_flottes]['Cle_primaire'] = 0;
$flottes[$nb_flottes]['Nom'] = '--- Affecté à aucune flotte ---';
$nb_flottes ++;
$requete = 'SELECT * FROM TB_Flottes WHERE Validite=1 ORDER BY Nom ASC';
$reponse = Requete_preparee_bdd($requete,
array(),
__FILE__, __LINE__);
while ($donnees = Recherche_ligne_bdd($reponse))
{
$flottes[$nb_flottes ++] = $donnees;
}
Liberation_jeu_de_resultat_bdd($reponse);
break;
}
//****************************************************************************************************************************
case "Confirmation_suppression_bateau":
{
// Nom du bateau à confirmer, relu en base (auparavant : le dernier nom chargé en session, qui pouvait être celui
// d'un AUTRE bateau) :
$reponse = Requete_preparee_bdd('SELECT Cle_primaire, Nom FROM TB_Bateaux WHERE Cle_primaire=? AND Validite=1',
array(array('i', $_SESSION['Bateaux']['bateau']['Cle_primaire'])), __FILE__, __LINE__);
$bateau_a_supprimer = Recherche_ligne_bdd($reponse);
Liberation_jeu_de_resultat_bdd($reponse);
if (!$bateau_a_supprimer) $_SESSION['Bateaux']['page'] = "Accueil";
break;
}
//****************************************************************************************************************************
default:
{
$_SESSION['Bateaux']['page'] = "Accueil";
break;
}
}
Fermeture_bdd(); // Déconnexion de MySQL
//************************************************************************************************************************************
// Affichage
//************************************************************************************************************************************
Entete_page_html();
//********************************************************************************************************************************
// Affichage principal :
echo '
';
}
//--------------------------------
// Bouton Live (synthèse §49) - formulaire POST avec jeton (un navigateur qui pré-charge les liens ne crée pas
// de tickets), ouvert dans un nouvel onglet :
echo '
';
// Indicateur de fraîcheur (synthèse §44.6, §50) : date UTC transmise au navigateur, qui l'affiche dans le fuseau
// de l'utilisateur et colore selon l'ancienneté (seuil orange/rouge = 1,25 x la période d'archivage du bateau) :
echo '
'.t("Dernières données reçues").' : ';
if ($bateau['Date_derniere_maj_utc'] === null || $bateau['Date_derniere_maj_utc'] < "2020-01-02") echo ''.t("Jamais").'';
else echo ''.htmlspecialchars($bateau['Date_derniere_maj_utc']).' UTC';
// Mise à jour à l'ouverture (étape 3d) : ce qui a été fait, et pourquoi (synthèse §50) :
$minutes = function($secondes) { return max(1, (int)ceil($secondes / 60)); };
switch ($consultation['demande']['statut'] ?? '')
{
case 'demandee':
echo ''.t("Mise à jour demandée au bateau...").'';
break;
case 'donnees_recentes':
echo ''.str_replace("[[M]]", $minutes($consultation['demande']['attente']),
t("Données récentes : pas de nouvelle demande au bateau avant [[M]] min.")).'';
break;
case 'deja_demandee':
echo ''.str_replace("[[M]]", $minutes($consultation['demande']['attente']),
t("Mise à jour déjà demandée récemment : nouvelle demande possible dans [[M]] min.")).'';
break;
}
echo '
';
echo '
';
//--------------------------------
// Boutons de haut de page (les anciens onglets Carte/Batteries/Circuits/Réservoirs/Capteurs/Système, liés au
// modèle CM4 centralisé, ont été retirés - la consultation des données sera refaite sur la base de la génération
// active du bateau, lot 3 - synthèse §44.10) :
echo '
';
// --- Mes alertes pour ce bateau ---
echo '';
echo '
'.t("Mes alertes pour ce bateau").'
';
if (!$p['a_un_acces']) echo '
'.t("Vous n'avez pas d'accès à ce bateau : vous ne recevez pas ses alertes.").'
';
else
{
echo '
'.t("Vous recevez les alarmes prévues par e-mail ou par SMS dans la configuration du bateau, sur les moyens cochés ci-dessous, ainsi que la perte et le retour de sa connexion.").'
';
echo '';
if ($p['sms'] && !$tel['verifie']) echo '
'.t("SMS en attente : votre numéro doit être vérifié pour que les SMS partent.").'
';
}
echo '';
// --- Mon numéro de téléphone (dès que les SMS sont cochés, ou qu'un numéro existe) ---
if ($p['a_un_acces'] && ($p['sms'] || $tel['telephone'] != ''))
{
echo '';
echo '
'.t("Une alarme qui se déclenche et s'arrête trop souvent suspend ses alertes (e-mail et SMS) pour tous les destinataires : sa configuration est à vérifier. Le son, la LED et les messages à bord ne sont pas concernés.").'
';
echo '
';
foreach ($vue_alertes['suspensions'] as $suspension)
{
echo '
';
break;
}
//===========================================================
case "gerer_les_acces":
{
// Convention (synthèse §48) : les textes traduits (t()) sont affichés tels quels - ils sont écrits dans les
// fichiers de langue, pas saisis par les utilisateurs ; seules les DONNÉES (base, saisie) sont échappées.
echo '
';
if (count($actions) > 0)
{
echo '';
}
if ($acces_profil['Modification_acces'] == 1)
{
echo '';
}
echo '
';
echo '
';
}
echo '
';
echo '
';
echo '
'; // Fin de class="gestion_acces"
//--------------------------------------------
// Popup de modification (administrateurs) : type d'accès et fin de validité.
// (La suppression se fait désormais par le bouton "Révoquer" de chaque ligne.)
if ($acces_profil['Modification_acces'] == 1)
{
echo '
';
echo '
';
echo '
';
echo '
'.t("Modification des droits").'
';
echo '
';
echo '
';
echo ' ';
echo '
';
echo '
';
}
break;
}
//===========================================================
// 'resume' (et toute ancienne valeur de session) :
default:
{
if (!$consultation['autorisee']) { echo '
'.t("Votre type d'accès ne permet pas de consulter les données de ce bateau.").'
'; break; }
if ($consultation['generation'] === null) { echo '
'.t("Aucune donnée n'a encore été reçue de ce bateau.").'
'; break; }
if ($consultation['erreur'] !== null) { echo '
'.htmlspecialchars($consultation['erreur']).'
'; break; }
echo '
';
// Rafraîchissement à l'arrivée d'une nouvelle archive (synthèse §50) - génération active seulement :
if ($consultation['generation']['Date_fin'] === null)
{
echo '';
}
//--------------------------------
// Génération consultée, et choix d'une génération antérieure :
$format_date = function($date) { return ($date === null) ? '' : date_format(date_create($date), 'd/m/Y'); };
echo '';
if ($consultation['generation']['Date_fin'] !== null)
{
echo '
'.t("Vous consultez une génération antérieure : les valeurs ci-dessous sont celles de sa dernière archive, pas l'état actuel du bateau.").'
';
}
//--------------------------------
// Dernières alarmes (synthèse §50) : déclenchements, du plus récent au plus ancien, dates dans le fuseau du
// navigateur. (Pas d'état "alarme en cours" : vieux de jusqu'à une période d'archivage, il ne serait ni
// fiable ni exploitable.)
echo '';
echo '
'.t("Dernières alarmes").'
';
if (count($consultation['alarmes'] ?? array()) == 0) echo '
';
foreach ($consultation['alarmes'] as $alarme)
{
switch ($alarme['statut'])
{
case 'termine': $fin = $date_affichee($alarme['fin_utc']); break;
case 'en_cours': $fin = ''.t("en cours à la dernière archive").''; break;
case 'fin_non_enregistree': $fin = ''.t("fin non enregistrée").''; break;
case 'debut_anterieur': $fin = $date_affichee($alarme['fin_utc']); break;
default: $fin = ''.t("ponctuel").''; break;
}
echo '
';
}
echo '';
//--------------------------------
// Les quatre domaines, par famille, avec leurs dernières valeurs :
foreach ($consultation['domaines'] as $domaine => $donnees_domaine)
{
if (count($donnees_domaine['familles']) == 0) continue;
echo '';
echo '
'.htmlspecialchars($donnees_domaine['titre']).'
';
foreach ($donnees_domaine['familles'] as $famille)
{
echo '
';
echo '
'.htmlspecialchars($famille['nom']).'
';
echo '
';
foreach ($famille['elements'] as $element)
{
// Élément avec courbe historique : ligne cliquable (synthèse §50, étape 3c)
$classes = array();
if ($element['defaut'] !== null) $classes[] = 'en_defaut';
if ($element['courbe']) $classes[] = 'avec_courbe';
echo '