0 remplacé par l'indicatif). Pour les // autres pays, le numéro doit être saisi au format international (+…). const INDICATIFS_PAYS = array('FR' => '33', 'BE' => '32', 'CH' => '41', 'LU' => '352', 'MC' => '377', 'ES' => '34', 'IT' => '39', 'DE' => '49', 'NL' => '31', 'PT' => '351', 'GB' => '44', 'IE' => '353', 'AT' => '43', 'GR' => '30', 'HR' => '385'); //************************************************************************************************************ // Fonction : Numéro de téléphone au format international E.164 (+33612345678), à partir d'une saisie libre // ("06 12 34 56 78", "+33 6 12 34 56 78", "0033 6...", "(0)6..."). // Reçoit : La saisie, l'ID du pays du ShipMember (TB_Pays) pour les numéros nationaux // Retour : Le numéro E.164, "" si la saisie est vide, ou false si elle n'est pas un numéro valide //************************************************************************************************************ function Normaliser_telephone($saisie, $id_pays) { $saisie = trim((string)$saisie); if ($saisie === '') return ''; $numero = preg_replace('/[\s.\-\/()]/', '', $saisie); if (strncmp($numero, '00', 2) === 0) $numero = '+'.substr($numero, 2); if ($numero !== '' && $numero[0] !== '+') { if ($numero[0] !== '0') return false; $reponse = Requete_preparee_bdd("SELECT Code_alpha2 FROM TB_Pays WHERE ID=?", array(array('i', (int)$id_pays)), __FILE__, __LINE__); $pays = Recherche_ligne_bdd($reponse, MYSQLI_ASSOC); Liberation_jeu_de_resultat_bdd($reponse); $indicatif = INDICATIFS_PAYS[strtoupper((string)($pays['Code_alpha2'] ?? ''))] ?? null; if ($indicatif === null) return false; $numero = '+'.$indicatif.substr($numero, 1); } // "+33 (0)6..." : le 0 national après l'indicatif est retiré $numero = preg_replace('/^\+(33|32|41|39|49|31|44|43|30|385|352|377|351|353|34)0/', '+$1', $numero); if (preg_match('/^\+[1-9][0-9]{7,14}$/', $numero) !== 1) return false; // Longueur exacte pour les pays dont le plan de numérotation est fixe (chiffres après l'indicatif) : foreach (array('33' => '9', '34' => '9', '31' => '9', '351' => '9', '41' => '9', '32' => '8,9', '44' => '10') as $indicatif => $longueurs) { if (strncmp($numero, '+'.$indicatif, strlen($indicatif) + 1) === 0) { return in_array((string)(strlen($numero) - strlen($indicatif) - 1), explode(',', $longueurs), true) ? $numero : false; } } return $numero; } //************************************************************************************************************ // Fonction : Préférences d'alerte d'un ShipMember pour un bateau (sur ses accès actifs ou suspendus). // Retour : ['email' => bool, 'sms' => bool, 'a_un_acces' => bool] //************************************************************************************************************ function Preferences_alertes($id_shipmember, $id_bateau) { $reponse = Requete_preparee_bdd("SELECT COUNT(*) AS nb, MAX(Alertes_email) AS email, MAX(Alertes_sms) AS sms FROM TB_Lien_bateau_shipmember WHERE ID_Shipmember=? AND ID_Bateau=? AND Statut IN ('actif','suspendu')", array(array('i', $id_shipmember), array('i', $id_bateau)), __FILE__, __LINE__); $d = Recherche_ligne_bdd($reponse, MYSQLI_ASSOC); Liberation_jeu_de_resultat_bdd($reponse); return array('email' => ((int)$d['email'] == 1), 'sms' => ((int)$d['sms'] == 1), 'a_un_acces' => ((int)$d['nb'] > 0)); } function Enregistrer_preferences_alertes($id_shipmember, $id_bateau, $email, $sms) { Requete_preparee_bdd("UPDATE TB_Lien_bateau_shipmember SET Alertes_email=?, Alertes_sms=? WHERE ID_Shipmember=? AND ID_Bateau=? AND Statut IN ('actif','suspendu')", array(array('i', $email ? 1 : 0), array('i', $sms ? 1 : 0), array('i', $id_shipmember), array('i', $id_bateau)), __FILE__, __LINE__); } //************************************************************************************************************ // Fonction : Téléphone d'un ShipMember et état de sa vérification. // Retour : ['telephone', 'verifie' (bool), 'code_en_cours' (bool), 'id_pays'] //************************************************************************************************************ function Telephone_shipmember($id_shipmember) { $reponse = Requete_preparee_bdd("SELECT Telephone, Telephone_verifie, ID_PAYS, (Code_telephone_expiration > UTC_TIMESTAMP()) AS code_en_cours FROM TB_Shipmembers WHERE ID=?", array(array('i', $id_shipmember)), __FILE__, __LINE__); $d = Recherche_ligne_bdd($reponse, MYSQLI_ASSOC); Liberation_jeu_de_resultat_bdd($reponse); return array('telephone' => (string)($d['Telephone'] ?? ''), 'verifie' => ((int)($d['Telephone_verifie'] ?? 0) == 1), 'code_en_cours' => ((int)($d['code_en_cours'] ?? 0) == 1), 'id_pays' => (int)($d['ID_PAYS'] ?? 0)); } //************************************************************************************************************ // Fonction : Enregistre le numéro d'un ShipMember (même champ que le profil). Un numéro MODIFIÉ n'est plus vérifié. // Retour : '' si enregistré, sinon le message d'erreur //************************************************************************************************************ function Enregistrer_telephone($id_shipmember, $saisie, $id_pays) { $numero = Normaliser_telephone($saisie, $id_pays); if ($numero === false) return t("Numéro invalide : saisissez-le au format international (par exemple +33 6 12 34 56 78)."); // Ordre des affectations : la vérification est évaluée AVANT le changement du numéro Requete_preparee_bdd("UPDATE TB_Shipmembers SET Telephone_verifie = IF(Telephone <=> ?, Telephone_verifie, 0), Code_telephone_empreinte = IF(Telephone <=> ?, Code_telephone_empreinte, NULL), Telephone = ? WHERE ID=?", array(array('s', $numero), array('s', $numero), array('s', $numero), array('i', $id_shipmember)), __FILE__, __LINE__); $_SESSION['shipmember']['Telephone'] = $numero; return ''; } //************************************************************************************************************ // Fonction : Envoie un code de vérification à 6 chiffres au numéro du ShipMember (valable 10 min ; seule son // empreinte est enregistrée). Tant que les identifiants OVH ne sont pas renseignés (Sms_configure) : sur un // site de TEST, le code est affiché à l'écran (pour pouvoir tester) ; en production, l'envoi est refusé. // Retour : ['ok' => bool, 'message' => texte à afficher] //************************************************************************************************************ function Envoyer_code_telephone($id_shipmember) { $telephone = Telephone_shipmember($id_shipmember); if (preg_match('/^\+[1-9][0-9]{7,14}$/', $telephone['telephone']) !== 1) return array('ok' => false, 'message' => t("Enregistrez d'abord un numéro au format international.")); if ($telephone['verifie']) return array('ok' => false, 'message' => t("Ce numéro est déjà vérifié.")); // Pas plus d'un code par minute : $reponse = Requete_preparee_bdd("SELECT (Code_telephone_expiration IS NOT NULL AND Code_telephone_expiration > UTC_TIMESTAMP() + INTERVAL ? SECOND) AS trop_tot FROM TB_Shipmembers WHERE ID=?", array(array('i', DUREE_VALIDITE_CODE_TELEPHONE - DELAI_ENTRE_CODES_TELEPHONE), array('i', $id_shipmember)), __FILE__, __LINE__); $d = Recherche_ligne_bdd($reponse, MYSQLI_ASSOC); Liberation_jeu_de_resultat_bdd($reponse); if ((int)$d['trop_tot'] == 1) return array('ok' => false, 'message' => t("Un code vient d'être envoyé : patientez une minute avant d'en demander un autre.")); $code = sprintf("%06d", random_int(0, 999999)); $erreur = null; $envoye = false; if (Sms_configure()) // config/fonctions_sms.php (étape F) { $envoye = Envoyer_sms_ovh($telephone['telephone'], str_replace("[[Code]]", $code, t("ShipHeart : votre code de vérification est [[Code]] (valable 10 min).")), $erreur); // Le détail (réponse d'OVH) va au journal, pas à l'écran : if (!$envoye) error_log("Envoyer_code_telephone(".(int)$id_shipmember.") : ".(string)$erreur); if (!$envoye) return array('ok' => false, 'message' => t("Le SMS n'a pas pu être envoyé. Vérifiez le numéro, ou réessayez plus tard.")); } elseif (PRODUCTION) return array('ok' => false, 'message' => t("L'envoi de SMS n'est pas disponible pour l'instant.")); Requete_preparee_bdd("UPDATE TB_Shipmembers SET Code_telephone_empreinte=?, Code_telephone_expiration=UTC_TIMESTAMP() + INTERVAL ? SECOND, Code_telephone_essais=0 WHERE ID=?", array(array('s', hash('sha256', $code)), array('i', DUREE_VALIDITE_CODE_TELEPHONE), array('i', $id_shipmember)), __FILE__, __LINE__); if ($envoye) return array('ok' => true, 'message' => t("Un code vient de vous être envoyé par SMS.")); return array('ok' => true, 'message' => str_replace("[[Code]]", $code, t("Site de test, envoi de SMS non configuré : votre code est [[Code]]."))); } //************************************************************************************************************ // Fonction : Vérifie le code saisi (valide, non expiré, moins de ESSAIS_MAX_CODE_TELEPHONE erreurs). // Retour : '' si le numéro est désormais vérifié, sinon le message d'erreur //************************************************************************************************************ function Verifier_code_telephone($id_shipmember, $saisie) { $code = preg_replace('/\D/', '', (string)$saisie); $reponse = Requete_preparee_bdd("SELECT Code_telephone_empreinte, Code_telephone_essais, (Code_telephone_expiration > UTC_TIMESTAMP()) AS valide FROM TB_Shipmembers WHERE ID=?", array(array('i', $id_shipmember)), __FILE__, __LINE__); $d = Recherche_ligne_bdd($reponse, MYSQLI_ASSOC); Liberation_jeu_de_resultat_bdd($reponse); if (!$d || $d['Code_telephone_empreinte'] === null || (int)$d['valide'] != 1) return t("Aucun code en cours de validité : demandez-en un nouveau."); if ((int)$d['Code_telephone_essais'] >= ESSAIS_MAX_CODE_TELEPHONE) return t("Trop d'essais : demandez un nouveau code."); if (strlen($code) != 6 || !hash_equals($d['Code_telephone_empreinte'], hash('sha256', $code))) { Requete_preparee_bdd("UPDATE TB_Shipmembers SET Code_telephone_essais = Code_telephone_essais + 1 WHERE ID=?", array(array('i', $id_shipmember)), __FILE__, __LINE__); return t("Code incorrect."); } Requete_preparee_bdd("UPDATE TB_Shipmembers SET Telephone_verifie=1, Code_telephone_empreinte=NULL, Code_telephone_expiration=NULL, Code_telephone_essais=0 WHERE ID=?", array(array('i', $id_shipmember)), __FILE__, __LINE__); return ''; } //************************************************************************************************************ // Fonction : Dernières alertes d'un bateau, avec le nombre d'envois réussis et en échec. // Retour : Tableau de lignes (Date_evenement, Type, Nom_event, Statut, Envoyes, Echecs) //************************************************************************************************************ function Alertes_bateau($id_bateau, $limite = 30) { $alertes = array(); $reponse = Requete_preparee_bdd("SELECT A.ID, A.Date_evenement, A.Type, A.Nom_event, A.Statut, (SELECT COUNT(*) FROM TB_Alertes_envois E WHERE E.ID_Alerte=A.ID AND E.Statut='envoye') AS Envoyes, (SELECT COUNT(*) FROM TB_Alertes_envois E WHERE E.ID_Alerte=A.ID AND E.Statut='echec') AS Echecs FROM TB_Alertes A WHERE A.ID_Bateau=? ORDER BY A.Date_evenement DESC, A.ID DESC LIMIT ?", array(array('i', $id_bateau), array('i', $limite)), __FILE__, __LINE__); while ($d = Recherche_ligne_bdd($reponse, MYSQLI_ASSOC)) $alertes[] = $d; Liberation_jeu_de_resultat_bdd($reponse); return $alertes; } function Suspensions_actives($id_bateau) { $suspensions = array(); $reponse = Requete_preparee_bdd("SELECT ID, Nom_event, Date_suspension, Motif FROM TB_Suspensions_alertes WHERE ID_Bateau=? AND Date_reactivation IS NULL ORDER BY Date_suspension DESC", array(array('i', $id_bateau)), __FILE__, __LINE__); while ($d = Recherche_ligne_bdd($reponse, MYSQLI_ASSOC)) $suspensions[] = $d; Liberation_jeu_de_resultat_bdd($reponse); return $suspensions; } // Réactivation (droits vérifiés par l'appelant) - la suspension doit appartenir au bateau et être encore active. // Retour : true si réactivée function Reactiver_suspension($id_suspension, $id_bateau, $id_shipmember) { Requete_preparee_bdd("UPDATE TB_Suspensions_alertes SET Date_reactivation=UTC_TIMESTAMP(), Reactivee_par=? WHERE ID=? AND ID_Bateau=? AND Date_reactivation IS NULL", array(array('i', $id_shipmember), array('i', $id_suspension), array('i', $id_bateau)), __FILE__, __LINE__); return (Nb_lignes_modifiees() == 1); }