Init Git Prod Mars 2026
This commit is contained in:
commit
d81efe93b1
362 files changed
+86627
No files matched your search
@@ -0,0 +1,26 @@
|
||||
# Pas de listing
|
||||
Options -Indexes
|
||||
|
||||
# Réécriture activée
|
||||
RewriteEngine On
|
||||
|
||||
# Autoriser UNIQUEMENT /sms/notify_rdv.php en HTTP
|
||||
RewriteCond %{REQUEST_URI} !/sms/notify_rdv\.php$ [NC]
|
||||
RewriteRule ^ - [F]
|
||||
|
||||
# notify_rdv.php : POST uniquement
|
||||
<Files "notify_rdv.php">
|
||||
<LimitExcept POST>
|
||||
Require all denied
|
||||
</LimitExcept>
|
||||
</Files>
|
||||
|
||||
# Interdire l'accès direct aux fichiers sensibles
|
||||
<FilesMatch "^(config|config\.sample|wa_client|templates)\.php$">
|
||||
Require all denied
|
||||
</FilesMatch>
|
||||
|
||||
# Bloquer les fichiers cachés/artefacts
|
||||
<FilesMatch "^(\.env|\.git|composer\.json|composer\.lock|.*\.md|.*\.txt)$">
|
||||
Require all denied
|
||||
</FilesMatch>
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
// Interdit l'accès direct au fichier dans un navigateur
|
||||
if (isset($_SERVER['SCRIPT_FILENAME']) && realpath(__FILE__) === realpath($_SERVER['SCRIPT_FILENAME'])) {
|
||||
http_response_code(403);
|
||||
exit('Forbidden');
|
||||
}
|
||||
|
||||
// Secrets par variables d'environnement (idéal en production)
|
||||
return [
|
||||
// Récupération depuis l'ENV si dispo, sinon fallback
|
||||
'phone_number_id' => getenv('WHATSAPP_PHONE_NUMBER_ID') ?: '720141641193323',
|
||||
'access_token' => getenv('WHATSAPP_TOKEN') ?: 'EAAYZApQ01JR8BPVMSMYVAYHzg6WHfFJW5Q9fyphrxvDl32hIq4X169LujTe0ffmlscl5t0vEFuYJrjNUCWFBHYOUGRJU5UT4QQYJb1TwqFH1Pn9ZBu9UeAi0cp6vUKmKNV40B3JlN1bhE2u0czZBX8dXko3Oh97dCIMijcY4Ib88aZCMUBxjBzsSQuk7Rl52QwZDZD',
|
||||
// Langue par défaut de tes templates
|
||||
'default_lang' => 'ru',
|
||||
// Timeout réseau cURL
|
||||
'timeout' => 20,
|
||||
];
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
if (!defined('APP_BOOT')) { http_response_code(403); exit('Forbidden'); }
|
||||
// /sms/config.sample.php
|
||||
return [
|
||||
// Récupération depuis l'ENV si dispo, sinon fallback
|
||||
'phone_number_id' => getenv('WHATSAPP_PHONE_NUMBER_ID') ?: '720141641193323',
|
||||
'access_token' => getenv('WHATSAPP_TOKEN') ?: 'EAAYZApQ01JR8BPVMSMYVAYHzg6WHfFJW5Q9fyphrxvDl32hIq4X169LujTe0ffmlscl5t0vEFuYJrjNUCWFBHYOUGRJU5UT4QQYJb1TwqFH1Pn9ZBu9UeAi0cp6vUKmKNV40B3JlN1bhE2u0czZBX8dXko3Oh97dCIMijcY4Ib88aZCMUBxjBzsSQuk7Rl52QwZDZD',
|
||||
// Langue par défaut de tes templates
|
||||
'default_lang' => 'ru',
|
||||
// Timeout réseau cURL
|
||||
'timeout' => 20,
|
||||
];
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
// /sms/notify_rdv.php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sms;
|
||||
|
||||
require_once __DIR__ . '/wa_client.php';
|
||||
|
||||
function notify_rdv(string $to, string $prenomNom, string $dateHeureDDMMYYYY_HHMM): array
|
||||
{
|
||||
// Charge config & templates
|
||||
$cfg = require __DIR__ . '/config.php';
|
||||
$templates = require __DIR__ . '/templates.php';
|
||||
|
||||
$tplName = 'notification_rdv';
|
||||
$tpl = $templates[$tplName] ?? null;
|
||||
if (!$tpl) {
|
||||
throw new \RuntimeException("Template {$tplName} non déclaré dans /sms/templates.php");
|
||||
}
|
||||
|
||||
// Valide le format date/heure attendu "01/01/2025 12:00"
|
||||
if (!preg_match('/^\d{2}\/\d{2}\/\d{4}\s\d{2}:\d{2}$/', $dateHeureDDMMYYYY_HHMM)) {
|
||||
throw new \InvalidArgumentException('Format date/heure invalide. Attendu: "01/01/2025 12:00"');
|
||||
}
|
||||
|
||||
// Vérifie le compte de variables attendu dans le BODY
|
||||
$expected = (int)($tpl['components']['body_params'] ?? 0);
|
||||
$actual = 2;
|
||||
if ($expected !== $actual) {
|
||||
throw new \RuntimeException("Le template {$tplName} attend {$expected} paramètres BODY, {$actual} fournis.");
|
||||
}
|
||||
|
||||
$lang = $tpl['language'] ?: ($cfg['default_lang'] ?? 'ru');
|
||||
|
||||
$client = new WaClient($cfg);
|
||||
// {{1}} = prénom nom ; {{2}} = date-heure
|
||||
return $client->sendTemplate($tplName, $lang, $to, [$prenomNom, $dateHeureDDMMYYYY_HHMM]);
|
||||
}
|
||||
function notify_rdv_en_attente(string $to, string $prenomNom, string $dateHeureDDMMYYYY_HHMM, int $rdvId): array
|
||||
{
|
||||
// Config & templates
|
||||
$cfg = is_file(__DIR__.'/config.php') ? require __DIR__.'/config.php' : [
|
||||
'phone_number_id' => getenv('WHATSAPP_PHONE_NUMBER_ID') ?: '',
|
||||
'access_token' => getenv('WHATSAPP_TOKEN') ?: '',
|
||||
'default_lang' => 'ru',
|
||||
'timeout' => 20,
|
||||
];
|
||||
if (($cfg['phone_number_id'] ?? '') === '' || ($cfg['access_token'] ?? '') === '') {
|
||||
throw new \RuntimeException("Config WhatsApp manquante: crée /sms/config.php ou définis les variables d'environnement.");
|
||||
}
|
||||
|
||||
$templates = require __DIR__ . '/templates.php';
|
||||
$tplName = 'en_attente_confirmation';
|
||||
$tpl = $templates[$tplName] ?? null;
|
||||
if (!$tpl) {
|
||||
throw new \RuntimeException("Template {$tplName} non déclaré dans /sms/templates.php");
|
||||
}
|
||||
|
||||
// Valide date/heure "01/01/2025 12:00"
|
||||
if (!preg_match('/^\d{2}\/\d{2}\/\d{4}\s\d{2}:\d{2}$/', $dateHeureDDMMYYYY_HHMM)) {
|
||||
throw new \InvalidArgumentException('Format date/heure invalide. Attendu: "01/01/2025 12:00"');
|
||||
}
|
||||
|
||||
// Vérifie compte de variables attendu
|
||||
$expectedBody = (int)($tpl['components']['body_params'] ?? 0);
|
||||
if ($expectedBody !== 2) {
|
||||
throw new \RuntimeException("Le template {$tplName} attend {$expectedBody} paramètres BODY, 2 fournis.");
|
||||
}
|
||||
|
||||
// Bouton URL index "0" avec 1 paramètre ({{1}})
|
||||
$btnCfg = $tpl['components']['button_url_params']['0'] ?? 0;
|
||||
if ((int)$btnCfg !== 1) {
|
||||
throw new \RuntimeException("Le bouton URL index 0 du template {$tplName} doit attendre 1 paramètre.");
|
||||
}
|
||||
|
||||
$lang = $tpl['language'] ?: ($cfg['default_lang'] ?? 'ru');
|
||||
$client = new WaClient($cfg);
|
||||
|
||||
// BODY: {{1}} prénomNom, {{2}} dateHeure ; BUTTON index "0": {{1}} = rdvId
|
||||
return $client->sendTemplate(
|
||||
$tplName,
|
||||
$lang,
|
||||
$to,
|
||||
[$prenomNom, $dateHeureDDMMYYYY_HHMM],
|
||||
['0' => [(string)$rdvId]]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
// /sms/templates.php
|
||||
return [
|
||||
// Notification Utility pour rendez-vous
|
||||
'notification_rdv' => [
|
||||
'language' => 'en', // ou 'fr' si tu crées une version FR
|
||||
'components' => [
|
||||
// BODY a exactement 2 variables: {{1}} = Nom client, {{2}} = DateHeure
|
||||
'body_params' => 2,
|
||||
// Si un jour tu ajoutes un HEADER ou un BUTTON avec variables, ajoute 'header_params' / 'button_url_params' etc.
|
||||
],
|
||||
],
|
||||
|
||||
// Nouveau template avec bouton URL
|
||||
'en_attente_confirmation' => [
|
||||
'language' => 'ru', // adapte si tu as une locale différente
|
||||
'components' => [
|
||||
'body_params' => 2, // {{1}} nom client, {{2}} date/heure
|
||||
'button_url_params' => ['0' => 1] // bouton index "0" a 1 placeholder {{1}} (l’ID du RDV)
|
||||
],
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
// /sms/wa_client.php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sms;
|
||||
|
||||
final class WaClient
|
||||
{
|
||||
private string $phoneId;
|
||||
private string $token;
|
||||
private int $timeout;
|
||||
|
||||
public function __construct(array $cfg)
|
||||
{
|
||||
$this->phoneId = $cfg['phone_number_id'] ?? '';
|
||||
$this->token = $cfg['access_token'] ?? '';
|
||||
$this->timeout = (int)($cfg['timeout'] ?? 20);
|
||||
|
||||
if ($this->phoneId === '' || $this->token === '') {
|
||||
throw new \RuntimeException('Config WhatsApp incomplète: phone_number_id / access_token manquants.');
|
||||
}
|
||||
}
|
||||
|
||||
/** Normalise en format international chiffres-only (WhatsApp: pas de "+") */
|
||||
public static function normalizePhone(string $raw): string
|
||||
{
|
||||
$n = preg_replace('/\D+/', '', $raw ?? '');
|
||||
// Ex: numéro FR commençant par 0 -> remplace 0 initial par 33
|
||||
if (preg_match('/^0\d{9}$/', $n)) {
|
||||
$n = '33' . substr($n, 1);
|
||||
}
|
||||
return $n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Envoi d’un template Utility.
|
||||
* $templateName: ex. 'notification_rdv'
|
||||
* $lang: 'ru' (ou celui déclaré/validé chez Meta)
|
||||
* $to: numéro destinataire (06..., +33..., 336..., etc.)
|
||||
* $bodyParams: tableau des valeurs pour {{1}}, {{2}}, ...
|
||||
*/
|
||||
public function sendTemplate(string $templateName, string $lang, string $to, array $bodyParams, ?array $buttonUrlParams = null): array
|
||||
{
|
||||
$toNorm = self::normalizePhone($to);
|
||||
if ($toNorm === '' || strlen($toNorm) < 8) {
|
||||
throw new \InvalidArgumentException('Numéro destinataire invalide.');
|
||||
}
|
||||
|
||||
// BODY params
|
||||
$components = [[
|
||||
'type' => 'body',
|
||||
'parameters' => array_map(fn($t) => ['type'=>'text','text'=>(string)$t], $bodyParams)
|
||||
]];
|
||||
|
||||
// BUTTON URL params (facultatif)
|
||||
// $buttonUrlParams attendu sous la forme: ['0' => ['123']] // index "0" => [ valeurs pour {{1}}, {{2}} si existait ]
|
||||
if ($buttonUrlParams) {
|
||||
foreach ($buttonUrlParams as $index => $values) {
|
||||
$params = array_map(fn($t) => ['type'=>'text','text'=>(string)$t], (array)$values);
|
||||
$components[] = [
|
||||
'type' => 'button',
|
||||
'sub_type' => 'url',
|
||||
'index' => (string)$index,
|
||||
'parameters' => $params,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$payload = [
|
||||
'messaging_product' => 'whatsapp',
|
||||
'to' => $toNorm,
|
||||
'type' => 'template',
|
||||
'template' => [
|
||||
'name' => $templateName,
|
||||
'language' => ['code' => $lang],
|
||||
'components' => $components,
|
||||
]
|
||||
];
|
||||
|
||||
$json = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
|
||||
$ch = curl_init("https://graph.facebook.com/v23.0/{$this->phoneId}/messages");
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_HTTPHEADER => [
|
||||
"Authorization: Bearer {$this->token}",
|
||||
"Content-Type: application/json",
|
||||
"Accept: application/json",
|
||||
"Expect:",
|
||||
"Connection: close",
|
||||
],
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => $json,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => $this->timeout,
|
||||
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
|
||||
CURLOPT_SSL_VERIFYPEER => true,
|
||||
CURLOPT_SSL_VERIFYHOST => 2,
|
||||
]);
|
||||
|
||||
$resp = curl_exec($ch);
|
||||
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$err = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
$data = json_decode($resp ?? '', true);
|
||||
|
||||
return [
|
||||
'http_code' => $code,
|
||||
'error' => $err ?: null,
|
||||
'response' => $data ?? $resp,
|
||||
'payload' => $payload,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user