Grosse évolution avec, entre autres, l'implémentation des alertes par SMS et email
This commit is contained in:
@@ -43,5 +43,10 @@ define("BDD_BATEAUX_DEV_DATABASE_PREFIXE", "");
|
||||
|
||||
// --- Autres ---
|
||||
define("BDD_BATEAU_PASSWORD", "");
|
||||
define("SMTP_PASSWORD", ""); // mot de passe de l'adresse email d'envoi (fonctions_mail.php)
|
||||
define("SMTP_PASSWORD", "");
|
||||
define("CLE_CHIFFREMENT_ARCHIVE", "");
|
||||
define("OVH_SMS_SERVICE", "");
|
||||
define("OVH_SMS_EXPEDITEUR", "");
|
||||
define("OVH_SMS_APPLICATION_KEY", "");
|
||||
define("OVH_SMS_APPLICATION_SECRET", "");
|
||||
define("OVH_SMS_CONSUMER_KEY", "");
|
||||
@@ -10,5 +10,6 @@ require_once(__DIR__.'/fonctions_pdf.php'); // Contient les fonctions de cr
|
||||
require_once(__DIR__.'/fonctions_traductions.php');
|
||||
require_once(__DIR__.'/fonctions_utilitaires.php');
|
||||
require_once(__DIR__.'/fonctions_consultation.php'); // Consultation des données d'un bateau (bases de génération)
|
||||
|
||||
require_once(__DIR__.'/fonctions_alertes_ship2net.php'); // Alertes : préférences, téléphone, alertes envoyées (synthèse §51)
|
||||
require_once(__DIR__.'/fonctions_sms.php');
|
||||
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
<?php
|
||||
//************************************************************************************************************
|
||||
// fonctions_alertes_portail.php - Lot "Alertes", côté PORTAIL (synthèse §51, étape E) : préférences d'alerte de chaque
|
||||
// ShipMember (bateau par bateau), numéro de téléphone (format international, vérification par code), alertes envoyées,
|
||||
// suspensions par le fusible et leur réactivation.
|
||||
// (Le traitement des alertes lui-même est fait par les scripts natifs du VPS : /var/www/fonctions_alertes.php.)
|
||||
//************************************************************************************************************
|
||||
|
||||
const DUREE_VALIDITE_CODE_TELEPHONE = 600; // secondes
|
||||
const ESSAIS_MAX_CODE_TELEPHONE = 5; // saisies erronées tolérées pour un même code
|
||||
const DELAI_ENTRE_CODES_TELEPHONE = 60; // secondes minimum entre deux envois de code
|
||||
|
||||
// Indicatifs des pays les plus probables (numéro national commençant par 0 -> 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);
|
||||
}
|
||||
@@ -20,9 +20,11 @@ require_once(__DIR__ . "/../vendor/autoload.php");
|
||||
// - fichier(s) joint(s) sous forme de tableau "$fichier_joints[N]['fichier']" et "$fichier_joints[N]['nom']"
|
||||
// /!\ Le nombre maxi de fichier joints est limité à 10 (cf le code). Si vous en mettez plus, les derniers ne seront pas traités...
|
||||
// Mais on peut changer cette valeur sans soucis...
|
||||
// Retour : Rien
|
||||
// - (facultatif, synthèse §51) une variable qui reçoit le message d'erreur de PHPMailer en cas d'échec
|
||||
// Retour : true si l'e-mail a été accepté par le serveur SMTP, false sinon (les appels existants, qui ignoraient le retour,
|
||||
// ne sont pas affectés)
|
||||
//************************************************************************************************************
|
||||
function Envoi_email($nom_destinataire, $adresse_destinataire, $objet, $texte_brut, $nom_expediteur, $adresse_expediteur, $fichiers_joints)
|
||||
function Envoi_email($nom_destinataire, $adresse_destinataire, $objet, $texte_brut, $nom_expediteur, $adresse_expediteur, $fichiers_joints, &$erreur = null)
|
||||
{
|
||||
global $message_information;
|
||||
|
||||
@@ -94,10 +96,14 @@ global $message_information;
|
||||
if(!$mail->Send()) // Envoi de l'email et test du résultat
|
||||
{
|
||||
// $message_information .= "Erreur lors de l'envoi de l'email. PHPMailer Error = [" . $mail->ErrorInfo."]";
|
||||
$erreur = $mail->ErrorInfo;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// $message_information .= "L'email a été envoyé correctement.";
|
||||
$erreur = null;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
// ============================================================================
|
||||
// fonctions_sms.php - Envoi de SMS par l'API OVHcloud (synthèse §51, étape F).
|
||||
// Commun au portail (code de vérification du numéro) et aux scripts natifs (alertes) : inclus par config/fonctions.php.
|
||||
//
|
||||
// Identifiants : dans config/confidentiel.php (jamais versionné), constantes OVH_SMS_*. Tant qu'elles sont absentes ou
|
||||
// vides, Sms_configure() renvoie false et aucun envoi n'est tenté (l'échec est enregistré comme tel).
|
||||
//
|
||||
// Requête : POST https://eu.api.ovh.com/1.0/sms/{service}/jobs, signée :
|
||||
// X-Ovh-Signature = "$1$" . sha1(AS + "+" + CK + "+" + METHODE + "+" + URL + "+" + CORPS + "+" + HORODATAGE)
|
||||
// L'horodatage doit être proche de l'heure des serveurs OVH (VPS synchronisé par NTP ; sinon : erreur "Invalid
|
||||
// signature" ou "Query out of time"). La clé n'a besoin que des droits : GET /sms/{service} (crédits) et
|
||||
// POST /sms/{service}/jobs (envoi).
|
||||
// ============================================================================
|
||||
|
||||
const OVH_API_RACINE = "https://eu.api.ovh.com/1.0";
|
||||
const SMS_LONGUEUR_MAX = 160; // un seul SMS (alphabet GSM 7 bits) : ni découpage, ni surcoût
|
||||
const SMS_VALIDITE_MINUTES = 2880; // téléphone éteint / hors réseau : remise tentée pendant 48 h
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Les identifiants OVH sont-ils renseignés ? (Expéditeur facultatif : sans nom d'expéditeur validé, OVH envoie depuis
|
||||
// un numéro court.)
|
||||
// ----------------------------------------------------------------------------
|
||||
function Sms_configure()
|
||||
{
|
||||
foreach (array('OVH_SMS_APPLICATION_KEY', 'OVH_SMS_APPLICATION_SECRET', 'OVH_SMS_CONSUMER_KEY', 'OVH_SMS_SERVICE') as $c)
|
||||
if (!defined($c) || trim((string)constant($c)) === '') return false;
|
||||
return function_exists('curl_init');
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Ramène un texte à l'alphabet GSM 7 bits (accents sans équivalent remplacés par la lettre simple, typographie
|
||||
// remplacée par l'équivalent ASCII, le reste par "?") et le limite à SMS_LONGUEUR_MAX (les caractères de l'extension
|
||||
// GSM - € [ ] { } ^ ~ | \ - comptent double). Sans cela, un seul "ê" ferait passer tout le SMS en UCS-2 (70 caractères
|
||||
// par SMS au lieu de 160).
|
||||
// ----------------------------------------------------------------------------
|
||||
function Texte_sms_gsm($texte)
|
||||
{
|
||||
static $base = "@£\$¥èéùìòÇ\nØø\rÅåΔ_ΦΓΛΩΠΨΣΘΞÆæßÉ !\"#¤%&'()*+,-./0123456789:;<=>?¡ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÑܧ¿abcdefghijklmnopqrstuvwxyzäöñüà";
|
||||
static $extension = "€[]{}^~|\\";
|
||||
static $remplacements = array(
|
||||
'â'=>'a', 'á'=>'a', 'ã'=>'a', 'ê'=>'e', 'ë'=>'e', 'î'=>'i', 'ï'=>'i', 'í'=>'i', 'ô'=>'o', 'ó'=>'o', 'õ'=>'o',
|
||||
'û'=>'u', 'ú'=>'u', 'ç'=>'c', 'ÿ'=>'y', 'œ'=>'oe', 'À'=>'A', 'Â'=>'A', 'Á'=>'A', 'È'=>'E', 'Ê'=>'E', 'Ë'=>'E',
|
||||
'Î'=>'I', 'Ï'=>'I', 'Ô'=>'O', 'Ù'=>'U', 'Û'=>'U', 'Œ'=>'OE', '’'=>"'", '‘'=>"'", '“'=>'"', '”'=>'"', '«'=>'"',
|
||||
'»'=>'"', '–'=>'-', '—'=>'-', '…'=>'...', "\u{00A0}"=>' ', "\u{202F}"=>' ', "\t"=>' ', '°'=>' deg', '•'=>'-');
|
||||
|
||||
$texte = strtr((string)$texte, $remplacements);
|
||||
preg_match_all('/./us', $texte, $caracteres);
|
||||
$resultat = '';
|
||||
$longueur = 0;
|
||||
foreach ($caracteres[0] as $c)
|
||||
{
|
||||
if (strpos($extension, $c) !== false) $poids = 2;
|
||||
else
|
||||
{
|
||||
$poids = 1;
|
||||
if (strpos($base, $c) === false) $c = '?';
|
||||
}
|
||||
if ($longueur + $poids > SMS_LONGUEUR_MAX) break;
|
||||
$resultat .= $c;
|
||||
$longueur += $poids;
|
||||
}
|
||||
return $resultat;
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Appel signé de l'API OVH.
|
||||
// Retour : ['code' => code HTTP (0 si pas de réponse), 'donnees' => réponse JSON décodée (ou null), 'erreur' => texte]
|
||||
// ----------------------------------------------------------------------------
|
||||
function Appel_api_ovh($methode, $chemin, $corps = null)
|
||||
{
|
||||
$url = OVH_API_RACINE.$chemin;
|
||||
$json = ($corps === null) ? '' : json_encode($corps, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
$horodatage = (string)time();
|
||||
$signature = '$1$'.sha1(OVH_SMS_APPLICATION_SECRET.'+'.OVH_SMS_CONSUMER_KEY.'+'.$methode.'+'.$url.'+'.$json.'+'.$horodatage);
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $methode);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 15);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
|
||||
'Content-Type: application/json; charset=utf-8',
|
||||
'X-Ovh-Application: '.OVH_SMS_APPLICATION_KEY,
|
||||
'X-Ovh-Consumer: '.OVH_SMS_CONSUMER_KEY,
|
||||
'X-Ovh-Timestamp: '.$horodatage,
|
||||
'X-Ovh-Signature: '.$signature));
|
||||
if ($corps !== null) curl_setopt($ch, CURLOPT_POSTFIELDS, $json);
|
||||
$reponse = curl_exec($ch);
|
||||
$code = (int)curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
|
||||
$erreur_curl = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($reponse === false) return array('code' => 0, 'donnees' => null, 'erreur' => "OVH injoignable : ".$erreur_curl);
|
||||
$donnees = json_decode($reponse, true);
|
||||
if ($code !== 200)
|
||||
{
|
||||
$message = is_array($donnees) && isset($donnees['message']) ? $donnees['message'] : substr($reponse, 0, 200);
|
||||
return array('code' => $code, 'donnees' => $donnees, 'erreur' => "OVH HTTP ".$code." : ".$message);
|
||||
}
|
||||
return array('code' => $code, 'donnees' => $donnees, 'erreur' => '');
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Envoi d'un SMS à un numéro E.164 (+33612345678).
|
||||
// $erreur : motif de l'échec (sinon null)
|
||||
// $reference : identifiant de l'envoi chez OVH (pour le suivi dans l'espace client), sinon null
|
||||
// Retour : true si OVH a accepté le numéro (la remise effective au téléphone n'est pas connue à ce stade)
|
||||
// ----------------------------------------------------------------------------
|
||||
function Envoyer_sms_ovh($numero, $texte, &$erreur = null, &$reference = null)
|
||||
{
|
||||
$erreur = null;
|
||||
$reference = null;
|
||||
if (!Sms_configure()) { $erreur = "envoi de SMS non configuré (identifiants OVH absents)"; return false; }
|
||||
if (preg_match('/^\+[1-9][0-9]{6,14}$/', (string)$numero) !== 1) { $erreur = "numéro invalide"; return false; }
|
||||
|
||||
$corps = array(
|
||||
'message' => Texte_sms_gsm($texte),
|
||||
'receivers' => array($numero),
|
||||
'charset' => 'UTF-8',
|
||||
'coding' => '7bit',
|
||||
'noStopClause' => true, // SMS non commerciaux (alertes, codes) : pas de mention "STOP"
|
||||
'priority' => 'high',
|
||||
'validityPeriod' => SMS_VALIDITE_MINUTES);
|
||||
$expediteur = defined('OVH_SMS_EXPEDITEUR') ? trim((string)OVH_SMS_EXPEDITEUR) : '';
|
||||
if ($expediteur !== '') $corps['sender'] = $expediteur; // nom validé par OVH
|
||||
else $corps['senderForResponse'] = true; // sinon : numéro court d'OVH
|
||||
|
||||
$r = Appel_api_ovh('POST', '/sms/'.rawurlencode(OVH_SMS_SERVICE).'/jobs', $corps);
|
||||
if ($r['erreur'] !== '') { $erreur = $r['erreur']; return false; }
|
||||
|
||||
$valides = $r['donnees']['validReceivers'] ?? array();
|
||||
if (!is_array($valides) || count($valides) == 0)
|
||||
{
|
||||
$erreur = "numéro refusé par OVH (".implode(', ', (array)($r['donnees']['invalidReceivers'] ?? array())).")";
|
||||
return false;
|
||||
}
|
||||
$ids = $r['donnees']['ids'] ?? array();
|
||||
if (is_array($ids) && count($ids) > 0) $reference = (string)$ids[0];
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Crédits SMS restants sur le compte OVH, ou null (non configuré, erreur).
|
||||
// ----------------------------------------------------------------------------
|
||||
function Credits_sms_ovh(&$erreur = null)
|
||||
{
|
||||
$erreur = null;
|
||||
if (!Sms_configure()) { $erreur = "envoi de SMS non configuré (identifiants OVH absents)"; return null; }
|
||||
$r = Appel_api_ovh('GET', '/sms/'.rawurlencode(OVH_SMS_SERVICE));
|
||||
if ($r['erreur'] !== '') { $erreur = $r['erreur']; return null; }
|
||||
return isset($r['donnees']['creditsLeft']) ? (float)$r['donnees']['creditsLeft'] : null;
|
||||
}
|
||||
Reference in New Issue
Block a user