Files
ship2net-www/config/fonctions_sms.php

159 lines
7.8 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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;
}