325 lines
18 KiB
PHP
325 lines
18 KiB
PHP
<?php
|
|
//************************************************************************************************************
|
|
// fonctions_acces.php - Accès des ShipMembers aux bateaux et invitations (synthèse §47)
|
|
//
|
|
// Regroupe ce qui était recopié dans shipmembers.php (5 blocs d'activation de code, 7 requêtes "accès actifs")
|
|
// et administration.php (1 + 1). Modèle de TB_Lien_bateau_shipmember :
|
|
// - Statut : 'en_attente' (invitation non acceptée), 'actif', 'suspendu', 'revoque' ;
|
|
// - une invitation désigne une adresse e-mail (Email_invite) : seul un compte ShipMember ACTIVÉ ayant cette
|
|
// adresse peut l'accepter (l'activation prouve le contrôle de la boîte - un compte non activé ne le prouve pas) ;
|
|
// - Expiration_invitation : date limite pour accepter ; Fin_validite : fin de l'accès accordé.
|
|
//
|
|
// Toutes les fonctions utilisent la connexion courante ($_SESSION['db'], ouverte par Ouverture_bdd()).
|
|
//************************************************************************************************************
|
|
|
|
// Alphabet des codes d'invitation : majuscules et chiffres SANS les caractères ambigus à la lecture (0/O, 1/I/L) -
|
|
// 31 symboles. 8 caractères présentés en deux groupes (ex : K7QX-M3PW), soit 31^8 = 8,5.10^11 combinaisons.
|
|
const CODE_INVITATION_ALPHABET = "23456789ABCDEFGHJKMNPQRSTUVWXYZ";
|
|
const CODE_INVITATION_LONGUEUR = 8;
|
|
|
|
// Durée de validité par défaut d'une invitation (jours), si TB_Configuration.Duree_validite_invitation n'est pas
|
|
// chargée en session :
|
|
const DUREE_VALIDITE_INVITATION_DEFAUT = 30;
|
|
|
|
|
|
//************************************************************************************************************
|
|
// Fonction : Nombre de lignes réellement modifiées par la DERNIÈRE requête exécutée sur la connexion courante.
|
|
// NB : mysqli_affected_rows() ne convient pas après Requete_preparee_bdd(), qui appelle
|
|
// mysqli_stmt_get_result() même pour un UPDATE - ce qui remet le compteur côté PHP à -1 (constaté en
|
|
// test). ROW_COUNT() interroge directement le serveur. À appeler IMMÉDIATEMENT après la requête.
|
|
// Reçoit : Rien
|
|
// Retour : Le nombre de lignes modifiées
|
|
//************************************************************************************************************
|
|
function Nb_lignes_modifiees()
|
|
{
|
|
$reponse = Requete_bdd("SELECT ROW_COUNT() AS nb", __FILE__, __LINE__);
|
|
$donnees = Recherche_ligne_bdd($reponse, MYSQLI_ASSOC);
|
|
Liberation_jeu_de_resultat_bdd($reponse);
|
|
return (int)$donnees['nb'];
|
|
}
|
|
|
|
|
|
//************************************************************************************************************
|
|
// Fonction : Liste des accès ACTIFS d'un ShipMember (statut 'actif' et Fin_validite non dépassée).
|
|
// Reçoit : La clé primaire du ShipMember
|
|
// Retour : Un tableau de lignes ['ID_Bateau', 'ID_Type_acces_bateau_shipmember'] (vide si aucun accès)
|
|
//************************************************************************************************************
|
|
function Acces_actifs_shipmember($id_shipmember)
|
|
{
|
|
$acces = [];
|
|
$requete = "SELECT ID_Bateau, ID_Type_acces_bateau_shipmember FROM TB_Lien_bateau_shipmember
|
|
WHERE ID_Shipmember=? AND Statut='actif' AND Fin_validite >= NOW()";
|
|
$reponse = Requete_preparee_bdd($requete, array(array('i', $id_shipmember)), __FILE__, __LINE__);
|
|
while ($donnees = Recherche_ligne_bdd($reponse, MYSQLI_ASSOC)) $acces[] = $donnees;
|
|
Liberation_jeu_de_resultat_bdd($reponse);
|
|
return $acces;
|
|
}
|
|
|
|
|
|
//************************************************************************************************************
|
|
// Fonction : Indique si le compte d'un ShipMember est activé, c'est-à-dire si son titulaire a prouvé qu'il
|
|
// contrôle l'adresse e-mail (connexion avec le mot de passe provisoire reçu, puis choix d'un mot de
|
|
// passe : la ligne TB_Activation est alors supprimée).
|
|
// Reçoit : La clé primaire du ShipMember
|
|
// Retour : true si activé
|
|
//************************************************************************************************************
|
|
function Compte_est_active($id_shipmember)
|
|
{
|
|
$reponse = Requete_preparee_bdd("SELECT COUNT(*) AS nb FROM TB_Activation WHERE ID_SHIPMEMBERS=?",
|
|
array(array('i', $id_shipmember)), __FILE__, __LINE__);
|
|
$donnees = Recherche_ligne_bdd($reponse, MYSQLI_ASSOC);
|
|
Liberation_jeu_de_resultat_bdd($reponse);
|
|
return ($donnees['nb'] == 0);
|
|
}
|
|
|
|
|
|
//************************************************************************************************************
|
|
// Fonction : Normalise un code d'invitation saisi ou reçu (majuscules, espaces et tirets retirés, puis tiret
|
|
// remis au milieu) et vérifie son format.
|
|
// Reçoit : La valeur brute (GET, POST, saisie manuelle...)
|
|
// Retour : Le code normalisé (ex : "K7QX-M3PW"), ou false si le format est invalide
|
|
//************************************************************************************************************
|
|
function Normaliser_code_invitation($valeur)
|
|
{
|
|
if (!is_string($valeur)) return false; // écarte ?code_acces[]=... (tableau)
|
|
$brut = strtoupper(preg_replace('/[\s-]/', '', $valeur));
|
|
$moitie = intdiv(CODE_INVITATION_LONGUEUR, 2);
|
|
if (preg_match('/^['.CODE_INVITATION_ALPHABET.']{'.CODE_INVITATION_LONGUEUR.'}\z/', $brut) !== 1) return false;
|
|
return substr($brut, 0, $moitie).'-'.substr($brut, $moitie);
|
|
}
|
|
|
|
|
|
//************************************************************************************************************
|
|
// Fonction : Génère un code d'invitation unique (générateur aléatoire cryptographique random_int).
|
|
// Reçoit : Rien
|
|
// Retour : Le code (ex : "K7QX-M3PW")
|
|
//************************************************************************************************************
|
|
function Generer_code_invitation()
|
|
{
|
|
$max = strlen(CODE_INVITATION_ALPHABET) - 1;
|
|
do
|
|
{
|
|
$brut = "";
|
|
for ($i = 0; $i < CODE_INVITATION_LONGUEUR; $i++) $brut .= CODE_INVITATION_ALPHABET[random_int(0, $max)];
|
|
$code = Normaliser_code_invitation($brut);
|
|
|
|
$reponse = Requete_preparee_bdd("SELECT COUNT(*) AS nb FROM TB_Lien_bateau_shipmember WHERE Code=?",
|
|
array(array('s', $code)), __FILE__, __LINE__);
|
|
$donnees = Recherche_ligne_bdd($reponse, MYSQLI_ASSOC);
|
|
Liberation_jeu_de_resultat_bdd($reponse);
|
|
}
|
|
while ($donnees['nb'] != 0);
|
|
return $code;
|
|
}
|
|
|
|
|
|
//************************************************************************************************************
|
|
// Fonction : Crée une invitation nominative (statut 'en_attente').
|
|
// Reçoit : Le bateau, l'adresse e-mail du destinataire (obligatoire), son prénom et son nom (facultatifs, pour
|
|
// l'affichage), le type d'accès, la fin de l'accès accordé (format 'Y-m-d', vide = sans limite) et le
|
|
// ShipMember qui invite.
|
|
// Retour : ['ok'=>true, 'id'=>int, 'code'=>string] ou ['ok'=>false, 'raison'=>string]
|
|
// NB : les droits de celui qui invite sont vérifiés par la page appelante.
|
|
//************************************************************************************************************
|
|
function Creer_invitation($id_bateau, $email, $prenom, $nom, $id_type_acces, $fin_acces, $id_cree_par)
|
|
{
|
|
$email = strtolower(trim((string)$email));
|
|
if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) return ['ok' => false, 'raison' => t("Adresse e-mail invalide.")];
|
|
|
|
$prenom = trim((string)$prenom);
|
|
$nom = trim((string)$nom);
|
|
$prenom = ($prenom === '') ? null : mb_substr($prenom, 0, 50);
|
|
$nom = ($nom === '') ? null : mb_substr($nom, 0, 50);
|
|
|
|
// Type d'accès : doit exister et être valide (l'ancien type "Administrateur" est dévalidé) :
|
|
$reponse = Requete_preparee_bdd("SELECT COUNT(*) AS nb FROM TB_Types_acces_bateau_shipmember WHERE Cle_primaire=? AND Validite=1",
|
|
array(array('i', $id_type_acces)), __FILE__, __LINE__);
|
|
$donnees = Recherche_ligne_bdd($reponse, MYSQLI_ASSOC);
|
|
Liberation_jeu_de_resultat_bdd($reponse);
|
|
if ($donnees['nb'] == 0) return ['ok' => false, 'raison' => t("Type d'accès invalide.")];
|
|
|
|
// Fin de l'accès accordé : date du jour en fin de journée, ou sans limite :
|
|
if ($fin_acces === null || trim($fin_acces) === '') $fin_validite = '9999-12-31 23:59:59';
|
|
else
|
|
{
|
|
$date = date_create_from_format('Y-m-d', trim($fin_acces));
|
|
if ($date === false) return ['ok' => false, 'raison' => t("Date de fin d'accès invalide.")];
|
|
$fin_validite = $date->format('Y-m-d').' 23:59:59';
|
|
}
|
|
|
|
$duree = isset($_SESSION['Config__Duree_validite_invitation']) ? (int)$_SESSION['Config__Duree_validite_invitation'] : DUREE_VALIDITE_INVITATION_DEFAUT;
|
|
if ($duree <= 0) $duree = DUREE_VALIDITE_INVITATION_DEFAUT;
|
|
|
|
$code = Generer_code_invitation();
|
|
$requete = "INSERT INTO TB_Lien_bateau_shipmember
|
|
(Statut, ID_Shipmember, ID_Bateau, Email_invite, Prenom_invite, Nom_invite, Code, Expiration_invitation,
|
|
Fin_validite, Cree_par, ID_Type_acces_bateau_shipmember)
|
|
VALUES ('en_attente', 0, ?, ?, ?, ?, ?, DATE_ADD(NOW(), INTERVAL ? DAY), ?, ?, ?)";
|
|
Requete_preparee_bdd($requete, array(
|
|
array('i', $id_bateau), array('s', $email), array('s', $prenom), array('s', $nom),
|
|
array('s', $code), array('i', $duree), array('s', $fin_validite),
|
|
array('i', $id_cree_par), array('i', $id_type_acces)),
|
|
__FILE__, __LINE__);
|
|
return ['ok' => true, 'id' => Recuperation_id_cree_bdd(), 'code' => $code];
|
|
}
|
|
|
|
|
|
//************************************************************************************************************
|
|
// Fonction : Informations sur une invitation à partir de son code (bandeau d'accueil d'un lien d'invitation).
|
|
// Reçoit : Le code (brut ou normalisé)
|
|
// Retour : Un tableau (ID, Statut, Email_invite, Prenom_invite, Nom_invite, Nom_bateau, Type_acces,
|
|
// Expiree [0/1]) ou null si le code est inconnu ou mal formé
|
|
//************************************************************************************************************
|
|
function Invitation_par_code($code)
|
|
{
|
|
$code = Normaliser_code_invitation($code);
|
|
if ($code === false) return null;
|
|
|
|
$colonne_type = (isset($_SESSION['langue']) && $_SESSION['langue'] == 'EN') ? 'Nom_en' : 'Nom_fr';
|
|
$requete = "SELECT L.ID, L.Statut, L.Email_invite, L.Prenom_invite, L.Nom_invite, B.Nom AS Nom_bateau,
|
|
T.".$colonne_type." AS Type_acces,
|
|
(L.Expiration_invitation IS NOT NULL AND L.Expiration_invitation <= NOW()) AS Expiree
|
|
FROM TB_Lien_bateau_shipmember L
|
|
JOIN TB_Bateaux B ON B.Cle_primaire = L.ID_Bateau
|
|
LEFT JOIN TB_Types_acces_bateau_shipmember T ON T.Cle_primaire = L.ID_Type_acces_bateau_shipmember
|
|
WHERE L.Code=?";
|
|
$reponse = Requete_preparee_bdd($requete, array(array('s', $code)), __FILE__, __LINE__);
|
|
$donnees = Recherche_ligne_bdd($reponse, MYSQLI_ASSOC);
|
|
Liberation_jeu_de_resultat_bdd($reponse);
|
|
return $donnees ?: null;
|
|
}
|
|
|
|
|
|
//************************************************************************************************************
|
|
// Fonction : Invitations en attente (non expirées) adressées à une adresse e-mail.
|
|
// Reçoit : L'adresse e-mail du ShipMember connecté
|
|
// Retour : Un tableau de lignes (ID, Nom_bateau, Type_acces, Expiration_invitation, Prenom_invitant, Nom_invitant)
|
|
//************************************************************************************************************
|
|
function Invitations_en_attente($email)
|
|
{
|
|
$invitations = [];
|
|
$colonne_type = (isset($_SESSION['langue']) && $_SESSION['langue'] == 'EN') ? 'Nom_en' : 'Nom_fr';
|
|
$requete = "SELECT L.ID, B.Nom AS Nom_bateau, T.".$colonne_type." AS Type_acces, L.Expiration_invitation,
|
|
S.Prenom AS Prenom_invitant, S.Nom AS Nom_invitant
|
|
FROM TB_Lien_bateau_shipmember L
|
|
JOIN TB_Bateaux B ON B.Cle_primaire = L.ID_Bateau
|
|
LEFT JOIN TB_Types_acces_bateau_shipmember T ON T.Cle_primaire = L.ID_Type_acces_bateau_shipmember
|
|
LEFT JOIN TB_Shipmembers S ON S.ID = L.Cree_par
|
|
WHERE L.Statut='en_attente' AND L.Email_invite=?
|
|
AND (L.Expiration_invitation IS NULL OR L.Expiration_invitation > NOW())
|
|
ORDER BY L.Date_creation";
|
|
$reponse = Requete_preparee_bdd($requete, array(array('s', strtolower(trim((string)$email)))), __FILE__, __LINE__);
|
|
while ($donnees = Recherche_ligne_bdd($reponse, MYSQLI_ASSOC)) $invitations[] = $donnees;
|
|
Liberation_jeu_de_resultat_bdd($reponse);
|
|
return $invitations;
|
|
}
|
|
|
|
|
|
//************************************************************************************************************
|
|
// Fonction : Accepte une invitation pour le ShipMember connecté.
|
|
// Toutes les conditions (en attente, adresse identique, non expirée) sont vérifiées DANS la requête de
|
|
// mise à jour elle-même : une invitation ne peut donc être acceptée qu'une fois, même par deux
|
|
// requêtes simultanées.
|
|
// Reçoit : L'ID de l'invitation, la clé primaire et l'adresse e-mail du ShipMember connecté
|
|
// Retour : ['ok'=>true] ou ['ok'=>false, 'raison'=>string]
|
|
//************************************************************************************************************
|
|
function Accepter_invitation($id_lien, $id_shipmember, $email)
|
|
{
|
|
if (!Compte_est_active($id_shipmember))
|
|
{
|
|
return ['ok' => false, 'raison' => t("Votre compte doit être activé (mot de passe provisoire reçu par e-mail) avant de pouvoir accepter une invitation.")];
|
|
}
|
|
$requete = "UPDATE TB_Lien_bateau_shipmember
|
|
SET Statut='actif', ID_Shipmember=?, Date_activation=NOW()
|
|
WHERE ID=? AND Statut='en_attente' AND Email_invite=?
|
|
AND (Expiration_invitation IS NULL OR Expiration_invitation > NOW())";
|
|
Requete_preparee_bdd($requete, array(
|
|
array('i', $id_shipmember), array('i', $id_lien), array('s', strtolower(trim((string)$email)))),
|
|
__FILE__, __LINE__);
|
|
if (Nb_lignes_modifiees() == 1) return ['ok' => true];
|
|
return ['ok' => false, 'raison' => t("Cette invitation n'est plus valable (déjà utilisée, expirée, révoquée, ou adressée à une autre adresse).")];
|
|
}
|
|
|
|
|
|
//************************************************************************************************************
|
|
// Fonction : Refuse une invitation (elle passe à 'revoque').
|
|
// Reçoit : L'ID de l'invitation et l'adresse e-mail du ShipMember connecté
|
|
// Retour : true si l'invitation a bien été refusée
|
|
//************************************************************************************************************
|
|
function Refuser_invitation($id_lien, $email)
|
|
{
|
|
$requete = "UPDATE TB_Lien_bateau_shipmember SET Statut='revoque'
|
|
WHERE ID=? AND Statut='en_attente' AND Email_invite=?";
|
|
Requete_preparee_bdd($requete, array(array('i', $id_lien), array('s', strtolower(trim((string)$email)))), __FILE__, __LINE__);
|
|
return (Nb_lignes_modifiees() == 1);
|
|
}
|
|
|
|
|
|
//************************************************************************************************************
|
|
// Fonction : Masque partiellement une adresse e-mail pour l'afficher à quelqu'un qui n'est pas encore connecté
|
|
// (ex : "pierre.durand@exemple.fr" -> "p•••@exemple.fr").
|
|
// Reçoit : L'adresse
|
|
// Retour : L'adresse masquée
|
|
//************************************************************************************************************
|
|
function Masquer_email($email)
|
|
{
|
|
$position = strpos((string)$email, '@');
|
|
if ($position === false || $position == 0) return "•••";
|
|
return mb_substr($email, 0, 1).'•••'.substr($email, $position);
|
|
}
|
|
|
|
|
|
//************************************************************************************************************
|
|
// Clé du type d'accès "Propriétaire" dans TB_Types_acces_bateau_shipmember (cf Devalidation_profils = 1 : tous
|
|
// les types SAUF Propriétaire).
|
|
//************************************************************************************************************
|
|
const TYPE_ACCES_PROPRIETAIRE = 2;
|
|
|
|
|
|
//************************************************************************************************************
|
|
// Fonction : Charge un accès (ou une invitation) À CONDITION qu'il appartienne au bateau indiqué - toute action
|
|
// sur un accès passe par là, pour qu'un identifiant envoyé par le navigateur ne puisse jamais viser
|
|
// l'accès d'un autre bateau.
|
|
// Reçoit : L'ID de l'accès et la clé primaire du bateau affiché
|
|
// Retour : La ligne (ID, Statut, Code, ID_Type_acces_bateau_shipmember, Expiration_invitation) ou null
|
|
//************************************************************************************************************
|
|
function Lien_du_bateau($id_lien, $id_bateau)
|
|
{
|
|
$requete = "SELECT ID, Statut, Code, ID_Type_acces_bateau_shipmember, Expiration_invitation
|
|
FROM TB_Lien_bateau_shipmember WHERE ID=? AND ID_Bateau=?";
|
|
$reponse = Requete_preparee_bdd($requete, array(array('i', $id_lien), array('i', $id_bateau)), __FILE__, __LINE__);
|
|
$donnees = Recherche_ligne_bdd($reponse, MYSQLI_ASSOC);
|
|
Liberation_jeu_de_resultat_bdd($reponse);
|
|
return $donnees ?: null;
|
|
}
|
|
|
|
|
|
//************************************************************************************************************
|
|
// Fonction : Indique si l'utilisateur peut suspendre, réactiver ou révoquer un accès d'un type donné.
|
|
// Administrateur : toujours. Devalidation_profils = 2 : tous les types. = 1 : tous sauf Propriétaire.
|
|
// Reçoit : Les droits de l'utilisateur sur le bateau ($acces_profil), s'il est administrateur, le type visé
|
|
// Retour : true si autorisé
|
|
//************************************************************************************************************
|
|
function Peut_devalider_acces($acces_profil, $est_administrateur, $id_type_cible)
|
|
{
|
|
if ($est_administrateur) return true;
|
|
$niveau = isset($acces_profil['Devalidation_profils']) ? (int)$acces_profil['Devalidation_profils'] : 0;
|
|
if ($niveau >= 2) return true;
|
|
if ($niveau == 1) return ((int)$id_type_cible != TYPE_ACCES_PROPRIETAIRE);
|
|
return false;
|
|
}
|
|
|
|
|
|
//************************************************************************************************************
|
|
// Fonction : Met à jour en session la liste des accès actifs du ShipMember connecté ($_SESSION['bateau_shipmember']
|
|
// et $_SESSION['acces']), à partir de la base - la liste est REMPLACÉE (l'ancien code y ajoutait des
|
|
// lignes sans la vider : des accès révoqués pouvaient y rester jusqu'à la déconnexion).
|
|
// Reçoit : La clé primaire du ShipMember
|
|
// Retour : Rien
|
|
//************************************************************************************************************
|
|
function Rafraichir_acces_session($id_shipmember)
|
|
{
|
|
$_SESSION['bateau_shipmember'] = Acces_actifs_shipmember($id_shipmember);
|
|
$_SESSION['acces'] = count($_SESSION['bateau_shipmember']);
|
|
}
|