Connexion tocken, stripe 3D, tracking analytic
This commit is contained in:
1 parent
1deb30fcd1
commit
bd311da711
17 files changed
+3324
-455
No files matched your search
+98
-66
@@ -5,62 +5,69 @@
|
||||
$mot_de_passe = '';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$email = trim($_POST['email'] ?? '');
|
||||
$mot_de_passe = $_POST['mot_de_passe'] ?? '';
|
||||
$email = trim($_POST['email'] ?? '');
|
||||
$mot_de_passe = $_POST['mot_de_passe'] ?? '';
|
||||
|
||||
if ($email === '' || $mot_de_passe === '') {
|
||||
$erreur = $translations['erreur_champs_vides'];
|
||||
} else {
|
||||
// 1) On vérifie l’utilisateur
|
||||
$stmt = $pdo->prepare("SELECT * FROM compte WHERE email = :email LIMIT 1");
|
||||
$stmt->execute(['email' => $email]);
|
||||
$compte = $stmt->fetch();
|
||||
if ($email === '' || $mot_de_passe === '') {
|
||||
$erreur = $translations['erreur_champs_vides'];
|
||||
} else {
|
||||
// 1) On vérifie l’utilisateur
|
||||
$stmt = $pdo->prepare("SELECT * FROM compte WHERE email = :email LIMIT 1");
|
||||
$stmt->execute(['email' => $email]);
|
||||
$compte = $stmt->fetch();
|
||||
|
||||
if ($compte && password_verify($mot_de_passe, $compte['mot_de_passe'])) {
|
||||
if (empty($compte['email_valide'])) {
|
||||
$erreur = $translations['erreur_email_non_valide'];
|
||||
} else {
|
||||
session_regenerate_id(true); // ✅ anti session fixation
|
||||
// 2) Connexion OK → on initialise la session
|
||||
$_SESSION['compte_id'] = $compte['id'];
|
||||
$_SESSION['role'] = strtolower(trim($compte['role'] ?? ''));
|
||||
$_SESSION['reference_id'] = $compte['reference_id'];
|
||||
$_SESSION['email'] = $compte['email'];
|
||||
$_SESSION['last_activity'] = time();
|
||||
if ($compte && password_verify($mot_de_passe, $compte['mot_de_passe'])) {
|
||||
if (empty($compte['email_valide'])) {
|
||||
$_SESSION['pending_validation_resend'] = [
|
||||
'compte_id' => (int) $compte['id'],
|
||||
'email' => (string) $compte['email'],
|
||||
'authorized' => true,
|
||||
'expires_at' => time() + 900, // 15 min
|
||||
];
|
||||
|
||||
// 3) On gère la redirection prioritaire vers la page demandée
|
||||
$next = $_POST['next'] ?? $_GET['next'] ?? ($_SESSION['redirect_to'] ?? null);
|
||||
$next = next_safe($next);
|
||||
if ($next) {
|
||||
unset($_SESSION['redirect_to']);
|
||||
header('Location: ' . $next);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 4) Sinon, fallback par rôle
|
||||
if ($_SESSION['role'] === 'client') {
|
||||
header('Location: /public/client/mes_rendez_vous.php');exit;
|
||||
} elseif ($_SESSION['role'] === 'admin') {
|
||||
header('Location: /admin/index.php');exit;
|
||||
} elseif ($_SESSION['role'] === 'secretaire') {
|
||||
header('Location: /public/cabinet/dashboard.php');exit;
|
||||
} else {
|
||||
header('Location: /public/praticien/agenda.php');exit;
|
||||
}
|
||||
}
|
||||
$erreur = $translations['tvalidat_erreur_email_non_valide'] ?? $translations['erreur_email_non_valide'] ?? 'Veuillez valider votre adresse email avant de vous connecter.';
|
||||
} else {
|
||||
$erreur = $translations['erreur_identifiants'];
|
||||
session_regenerate_id(true); // ✅ anti session fixation
|
||||
// 2) Connexion OK → on initialise la session
|
||||
$_SESSION['compte_id'] = $compte['id'];
|
||||
$_SESSION['role'] = strtolower(trim($compte['role'] ?? ''));
|
||||
$_SESSION['reference_id'] = $compte['reference_id'];
|
||||
$_SESSION['email'] = $compte['email'];
|
||||
$_SESSION['last_activity'] = time();
|
||||
|
||||
// 3) On gère la redirection prioritaire vers la page demandée
|
||||
$next = $_POST['next'] ?? $_GET['next'] ?? ($_SESSION['redirect_to'] ?? null);
|
||||
$next = next_safe($next);
|
||||
if ($next) {
|
||||
unset($_SESSION['redirect_to']);
|
||||
header('Location: ' . $next);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 4) Sinon, fallback par rôle
|
||||
if ($_SESSION['role'] === 'client') {
|
||||
header('Location: /public/client/mes_rendez_vous.php');exit;
|
||||
} elseif ($_SESSION['role'] === 'admin') {
|
||||
header('Location: /admin/index.php');exit;
|
||||
} elseif ($_SESSION['role'] === 'secretaire') {
|
||||
header('Location: /public/cabinet/dashboard.php');exit;
|
||||
} else {
|
||||
header('Location: /public/praticien/agenda.php');exit;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$erreur = $translations['erreur_identifiants'];
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title><?php echo htmlspecialchars($translations['seo_connexion_title'], ENT_QUOTES, 'UTF-8')?></title>
|
||||
<meta name="description" content="<?php echo htmlspecialchars($translations['seo_connexion_desc'], ENT_QUOTES, 'UTF-8')?>">
|
||||
<meta name="keywords" content="<?php echo htmlspecialchars($translations['seo_connexion_keywords'], ENT_QUOTES, 'UTF-8')?>">
|
||||
<title><?php echo htmlspecialchars($translations['seo_connexion_title'], ENT_QUOTES, 'UTF-8') ?></title>
|
||||
<meta name="description" content="<?php echo htmlspecialchars($translations['seo_connexion_desc'], ENT_QUOTES, 'UTF-8') ?>">
|
||||
<meta name="keywords" content="<?php echo htmlspecialchars($translations['seo_connexion_keywords'], ENT_QUOTES, 'UTF-8') ?>">
|
||||
<meta name="author" content="Uslugi.fr">
|
||||
|
||||
|
||||
@@ -68,10 +75,10 @@
|
||||
<meta name="robots" content="noindex, nofollow, noarchive, nosnippet">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="/css/bootstrap.min.css">
|
||||
|
||||
<link rel="stylesheet" href="<?= asset('/includes/site_style.css') ?>">
|
||||
<link rel="stylesheet" href="<?= asset('/includes/custom_style.css') ?>">
|
||||
<link rel="stylesheet" href="<?= asset('/includes/mobil_style.css') ?>">
|
||||
|
||||
<link rel="stylesheet" href="<?php echo asset('/includes/site_style.css') ?>">
|
||||
<link rel="stylesheet" href="<?php echo asset('/includes/custom_style.css') ?>">
|
||||
<link rel="stylesheet" href="<?php echo asset('/includes/mobil_style.css') ?>">
|
||||
</head>
|
||||
<body>
|
||||
<header class="site-header">
|
||||
@@ -96,10 +103,10 @@
|
||||
type="button" data-bs-toggle="dropdown" data-bs-auto-close="true"
|
||||
aria-expanded="false" aria-label="Помощь">?</button>
|
||||
<ul class="dropdown-menu dropdown-menu-end shadow help-menu menu-panel">
|
||||
<li><a href="/how-it-work.php"><?= $translations['kak_rabotaet'] ?></a></a></li>
|
||||
<li><a href="/about.php"><?= $translations['pro_nas'] ?></a></li>
|
||||
<li><a href="/faq.php"><?= $translations['faq'] ?></a></li>
|
||||
<li><a href="/contact.php"><?= $translations['nam_pisat'] ?></a></li>
|
||||
<li><a href="/how-it-work.php"><?php echo $translations['kak_rabotaet'] ?></a></a></li>
|
||||
<li><a href="/about.php"><?php echo $translations['pro_nas'] ?></a></li>
|
||||
<li><a href="/faq.php"><?php echo $translations['faq'] ?></a></li>
|
||||
<li><a href="/contact.php"><?php echo $translations['nam_pisat'] ?></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -107,31 +114,56 @@
|
||||
</header>
|
||||
|
||||
<div class="main">
|
||||
|
||||
|
||||
<div class="card shadow-sm p-4" style="min-width:380px; max-width:520px; margin:40px auto;">
|
||||
<h3 class="mb-4 text-center text-dark"><?php echo $translations['connexion_titre']?></h3>
|
||||
<h3 class="mb-4 text-center text-dark"><?php echo $translations['connexion_titre'] ?></h3>
|
||||
|
||||
<?php if ($erreur): ?>
|
||||
<div class="alert alert-danger py-2"><?php echo htmlspecialchars($erreur)?></div>
|
||||
<?php
|
||||
$canResend = ! empty($_SESSION['pending_validation_resend']['authorized'])
|
||||
&& ! empty($_SESSION['pending_validation_resend']['expires_at'])
|
||||
&& (int) $_SESSION['pending_validation_resend']['expires_at'] >= time()
|
||||
&& ! empty($email)
|
||||
&& ! empty($_SESSION['pending_validation_resend']['email'])
|
||||
&& hash_equals(
|
||||
(string) $_SESSION['pending_validation_resend']['email'],
|
||||
(string) $email
|
||||
);
|
||||
?>
|
||||
<div class="alert alert-danger py-2 text-center" role="alert">
|
||||
<?php echo htmlspecialchars($erreur, ENT_QUOTES, 'UTF-8'); ?>
|
||||
|
||||
<?php if ($canResend): ?>
|
||||
<div class="mt-3">
|
||||
<form method="POST" action="/renvoyer_validat_mail.php" class="m-0">
|
||||
<input type="hidden" name="email"
|
||||
value="<?php echo htmlspecialchars($email, ENT_QUOTES, 'UTF-8'); ?>">
|
||||
<button type="submit" class="btn btn-light btn-sm">
|
||||
<?php echo htmlspecialchars($translations['tvalidat_btn_renvoyer'] ?? 'Renvoyer le mail de validation', ENT_QUOTES, 'UTF-8'); ?>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="POST">
|
||||
<div class="mb-3">
|
||||
<input type="email" name="email" class="form-control"
|
||||
placeholder="<?php echo $translations['email_placeholder']?>"
|
||||
value="<?php echo htmlspecialchars($email)?>" required>
|
||||
placeholder="<?php echo $translations['email_placeholder'] ?>"
|
||||
value="<?php echo htmlspecialchars($email) ?>" required>
|
||||
</div>
|
||||
<div class="mb-3 position-relative">
|
||||
<input id="mot_de_passe" type="password" name="mot_de_passe" class="form-control"
|
||||
placeholder="<?php echo $translations['motdepasse_placeholder']?>"
|
||||
placeholder="<?php echo $translations['motdepasse_placeholder'] ?>"
|
||||
required autocomplete="current-password">
|
||||
<span class="toggle-password"
|
||||
id="eyePwd"
|
||||
onmouseenter="revealPwd('mot_de_passe')"
|
||||
onmouseleave="hidePwd('mot_de_passe')"
|
||||
onmousedown="revealPwd('mot_de_passe')"
|
||||
onmousedown="revealPwd('mot_de_passe')"
|
||||
onmouseup="hidePwd('mot_de_passe')"
|
||||
ontouchstart="revealPwd('mot_de_passe')"
|
||||
ontouchstart="revealPwd('mot_de_passe')"
|
||||
ontouchend="hidePwd('mot_de_passe')"
|
||||
title="Наведите, чтобы показать">
|
||||
👁️
|
||||
@@ -140,20 +172,20 @@
|
||||
|
||||
<!-- on propage le "next" -->
|
||||
<input type="hidden" name="next"
|
||||
value="<?php echo htmlspecialchars($_GET['next'] ?? ($_SESSION['redirect_to'] ?? ''), ENT_QUOTES)?>">
|
||||
value="<?php echo htmlspecialchars($_GET['next'] ?? ($_SESSION['redirect_to'] ?? ''), ENT_QUOTES) ?>">
|
||||
|
||||
<button type="submit" class="btn btn-primary w-100"><?php echo $translations['btn_se_connecter']?></button>
|
||||
<button type="submit" class="btn btn-primary w-100"><?php echo $translations['btn_se_connecter'] ?></button>
|
||||
<div class="text-center mt-3">
|
||||
<a href="mdp_oublie.php" class="me-2"><?php echo $translations['lien_mdp_oublie']?></a> |
|
||||
<a href="choix_inscr.php" class="ms-2"><?php echo $translations['lien_inscription']?></a>
|
||||
<a href="mdp_oublie.php" class="me-2"><?php echo $translations['lien_mdp_oublie'] ?></a> |
|
||||
<a href="choix_inscr.php" class="ms-2"><?php echo $translations['lien_inscription'] ?></a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<footer class="site-footer">
|
||||
<div class="container">
|
||||
<p><?php echo $translations['copyr']?></p>
|
||||
<p><?php echo $translations['copyr'] ?></p>
|
||||
</div>
|
||||
</footer>
|
||||
<script>
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/public/includes/init_auth.php';
|
||||
$erreur = '';
|
||||
$email = '';
|
||||
$mot_de_passe = '';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$email = trim($_POST['email'] ?? '');
|
||||
$mot_de_passe = $_POST['mot_de_passe'] ?? '';
|
||||
|
||||
if ($email === '' || $mot_de_passe === '') {
|
||||
$erreur = $translations['erreur_champs_vides'];
|
||||
} else {
|
||||
// 1) On vérifie l’utilisateur
|
||||
$stmt = $pdo->prepare("SELECT * FROM compte WHERE email = :email LIMIT 1");
|
||||
$stmt->execute(['email' => $email]);
|
||||
$compte = $stmt->fetch();
|
||||
|
||||
if ($compte && password_verify($mot_de_passe, $compte['mot_de_passe'])) {
|
||||
if (empty($compte['email_valide'])) {
|
||||
$erreur = $translations['erreur_email_non_valide'];
|
||||
} else {
|
||||
session_regenerate_id(true); // ✅ anti session fixation
|
||||
// 2) Connexion OK → on initialise la session
|
||||
$_SESSION['compte_id'] = $compte['id'];
|
||||
$_SESSION['role'] = strtolower(trim($compte['role'] ?? ''));
|
||||
$_SESSION['reference_id'] = $compte['reference_id'];
|
||||
$_SESSION['email'] = $compte['email'];
|
||||
$_SESSION['last_activity'] = time();
|
||||
|
||||
// 3) On gère la redirection prioritaire vers la page demandée
|
||||
$next = $_POST['next'] ?? $_GET['next'] ?? ($_SESSION['redirect_to'] ?? null);
|
||||
$next = next_safe($next);
|
||||
if ($next) {
|
||||
unset($_SESSION['redirect_to']);
|
||||
header('Location: ' . $next);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 4) Sinon, fallback par rôle
|
||||
if ($_SESSION['role'] === 'client') {
|
||||
header('Location: /public/client/mes_rendez_vous.php');exit;
|
||||
} elseif ($_SESSION['role'] === 'admin') {
|
||||
header('Location: /admin/index.php');exit;
|
||||
} elseif ($_SESSION['role'] === 'secretaire') {
|
||||
header('Location: /public/cabinet/dashboard.php');exit;
|
||||
} else {
|
||||
header('Location: /public/praticien/agenda.php');exit;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$erreur = $translations['erreur_identifiants'];
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title><?php echo htmlspecialchars($translations['seo_connexion_title'], ENT_QUOTES, 'UTF-8')?></title>
|
||||
<meta name="description" content="<?php echo htmlspecialchars($translations['seo_connexion_desc'], ENT_QUOTES, 'UTF-8')?>">
|
||||
<meta name="keywords" content="<?php echo htmlspecialchars($translations['seo_connexion_keywords'], ENT_QUOTES, 'UTF-8')?>">
|
||||
<meta name="author" content="Uslugi.fr">
|
||||
|
||||
|
||||
<!-- Recommandé pour une page de login -->
|
||||
<meta name="robots" content="noindex, nofollow, noarchive, nosnippet">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="/css/bootstrap.min.css">
|
||||
|
||||
<link rel="stylesheet" href="<?= asset('/includes/site_style.css') ?>">
|
||||
<link rel="stylesheet" href="<?= asset('/includes/custom_style.css') ?>">
|
||||
<link rel="stylesheet" href="<?= asset('/includes/mobil_style.css') ?>">
|
||||
</head>
|
||||
<body>
|
||||
<header class="site-header">
|
||||
<div class="container">
|
||||
<div class="header-left">
|
||||
<div class="brand">
|
||||
<a href="/index.php"><img src="/public/uploads/img/logo_u.png"
|
||||
alt="uslugi.fr"
|
||||
class="logo-mobile"></a>
|
||||
|
||||
<a href="/index.php"><img src="/public/uploads/img/logo.png"
|
||||
alt="uslugi.fr"
|
||||
class="logo-desktop"></a>
|
||||
</div>
|
||||
<div class="lang-switch">
|
||||
<?php require __DIR__ . '/includes/menu_langue.php'; ?>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="header-actions">
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-logo rounded-circle d-inline-flex align-items-center justify-content-center question-btn"
|
||||
type="button" data-bs-toggle="dropdown" data-bs-auto-close="true"
|
||||
aria-expanded="false" aria-label="Помощь">?</button>
|
||||
<ul class="dropdown-menu dropdown-menu-end shadow help-menu menu-panel">
|
||||
<li><a href="/how-it-work.php"><?= $translations['kak_rabotaet'] ?></a></a></li>
|
||||
<li><a href="/about.php"><?= $translations['pro_nas'] ?></a></li>
|
||||
<li><a href="/faq.php"><?= $translations['faq'] ?></a></li>
|
||||
<li><a href="/contact.php"><?= $translations['nam_pisat'] ?></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="main">
|
||||
|
||||
<div class="card shadow-sm p-4" style="min-width:380px; max-width:520px; margin:40px auto;">
|
||||
<h3 class="mb-4 text-center text-dark"><?php echo $translations['connexion_titre']?></h3>
|
||||
|
||||
<?php if ($erreur): ?>
|
||||
<div class="alert alert-danger py-2"><?php echo htmlspecialchars($erreur)?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="POST">
|
||||
<div class="mb-3">
|
||||
<input type="email" name="email" class="form-control"
|
||||
placeholder="<?php echo $translations['email_placeholder']?>"
|
||||
value="<?php echo htmlspecialchars($email)?>" required>
|
||||
</div>
|
||||
<div class="mb-3 position-relative">
|
||||
<input id="mot_de_passe" type="password" name="mot_de_passe" class="form-control"
|
||||
placeholder="<?php echo $translations['motdepasse_placeholder']?>"
|
||||
required autocomplete="current-password">
|
||||
<span class="toggle-password"
|
||||
id="eyePwd"
|
||||
onmouseenter="revealPwd('mot_de_passe')"
|
||||
onmouseleave="hidePwd('mot_de_passe')"
|
||||
onmousedown="revealPwd('mot_de_passe')"
|
||||
onmouseup="hidePwd('mot_de_passe')"
|
||||
ontouchstart="revealPwd('mot_de_passe')"
|
||||
ontouchend="hidePwd('mot_de_passe')"
|
||||
title="Наведите, чтобы показать">
|
||||
👁️
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- on propage le "next" -->
|
||||
<input type="hidden" name="next"
|
||||
value="<?php echo htmlspecialchars($_GET['next'] ?? ($_SESSION['redirect_to'] ?? ''), ENT_QUOTES)?>">
|
||||
|
||||
<button type="submit" class="btn btn-primary w-100"><?php echo $translations['btn_se_connecter']?></button>
|
||||
<div class="text-center mt-3">
|
||||
<a href="mdp_oublie.php" class="me-2"><?php echo $translations['lien_mdp_oublie']?></a> |
|
||||
<a href="choix_inscr.php" class="ms-2"><?php echo $translations['lien_inscription']?></a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="site-footer">
|
||||
<div class="container">
|
||||
<p><?php echo $translations['copyr']?></p>
|
||||
</div>
|
||||
</footer>
|
||||
<script>
|
||||
function revealPwd(id){ var el = document.getElementById(id); if(el) el.type = 'text'; }
|
||||
function hidePwd(id){ var el = document.getElementById(id); if(el) el.type = 'password'; }
|
||||
(function(){
|
||||
var eye = document.getElementById('eyePwd');
|
||||
var input = document.getElementById('mot_de_passe');
|
||||
if(!eye || !input) return;
|
||||
|
||||
// Mobile (iOS/Android) — empêche sélection/callout et révèle/masque
|
||||
eye.addEventListener('touchstart', function(e){ e.preventDefault(); revealPwd('mot_de_passe'); }, {passive:false});
|
||||
eye.addEventListener('touchend', function(e){ e.preventDefault(); hidePwd('mot_de_passe'); }, {passive:false});
|
||||
eye.addEventListener('touchcancel',function(){ hidePwd('mot_de_passe'); });
|
||||
|
||||
// Desktop / trackpad — empêche sélection au maintien
|
||||
eye.addEventListener('mousedown', function(e){ e.preventDefault(); revealPwd('mot_de_passe'); });
|
||||
eye.addEventListener('mouseup', function(e){ e.preventDefault(); hidePwd('mot_de_passe'); });
|
||||
eye.addEventListener('mouseleave',function(){ hidePwd('mot_de_passe'); });
|
||||
})();
|
||||
</script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
<?php
|
||||
// htdocs/includes/accepter_cookies.php — CookieConsent v3 (correctif)
|
||||
// Si tu as des traductions dans lang/fr.php / lang/ru.php, on les lit (optionnel)
|
||||
$lang = $_SESSION['lang'] ?? 'fr';
|
||||
$langFile = __DIR__ . "/../lang/{$lang}.php";
|
||||
if (is_file($langFile)) { include $langFile; }
|
||||
$t = isset($translations) && is_array($translations) ? $translations : [];
|
||||
function tt($k,$d=''){ global $t; return htmlspecialchars($t[$k]??$d, ENT_QUOTES, 'UTF-8'); }
|
||||
// htdocs/includes/accepter_cookies.php — CookieConsent v3 (correctif)
|
||||
// Si tu as des traductions dans lang/fr.php / lang/ru.php, on les lit (optionnel)
|
||||
$lang = $_SESSION['lang'] ?? 'fr';
|
||||
$langFile = __DIR__ . "/../lang/{$lang}.php";
|
||||
if (is_file($langFile)) {include $langFile;}
|
||||
$t = isset($translations) && is_array($translations) ? $translations : [];
|
||||
function tt($k, $d = '')
|
||||
{global $t;return htmlspecialchars($t[$k] ?? $d, ENT_QUOTES, 'UTF-8');}
|
||||
?>
|
||||
<!--<a id="cc-manage" href="#" style="display:none"><?= tt('cookies_manage_link','Paramètres cookies') ?></a>-->
|
||||
<!--<a id="cc-manage" href="#" style="display:none"><?php echo tt('cookies_manage_link', 'Paramètres cookies') ?></a>-->
|
||||
|
||||
<link id="cc-css" rel="stylesheet" href="/js/cookieconsent.css"
|
||||
onerror="this.href='https://cdn.jsdelivr.net/gh/orestbida/cookieconsent@3.1.0/dist/cookieconsent.css'">
|
||||
@@ -16,15 +17,15 @@ function tt($k,$d=''){ global $t; return htmlspecialchars($t[$k]??$d, ENT_QUOTES
|
||||
(function(){
|
||||
// Textes (prend ceux de tes fichiers de langue si présents)
|
||||
const TXT = {
|
||||
title: <?= json_encode($t['cookies_title'] ?? 'Cookies & confidentialité') ?>,
|
||||
desc: <?= json_encode($t['cookies_desc'] ?? "Nous utilisons des cookies essentiels. Avec votre accord, nous activons aussi l'analyse et la personnalisation.") ?>,
|
||||
acceptAll: <?= json_encode($t['cookies_accept_all'] ?? 'Tout accepter') ?>,
|
||||
rejectAll: <?= json_encode($t['cookies_reject_all'] ?? 'Tout refuser') ?>,
|
||||
settings: <?= json_encode($t['cookies_settings'] ?? 'Personnaliser') ?>,
|
||||
necessary: <?= json_encode($t['cookies_necessary'] ?? 'Essentiels (toujours actifs)') ?>,
|
||||
analytics: <?= json_encode($t['cookies_analytics'] ?? 'Mesure d’audience') ?>,
|
||||
personalization: <?= json_encode($t['cookies_personalization'] ?? 'Personnalisation') ?>,
|
||||
save: <?= json_encode($t['cookies_save'] ?? 'Enregistrer') ?>,
|
||||
title: <?php echo json_encode($t['cookies_title'] ?? 'Cookies & confidentialité') ?>,
|
||||
desc: <?php echo json_encode($t['cookies_desc'] ?? "Nous utilisons des cookies essentiels. Avec votre accord, nous activons aussi l'analyse et la personnalisation.") ?>,
|
||||
acceptAll: <?php echo json_encode($t['cookies_accept_all'] ?? 'Tout accepter') ?>,
|
||||
rejectAll: <?php echo json_encode($t['cookies_reject_all'] ?? 'Tout refuser') ?>,
|
||||
settings: <?php echo json_encode($t['cookies_settings'] ?? 'Personnaliser') ?>,
|
||||
necessary: <?php echo json_encode($t['cookies_necessary'] ?? 'Essentiels (toujours actifs)') ?>,
|
||||
analytics: <?php echo json_encode($t['cookies_analytics'] ?? 'Mesure d’audience') ?>,
|
||||
personalization: <?php echo json_encode($t['cookies_personalization'] ?? 'Personnalisation') ?>,
|
||||
save: <?php echo json_encode($t['cookies_save'] ?? 'Enregistrer') ?>,
|
||||
};
|
||||
|
||||
const SRCS = [
|
||||
@@ -96,14 +97,8 @@ function tt($k,$d=''){ global $t; return htmlspecialchars($t[$k]??$d, ENT_QUOTES
|
||||
},
|
||||
|
||||
// Hooks
|
||||
onConsent: ({cookie}) => {
|
||||
if (CookieConsent.acceptedCategory('analytics')) enableAnalytics();
|
||||
},
|
||||
onChange: ({changedCategories}) => {
|
||||
if (changedCategories.includes('analytics')) {
|
||||
CookieConsent.acceptedCategory('analytics') ? enableAnalytics() : disableAnalytics();
|
||||
}
|
||||
}
|
||||
onConsent: ({cookie}) => {},
|
||||
onChange: ({changedCategories}) => {}
|
||||
});
|
||||
|
||||
// Lien pour rouvrir
|
||||
@@ -115,16 +110,6 @@ function tt($k,$d=''){ global $t; return htmlspecialchars($t[$k]??$d, ENT_QUOTES
|
||||
|
||||
}
|
||||
|
||||
// Tes hooks perso (ex: activer GA seulement si analytics accepté)
|
||||
window.enableAnalytics = function(){
|
||||
if (window.__gaLoaded) return; window.__gaLoaded = true;
|
||||
// Exemple:
|
||||
CookieConsent.loadScript('https://www.googletagmanager.com/gtag/js?id=G-XXXX').then(()=>{
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments)}; gtag('js', new Date()); gtag('config','G-XXXX');
|
||||
});
|
||||
};
|
||||
window.disableAnalytics = function(){ /* éventuellement reset */ };
|
||||
|
||||
// Lance le chargement (local → CDN fallback)
|
||||
loadScriptSequential(0);
|
||||
|
||||
@@ -141,6 +141,7 @@
|
||||
<link rel="stylesheet" href="<?php echo h(asset('/includes/custom_style.css')) ?>">
|
||||
<link rel="stylesheet" href="<?php echo h(asset('/includes/index_style.css')) ?>">
|
||||
<link rel="stylesheet" href="<?php echo h(asset('/includes/mobil_style.css')) ?>">
|
||||
<?php include_once __DIR__ . '/includes/tracking.php'; ?>
|
||||
<style>
|
||||
.site-header
|
||||
{
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
(function () {
|
||||
// --- helpers ---
|
||||
function loadScript(src) {
|
||||
var s = document.createElement("script");
|
||||
s.async = true;
|
||||
s.src = src;
|
||||
(document.head || document.documentElement).appendChild(s);
|
||||
}
|
||||
function consentOk() {
|
||||
try {
|
||||
// Si tu utilises vanilla-cookieconsent v3
|
||||
return window.CookieConsent &&
|
||||
typeof CookieConsent.acceptedCategory === "function"
|
||||
? CookieConsent.acceptedCategory("analytics")
|
||||
: true; // pas de CMP → on trace par défaut (change à false si tu veux l’inverse)
|
||||
} catch (e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Matomo ---
|
||||
function initMatomo() {
|
||||
if (window.__MATOMO_LOADED__) return;
|
||||
window.__MATOMO_LOADED__ = true;
|
||||
window._paq = window._paq || [];
|
||||
_paq.push(["trackPageView"]);
|
||||
_paq.push(["enableLinkTracking"]);
|
||||
var u = "https://staging.uslugi.fr/matomo/";
|
||||
_paq.push(["setTrackerUrl", u + "matomo.php"]);
|
||||
_paq.push(["setSiteId", "1"]);
|
||||
loadScript(u + "matomo.js"); // charge le tracker depuis ton staging (autorise ce domaine dans la CSP)
|
||||
}
|
||||
|
||||
// --- Google Analytics (facultatif) ---
|
||||
function initGA() {
|
||||
if (window.__GA_READY__) return;
|
||||
window.__GA_READY__ = true;
|
||||
loadScript("https://www.googletagmanager.com/gtag/js?id=G-RXS9TX1JYD");
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag() {
|
||||
dataLayer.push(arguments);
|
||||
}
|
||||
window.gtag = gtag;
|
||||
gtag("js", new Date());
|
||||
gtag("config", "G-RXS9TX1JYD", { anonymize_ip: true });
|
||||
}
|
||||
|
||||
// --- start when allowed ---
|
||||
function start() {
|
||||
if (!consentOk()) return;
|
||||
initMatomo();
|
||||
initGA(); // décommente si tu veux activer GA aussi
|
||||
}
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", start);
|
||||
} else {
|
||||
start();
|
||||
}
|
||||
|
||||
// Si la bannière change le consentement à chaud
|
||||
if (window.CookieConsent && typeof CookieConsent.onChange === "function") {
|
||||
CookieConsent.onChange(function ({ changedCategories }) {
|
||||
if (changedCategories.includes("analytics") && consentOk()) start();
|
||||
});
|
||||
}
|
||||
})();
|
||||
+54
-42
@@ -644,36 +644,13 @@ if (in_array($type, $checkoutEvents, true) && $obj) {
|
||||
'last_event_id' => $eventId,
|
||||
]);
|
||||
|
||||
// 1) Aligner praticien (abonnement_id + date_expiration)
|
||||
if ($praticien_id && $abonnement_id && $current_period_end && colExists($pdo, 'praticien', 'date_expiration')) {
|
||||
$pdo->prepare("UPDATE praticien SET abonnement_id = ?, date_expiration = ? WHERE id = ?")
|
||||
->execute([(int) $abonnement_id, (string) $current_period_end, (int) $praticien_id]);
|
||||
} elseif ($praticien_id && $abonnement_id && $current_period_end && colExists($pdo, 'praticien', 'abonnement_expire_le')) {
|
||||
$pdo->prepare("UPDATE praticien SET abonnement_id = ?, abonnement_expire_le = ? WHERE id = ?")
|
||||
->execute([(int) $abonnement_id, (string) $current_period_end, (int) $praticien_id]);
|
||||
}
|
||||
// IMPORTANT:
|
||||
// Ne pas activer/prolonger l'accès ici.
|
||||
// L'accès réel est accordé uniquement sur invoice.paid.
|
||||
|
||||
// 2) Créer le paiement même si invoice.paid est arrivé avant (robuste aux webhooks désordonnés)
|
||||
try {
|
||||
if ($sub_id) {
|
||||
$subLatest = $stripe->subscriptions->retrieve((string) $sub_id, []);
|
||||
$latestInvoiceId = stripeId($subLatest->latest_invoice ?? null);
|
||||
|
||||
if ($latestInvoiceId) {
|
||||
$invLatest = $stripe->invoices->retrieve((string) $latestInvoiceId, []);
|
||||
insertPaiementFromInvoice($pdo, $configAll, $stripe, $invLatest, (int) $praticien_id, (int) $abonnement_id, (string) $sub_id, $eventId);
|
||||
|
||||
// Optionnel: stocker last_invoice_id dans billing
|
||||
$upsertBilling([
|
||||
'praticien_id' => $praticien_id,
|
||||
'last_invoice_id' => (string) $latestInvoiceId,
|
||||
'last_event_id' => $eventId,
|
||||
]);
|
||||
}
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
wh_log('WARN checkout-sub latest_invoice insert paiement failed: ' . $e->getMessage());
|
||||
}
|
||||
// IMPORTANT:
|
||||
// Ne pas créer de ligne paiement ici pour un abonnement.
|
||||
// La création du paiement réel se fait uniquement sur invoice.paid.
|
||||
|
||||
http_response_code(200);
|
||||
echo 'ok-checkout-subscription';
|
||||
@@ -810,6 +787,7 @@ if (in_array($type, $checkoutEvents, true) && $obj) {
|
||||
$billingEvents = [
|
||||
'invoice.paid',
|
||||
'invoice.payment_failed',
|
||||
'invoice.payment_action_required',
|
||||
'invoice_payment.paid', // type vu chez toi (non standard)
|
||||
'customer.subscription.updated',
|
||||
'customer.subscription.deleted',
|
||||
@@ -822,7 +800,12 @@ if (in_array($type, $billingEvents, true) && $obj) {
|
||||
}
|
||||
|
||||
// ---------- invoice.* ----------
|
||||
if ($type === 'invoice.paid' || $type === 'invoice.payment_failed' || $type === 'invoice_payment.paid') {
|
||||
if (
|
||||
$type === 'invoice.paid' ||
|
||||
$type === 'invoice.payment_failed' ||
|
||||
$type === 'invoice.payment_action_required' ||
|
||||
$type === 'invoice_payment.paid'
|
||||
) {
|
||||
|
||||
// invoice_payment.paid => récupérer la vraie invoice
|
||||
if ($type === 'invoice_payment.paid') {
|
||||
@@ -870,18 +853,30 @@ if (in_array($type, $billingEvents, true) && $obj) {
|
||||
$amount_cent = is_numeric($inv->amount_due ?? null) ? (int) $inv->amount_due : null;
|
||||
}
|
||||
|
||||
// Price/Product depuis lignes
|
||||
$provider_plan_id = null;
|
||||
$provider_product_id = null;
|
||||
// Price/Product + période depuis lignes
|
||||
$provider_plan_id = null;
|
||||
$provider_product_id = null;
|
||||
$invoice_period_start = null;
|
||||
$invoice_period_end = null;
|
||||
try {
|
||||
if (isset($inv->lines->data) && is_array($inv->lines->data) && ! empty($inv->lines->data)) {
|
||||
$line0 = $inv->lines->data[0];
|
||||
|
||||
if (isset($line0->price)) {
|
||||
$provider_plan_id = stripeId($line0->price);
|
||||
if (is_object($line0->price) && isset($line0->price->product)) {
|
||||
$provider_product_id = stripeId($line0->price->product);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($line0->period)) {
|
||||
if (isset($line0->period->start)) {
|
||||
$invoice_period_start = dtFromUnix($line0->period->start);
|
||||
}
|
||||
if (isset($line0->period->end)) {
|
||||
$invoice_period_end = dtFromUnix($line0->period->end);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
wh_log('WARN invoice lines parse: ' . $e->getMessage());
|
||||
@@ -1046,7 +1041,14 @@ if (in_array($type, $billingEvents, true) && $obj) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Pour un renouvellement, la période portée par la facture est souvent
|
||||
// la source la plus fiable pour prolonger l'accès.
|
||||
if ($invoice_period_start) {
|
||||
$current_period_start = $invoice_period_start;
|
||||
}
|
||||
if ($invoice_period_end) {
|
||||
$current_period_end = $invoice_period_end;
|
||||
}
|
||||
wh_log("BILL ev=$eventId $type inv=" . ($invoice_id ?: 'null') . " sub=" . ($sub_id ?: 'null') . " cust=" . ($cust_id ?: 'null') . " pid=" . ($praticien_id ?: 'null') . " aid=" . ($abonnement_id ?: 'null') . " cps=" . ($current_period_start ?: 'null') . " cpe=" . ($current_period_end ?: 'null') . " status=" . ($sub_status ?: ''));
|
||||
|
||||
// Upsert billing_subscriptions (NE PAS écraser avec null)
|
||||
@@ -1059,7 +1061,11 @@ if (in_array($type, $billingEvents, true) && $obj) {
|
||||
'provider_subscription_id' => $sub_id,
|
||||
'provider_plan_id' => $provider_plan_id,
|
||||
'provider_product_id' => $provider_product_id,
|
||||
'status' => $sub_status ?? ($type === 'invoice.payment_failed' ? 'payment_failed' : 'paid'),
|
||||
'status' => $sub_status ?? (
|
||||
$type === 'invoice.payment_failed'
|
||||
? 'payment_failed'
|
||||
: ($type === 'invoice.payment_action_required' ? 'action_required' : 'paid')
|
||||
),
|
||||
'current_period_start' => $current_period_start,
|
||||
'current_period_end' => $current_period_end,
|
||||
'cancel_at_period_end' => (int) $cancel_at_period_end,
|
||||
@@ -1068,7 +1074,12 @@ if (in_array($type, $billingEvents, true) && $obj) {
|
||||
'last_event_id' => $eventId,
|
||||
'last_invoice_id' => $invoice_id,
|
||||
]);
|
||||
|
||||
if ($type === 'invoice.payment_action_required') {
|
||||
wh_log("ACTION_REQUIRED inv=" . ($invoice_id ?: 'null') . " sub=" . ($sub_id ?: 'null') . " cust=" . ($cust_id ?: 'null') . " pid=" . ($praticien_id ?: 'null'));
|
||||
http_response_code(200);
|
||||
echo 'ok-invoice-action-required';
|
||||
exit;
|
||||
}
|
||||
// invoice.paid => insert paiement + update praticien si pid/aid OK
|
||||
if ($type === 'invoice.paid') {
|
||||
if (! $praticien_id || ! $abonnement_id) {
|
||||
@@ -1092,7 +1103,7 @@ if (in_array($type, $billingEvents, true) && $obj) {
|
||||
$prix = is_numeric($amount_cent) ? ((int) $amount_cent) / 100.0 : 0.0;
|
||||
$statut = pickStatut($pdo, 'payé');
|
||||
|
||||
$date_expiration = $current_period_end;
|
||||
$date_expiration = $invoice_period_end ?: $current_period_end;
|
||||
if (! $date_expiration) {
|
||||
[$cpsF, $cpeF] = periodFallbackFromPlan($pdo, (int) $abonnement_id);
|
||||
$date_expiration = $cpeF ?: (new DateTime('now'))->modify("+30 days")->format('Y-m-d H:i:s');
|
||||
@@ -1135,6 +1146,9 @@ if (in_array($type, $billingEvents, true) && $obj) {
|
||||
if (colExists($pdo, 'praticien', 'date_expiration')) {
|
||||
$pdo->prepare("UPDATE praticien SET abonnement_id = ?, date_expiration = ? WHERE id = ?")
|
||||
->execute([(int) $abonnement_id, (string) $date_expiration, (int) $praticien_id]);
|
||||
} elseif (colExists($pdo, 'praticien', 'abonnement_expire_le')) {
|
||||
$pdo->prepare("UPDATE praticien SET abonnement_id = ?, abonnement_expire_le = ? WHERE id = ?")
|
||||
->execute([(int) $abonnement_id, (string) $date_expiration, (int) $praticien_id]);
|
||||
}
|
||||
|
||||
$pdo->commit();
|
||||
@@ -1276,11 +1290,9 @@ if (in_array($type, $billingEvents, true) && $obj) {
|
||||
'last_event_id' => $eventId,
|
||||
]);
|
||||
|
||||
// align expiration praticien
|
||||
if ($praticien_id && $current_period_end && colExists($pdo, 'praticien', 'date_expiration')) {
|
||||
$pdo->prepare("UPDATE praticien SET date_expiration = ? WHERE id = ?")
|
||||
->execute([(string) $current_period_end, (int) $praticien_id]);
|
||||
}
|
||||
// IMPORTANT:
|
||||
// Ne pas modifier praticien.date_expiration ici.
|
||||
// Seul invoice.paid doit prolonger l'accès effectif.
|
||||
|
||||
http_response_code(200);
|
||||
echo 'ok-sub';
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
+81
-80
@@ -1,47 +1,47 @@
|
||||
<?php
|
||||
$lang = $_SESSION['lang'] ?? 'fr';
|
||||
$lang = $_SESSION['lang'] ?? 'fr';
|
||||
|
||||
$PAGE_TITLE = $PAGE_TITLE ?? ($translations['site_name'] ?? 'uslugi.fr');
|
||||
$HEAD_EXTRA = $HEAD_EXTRA ?? '';
|
||||
$PAGE_TITLE = $PAGE_TITLE ?? ($translations['site_name'] ?? 'uslugi.fr');
|
||||
$HEAD_EXTRA = $HEAD_EXTRA ?? '';
|
||||
|
||||
$initiales = $initiales ?? ($GLOBALS['initiales'] ?? 'U');
|
||||
$avatarSvg = $avatar_svg ?? ($GLOBALS['avatar_svg'] ?? null);
|
||||
$pushCfg = $pushCfg ?? ($GLOBALS['pushCfg'] ?? []);
|
||||
$alerte_abonnement = $alerte_abonnement ?? ($GLOBALS['alerte_abonnement'] ?? null);
|
||||
$initiales = $initiales ?? ($GLOBALS['initiales'] ?? 'U');
|
||||
$avatarSvg = $avatar_svg ?? ($GLOBALS['avatar_svg'] ?? null);
|
||||
$pushCfg = $pushCfg ?? ($GLOBALS['pushCfg'] ?? []);
|
||||
$alerte_abonnement = $alerte_abonnement ?? ($GLOBALS['alerte_abonnement'] ?? null);
|
||||
|
||||
require_once __DIR__ . '/../contact_admin.php';
|
||||
require_once __DIR__ . '/../contact_admin.php';
|
||||
|
||||
// =====================================================
|
||||
// [RETOUR / REFERER SÉCURISÉ]
|
||||
// - Autorise le retour seulement si le referer est sur le même host
|
||||
// =====================================================
|
||||
$ref = $_SERVER['HTTP_REFERER'] ?? '';
|
||||
$host = $_SERVER['HTTP_HOST'] ?? '';
|
||||
$safeRef = '';
|
||||
if ($ref) {
|
||||
$u = parse_url($ref);
|
||||
if (!empty($u['host']) && $u['host'] === $host) {
|
||||
$safeRef = $ref;
|
||||
}
|
||||
}
|
||||
$fallbackBack = $safeRef ?: '/';
|
||||
// =====================================================
|
||||
// [RETOUR / REFERER SÉCURISÉ]
|
||||
// - Autorise le retour seulement si le referer est sur le même host
|
||||
// =====================================================
|
||||
$ref = $_SERVER['HTTP_REFERER'] ?? '';
|
||||
$host = $_SERVER['HTTP_HOST'] ?? '';
|
||||
$safeRef = '';
|
||||
if ($ref) {
|
||||
$u = parse_url($ref);
|
||||
if (! empty($u['host']) && $u['host'] === $host) {
|
||||
$safeRef = $ref;
|
||||
}
|
||||
}
|
||||
$fallbackBack = $safeRef ?: '/';
|
||||
|
||||
// =====================================================
|
||||
// [DETECTION MOBILE]
|
||||
// - Très simple détection UA pour le logo adaptatif
|
||||
// =====================================================
|
||||
$is_mobile = preg_match('/Mobile|Android|iPhone|iPad/i', $_SERVER['HTTP_USER_AGENT']);
|
||||
// =====================================================
|
||||
// [DETECTION MOBILE]
|
||||
// - Très simple détection UA pour le logo adaptatif
|
||||
// =====================================================
|
||||
$is_mobile = preg_match('/Mobile|Android|iPhone|iPad/i', $_SERVER['HTTP_USER_AGENT']);
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<title><?= htmlspecialchars($PAGE_TITLE, ENT_QUOTES, 'UTF-8') ?></title>
|
||||
<?= $HEAD_EXTRA ?>
|
||||
<title><?php echo htmlspecialchars($PAGE_TITLE, ENT_QUOTES, 'UTF-8') ?></title>
|
||||
<?php echo $HEAD_EXTRA ?>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||
|
||||
<!-- Police Montserrat -->
|
||||
|
||||
|
||||
|
||||
<!-- Bootstrap -->
|
||||
<link href="/css/bootstrap.min.css" rel="stylesheet">
|
||||
@@ -49,20 +49,21 @@ $is_mobile = preg_match('/Mobile|Android|iPhone|iPad/i', $_SERVER['HTTP_USER_AGE
|
||||
|
||||
|
||||
<!-- Styles personnalisés -->
|
||||
<link rel="stylesheet" href="<?= asset('/includes/site_style.css') ?>">
|
||||
<link rel="stylesheet" href="<?= asset('/includes/custom_style.css') ?>"> <!-- ta home -->
|
||||
<link rel="stylesheet" href="<?= asset('/includes/mobil_style.css') ?>">
|
||||
<link rel="stylesheet" href="<?php echo asset('/includes/site_style.css') ?>">
|
||||
<link rel="stylesheet" href="<?php echo asset('/includes/custom_style.css') ?>"> <!-- ta home -->
|
||||
<link rel="stylesheet" href="<?php echo asset('/includes/mobil_style.css') ?>">
|
||||
<?php include_once __DIR__ . '/../../includes/tracking.php'; ?>
|
||||
</head>
|
||||
<body>
|
||||
<?php
|
||||
// Pour n'afficher qu'une fois par compte (PWA)
|
||||
$pushOnboardKey = 'uslugi_push_onboard_v1_' . (int)($_SESSION['compte_id'] ?? 0);
|
||||
// Pour n'afficher qu'une fois par compte (PWA)
|
||||
$pushOnboardKey = 'uslugi_push_onboard_v1_' . (int) ($_SESSION['compte_id'] ?? 0);
|
||||
|
||||
// helper traduction (si tu n'as pas déjà $t ici)
|
||||
$t = function(string $k, string $fallback = '') use (&$translations): string {
|
||||
$val = $translations[$k] ?? null;
|
||||
return ($val === null || $val === '') ? $fallback : (string)$val;
|
||||
};
|
||||
// helper traduction (si tu n'as pas déjà $t ici)
|
||||
$t = function (string $k, string $fallback = '') use (&$translations): string {
|
||||
$val = $translations[$k] ?? null;
|
||||
return ($val === null || $val === '') ? $fallback : (string) $val;
|
||||
};
|
||||
?>
|
||||
|
||||
<div class="modal fade" id="pushOnboardModal" tabindex="-1" aria-hidden="true">
|
||||
@@ -70,17 +71,17 @@ $t = function(string $k, string $fallback = '') use (&$translations): string {
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">
|
||||
<?= htmlspecialchars($t('push_onboard_title', 'Activer les notifications ?'), ENT_QUOTES, 'UTF-8') ?>
|
||||
<?php echo htmlspecialchars($t('push_onboard_title', 'Activer les notifications ?'), ENT_QUOTES, 'UTF-8') ?>
|
||||
</h5>
|
||||
<button type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="<?= htmlspecialchars($t('push_onboard_close_aria', 'Fermer'), ENT_QUOTES, 'UTF-8') ?>">
|
||||
aria-label="<?php echo htmlspecialchars($t('push_onboard_close_aria', 'Fermer'), ENT_QUOTES, 'UTF-8') ?>">
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<?= htmlspecialchars($t('push_onboard_body', 'Vous recevrez des alertes pour les nouveaux messages et rendez-vous.'), ENT_QUOTES, 'UTF-8') ?>
|
||||
<?php echo htmlspecialchars($t('push_onboard_body', 'Vous recevrez des alertes pour les nouveaux messages et rendez-vous.'), ENT_QUOTES, 'UTF-8') ?>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
@@ -88,13 +89,13 @@ $t = function(string $k, string $fallback = '') use (&$translations): string {
|
||||
class="btn btn-outline-secondary"
|
||||
data-bs-dismiss="modal"
|
||||
id="pushLaterBtn">
|
||||
<?= htmlspecialchars($t('push_onboard_later', 'Plus tard'), ENT_QUOTES, 'UTF-8') ?>
|
||||
<?php echo htmlspecialchars($t('push_onboard_later', 'Plus tard'), ENT_QUOTES, 'UTF-8') ?>
|
||||
</button>
|
||||
|
||||
<button type="button"
|
||||
class="btn btn-primary"
|
||||
id="pushEnableBtn">
|
||||
<?= htmlspecialchars($t('push_onboard_allow', 'Autoriser'), ENT_QUOTES, 'UTF-8') ?>
|
||||
<?php echo htmlspecialchars($t('push_onboard_allow', 'Autoriser'), ENT_QUOTES, 'UTF-8') ?>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -111,9 +112,9 @@ $t = function(string $k, string $fallback = '') use (&$translations): string {
|
||||
- Menu "?" compact (aide)
|
||||
================================================ -->
|
||||
<header class="site-header py-2 border-bottom">
|
||||
|
||||
|
||||
<?php if (!empty($_SESSION['admin_impersonate'])): ?>
|
||||
|
||||
<?php if (! empty($_SESSION['admin_impersonate'])): ?>
|
||||
<div class="alert alert-warning text-center mb-0 rounded-0">
|
||||
Вы вошли как другой пользователь.
|
||||
<a href="/admin/tools/retour_admin.php"
|
||||
@@ -125,11 +126,11 @@ $t = function(string $k, string $fallback = '') use (&$translations): string {
|
||||
<?php endif; ?>
|
||||
|
||||
<?php $alerte_abonnement = $GLOBALS['alerte_abonnement'] ?? null; ?>
|
||||
<?php if (!empty($alerte_abonnement)): ?>
|
||||
<?php if (! empty($alerte_abonnement)): ?>
|
||||
<div class="alert alert-warning m-0 py-1 px-2 rounded"
|
||||
role="alert"
|
||||
style="background:#F1F1F1; color:#E03C3F; font-size:14px; text-align:center;">
|
||||
<?= htmlspecialchars((string)$alerte_abonnement, ENT_QUOTES, 'UTF-8') ?>
|
||||
<?php echo htmlspecialchars((string) $alerte_abonnement, ENT_QUOTES, 'UTF-8') ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
@@ -140,11 +141,11 @@ $t = function(string $k, string $fallback = '') use (&$translations): string {
|
||||
<div class="brand">
|
||||
<!-- Bouton retour -->
|
||||
<a
|
||||
href="<?= htmlspecialchars($fallbackBack, ENT_QUOTES, 'UTF-8') ?>"
|
||||
href="<?php echo htmlspecialchars($fallbackBack, ENT_QUOTES, 'UTF-8') ?>"
|
||||
id="btnBack"
|
||||
class="btn btn-light btn-circle me-2"
|
||||
style="border-radius:5px; box-shadow:0 1px 2px rgba(0,0,0,.08);"
|
||||
aria-label="<?= htmlspecialchars($translations['retour'] ?? 'Retour', ENT_QUOTES, 'UTF-8') ?>"
|
||||
aria-label="<?php echo htmlspecialchars($translations['retour'] ?? 'Retour', ENT_QUOTES, 'UTF-8') ?>"
|
||||
>
|
||||
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" aria-hidden="true">
|
||||
<path
|
||||
@@ -179,9 +180,9 @@ $t = function(string $k, string $fallback = '') use (&$translations): string {
|
||||
<!-- RDV (+ badge non lus) -->
|
||||
<a
|
||||
id="btn-rdv"
|
||||
href="<?= ($_SESSION['role'] ?? '') === 'praticien'
|
||||
? '/public/praticien/rendez_vous.php'
|
||||
: '/public/client/mes_rendez_vous.php' ?>"
|
||||
href="<?php echo($_SESSION['role'] ?? '') === 'praticien'
|
||||
? '/public/praticien/rendez_vous.php'
|
||||
: '/public/client/mes_rendez_vous.php' ?>"
|
||||
class="nav-rdv position-relative"
|
||||
>
|
||||
<img src="/public/uploads/img/bell-on.png" width="22" alt="Rendez-vous">
|
||||
@@ -192,7 +193,7 @@ $t = function(string $k, string $fallback = '') use (&$translations): string {
|
||||
text-align:center; border-radius:999px; padding:0 5px; display:none;"
|
||||
>0</span>
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
<!-- Messages (+ badge non lus) -->
|
||||
<a href="/messages/" class="nav-msg position-relative">
|
||||
@@ -208,38 +209,38 @@ $t = function(string $k, string $fallback = '') use (&$translations): string {
|
||||
<!-- Menu principal -->
|
||||
<div class="menu-wrap">
|
||||
<button type="button" id="btnMenu">
|
||||
<div class="avatar"><?= $initiales ?></div>
|
||||
<div class="avatar"><?php echo $initiales ?></div>
|
||||
</button>
|
||||
|
||||
<!-- Menu principal (liste) -->
|
||||
<ul class="dropdown-menu-custom menu-panel" id="menu">
|
||||
<?php if ($_SESSION['role'] === 'client'): ?>
|
||||
<li onclick="location.href='/public/client/mes_rendez_vous.php'">
|
||||
<?= htmlspecialchars($translations['menu_mes_rendez_vous']) ?>
|
||||
<?php echo htmlspecialchars($translations['menu_mes_rendez_vous']) ?>
|
||||
</li>
|
||||
<li onclick="location.href='/public/client/list_praticien.php'">
|
||||
<?= htmlspecialchars($translations['menu_liste_specialistes']) ?>
|
||||
<?php echo htmlspecialchars($translations['menu_liste_specialistes']) ?>
|
||||
</li>
|
||||
<li onclick="location.href='/public/client/mes_favori.php'">
|
||||
<?= htmlspecialchars($translations['menu_mes_favoris']) ?>
|
||||
<?php echo htmlspecialchars($translations['menu_mes_favoris']) ?>
|
||||
</li>
|
||||
<?php else: ?>
|
||||
<li onclick="location.href='/public/praticien/rendez_vous.php'">
|
||||
<?= htmlspecialchars($translations['menu_mes_rendez_vous']) ?>
|
||||
<?php echo htmlspecialchars($translations['menu_mes_rendez_vous']) ?>
|
||||
</li>
|
||||
<li onclick="location.href='/public/praticien/agenda.php'">
|
||||
<?= htmlspecialchars($translations['menu_mon_agenda']) ?>
|
||||
<?php echo htmlspecialchars($translations['menu_mon_agenda']) ?>
|
||||
</li>
|
||||
<li onclick="location.href='/public/praticien/disponibilites.php'">
|
||||
<?= htmlspecialchars($translations['menu_mes_disponibilites']) ?>
|
||||
<?php echo htmlspecialchars($translations['menu_mes_disponibilites']) ?>
|
||||
</li>
|
||||
<li onclick="location.href='/public/praticien/synchronisation.php'">
|
||||
<?= htmlspecialchars($translations['menu_sync_agendas']) ?>
|
||||
<?php echo htmlspecialchars($translations['menu_sync_agendas']) ?>
|
||||
</li>
|
||||
<!--<li onclick="location.href='/public/praticien/clients/index.php'">
|
||||
<?= htmlspecialchars($translations['menu_mes_clients'] ?? 'Mes clients') ?>
|
||||
<?php echo htmlspecialchars($translations['menu_mes_clients'] ?? 'Mes clients') ?>
|
||||
</li>-->
|
||||
|
||||
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Sous-menu Admin -->
|
||||
@@ -250,7 +251,7 @@ $t = function(string $k, string $fallback = '') use (&$translations): string {
|
||||
aria-expanded="false"
|
||||
aria-controls="submenu-admin"
|
||||
>
|
||||
<?= htmlspecialchars($translations['menu_admin']) ?>
|
||||
<?php echo htmlspecialchars($translations['menu_admin']) ?>
|
||||
<svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
|
||||
<path d="M6 9l6 6 6-6" fill="none" stroke="currentColor" stroke-width="2"/>
|
||||
</svg>
|
||||
@@ -259,28 +260,28 @@ $t = function(string $k, string $fallback = '') use (&$translations): string {
|
||||
<ul id="submenu-admin" class="submenu" hidden>
|
||||
<?php if ($_SESSION['role'] === 'praticien'): ?>
|
||||
<li onclick="location.href='/public/praticien/resiliation.php'">
|
||||
<?= htmlspecialchars($translations['menu_gerer_abonnement']) ?>
|
||||
<?php echo htmlspecialchars($translations['menu_gerer_abonnement']) ?>
|
||||
</li>
|
||||
<li onclick="location.href='/public/praticien/factures.php'">
|
||||
<?= htmlspecialchars($translations['menu_mes_factures']) ?>
|
||||
<?php echo htmlspecialchars($translations['menu_mes_factures']) ?>
|
||||
</li>
|
||||
<li onclick="location.href='/public/cabinet/invitations.php'">
|
||||
<?= htmlspecialchars($translations['secretariat_inviter_secretaire']) ?>
|
||||
<?php echo htmlspecialchars($translations['secretariat_inviter_secretaire']) ?>
|
||||
</li>
|
||||
<li onclick="location.href='/public/praticien/clients/index.php'">
|
||||
<?= htmlspecialchars($translations['menu_mes_clients'] ?? 'Mes clients') ?>
|
||||
<?php echo htmlspecialchars($translations['menu_mes_clients'] ?? 'Mes clients') ?>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
|
||||
<li onclick="location.href='/public/<?= $_SESSION['role'] ?>/informations.php'">
|
||||
<?= htmlspecialchars($translations['menu_mes_informations']) ?>
|
||||
<li onclick="location.href='/public/<?php echo $_SESSION['role'] ?>/informations.php'">
|
||||
<?php echo htmlspecialchars($translations['menu_mes_informations']) ?>
|
||||
</li>
|
||||
|
||||
<li onclick="location.href='/public/<?= $_SESSION['role'] ?>/compte.php'">
|
||||
<?= htmlspecialchars($translations['menu_infos_connexion']) ?>
|
||||
|
||||
<li onclick="location.href='/public/<?php echo $_SESSION['role'] ?>/compte.php'">
|
||||
<?php echo htmlspecialchars($translations['menu_infos_connexion']) ?>
|
||||
</li>
|
||||
<li onclick="location.href='/supprimer_compte.php'">
|
||||
<?= htmlspecialchars($translations['menu_supprimer_compte']) ?>
|
||||
<?php echo htmlspecialchars($translations['menu_supprimer_compte']) ?>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
@@ -289,10 +290,10 @@ $t = function(string $k, string $fallback = '') use (&$translations): string {
|
||||
|
||||
<!-- Déconnexion -->
|
||||
<li onclick="closeMenu(); openContactAdmin()">
|
||||
<?= htmlspecialchars($translations['contact_admin']) ?>
|
||||
<?php echo htmlspecialchars($translations['contact_admin']) ?>
|
||||
</li>
|
||||
<li onclick="location.href='/public/deconnexion.php'">
|
||||
<?= htmlspecialchars($translations['menu_deconnexion']) ?>
|
||||
<?php echo htmlspecialchars($translations['menu_deconnexion']) ?>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -464,10 +465,10 @@ if ('serviceWorker' in navigator) {
|
||||
if (!('serviceWorker' in navigator) || !('PushManager' in window)) return;
|
||||
if (!('Notification' in window)) return;
|
||||
|
||||
const VAPID_PUBLIC = "<?= htmlspecialchars($pushCfg['vapid_public'] ?? '', ENT_QUOTES) ?>";
|
||||
const VAPID_PUBLIC = "<?php echo htmlspecialchars($pushCfg['vapid_public'] ?? '', ENT_QUOTES) ?>";
|
||||
if (!VAPID_PUBLIC) return;
|
||||
|
||||
const onboardKey = "<?= htmlspecialchars($pushOnboardKey, ENT_QUOTES) ?>";
|
||||
const onboardKey = "<?php echo htmlspecialchars($pushOnboardKey, ENT_QUOTES) ?>";
|
||||
|
||||
function urlBase64ToUint8Array(base64String) {
|
||||
const padding = '='.repeat((4 - (base64String.length % 4)) % 4);
|
||||
|
||||
+16
-1
@@ -84,7 +84,7 @@ return [
|
||||
'btn_da_udalit' => 'Oui, supprimer',
|
||||
|
||||
// Page Connexion
|
||||
'connexion_titre' => 'Connexion 1',
|
||||
'connexion_titre' => 'Connexion',
|
||||
'erreur_champs_vides' => 'Veuillez remplir tous les champs.',
|
||||
'erreur_email_non_valide' => 'Veuillez confirmer votre e-mail avant de vous connecter.',
|
||||
'erreur_identifiants' => 'E-mail ou mot de passe incorrect.',
|
||||
@@ -2012,4 +2012,19 @@ Cliquez simplement sur le lien pour vous connecter.<br>',
|
||||
'home_pro_point_4' => 'Gestion des rendez-vous',
|
||||
'home_pro_point_5' => 'Synchronisation des calendriers',
|
||||
'supp_uploads' => 'Supprimer orpheline',
|
||||
'tvalidat_erreur_email_non_valide' => 'Email non confirmé. Si vous n’avez pas reçu le message, renvoyez l’email',
|
||||
'tvalidat_btn_renvoyer' => 'Renvoyer le mail',
|
||||
'tvalidat_err_acces_titre' => 'Accès incorrect',
|
||||
'tvalidat_err_acces_msg' => 'Cette action n’est pas autorisée.',
|
||||
'tvalidat_err_email_invalide' => 'Email invalide.',
|
||||
'tvalidat_err_action_non_autorisee' => 'Action non autorisée. Merci de vous reconnecter.',
|
||||
'tvalidat_err_compte_introuvable' => 'Compte introuvable.',
|
||||
'tvalidat_info_titre' => 'Information',
|
||||
'tvalidat_info_email_deja_valide' => 'Votre adresse email est déjà validée.',
|
||||
'tvalidat_mail_intro' => 'Voici un nouveau lien pour valider votre adresse email.',
|
||||
'tvalidat_err_envoi' => 'Le mail n’a pas pu être envoyé. Merci de réessayer plus tard.',
|
||||
'tvalidat_success_titre' => 'Email envoyé',
|
||||
'tvalidat_success_msg' => 'Un nouveau lien de validation vient d’être envoyé à votre adresse email.',
|
||||
'tvalidat_err_lien_invalide' => 'Lien invalide.',
|
||||
'tvalidat_err_lien_expire' => 'Le lien de validation a expiré.',
|
||||
];
|
||||
@@ -2022,4 +2022,19 @@ return [
|
||||
'home_pro_point_4' => 'Управление записью и расписанием',
|
||||
'home_pro_point_5' => 'Синхронизация календарей',
|
||||
'supp_uploads' => 'Удалить лишние',
|
||||
'tvalidat_erreur_email_non_valide' => 'Email ещё не подтверждён. Если вы не получили письмо, отправьте подтверждение повторно',
|
||||
'tvalidat_btn_renvoyer' => 'Отправить письмо ещё раз',
|
||||
'tvalidat_err_acces_titre' => 'Неверный доступ',
|
||||
'tvalidat_err_acces_msg' => 'Это действие не разрешено.',
|
||||
'tvalidat_err_email_invalide' => 'Неверный email.',
|
||||
'tvalidat_err_action_non_autorisee' => 'Действие не авторизовано. Пожалуйста, войдите заново.',
|
||||
'tvalidat_err_compte_introuvable' => 'Аккаунт не найден.',
|
||||
'tvalidat_info_titre' => 'Информация',
|
||||
'tvalidat_info_email_deja_valide' => 'Ваш email уже подтверждён.',
|
||||
'tvalidat_mail_intro' => 'Вот новая ссылка для подтверждения вашего email.',
|
||||
'tvalidat_err_envoi' => 'Не удалось отправить письмо. Пожалуйста, попробуйте позже.',
|
||||
'tvalidat_success_titre' => 'Письмо отправлено',
|
||||
'tvalidat_success_msg' => 'Новая ссылка для подтверждения была отправлена на ваш email.',
|
||||
'tvalidat_err_lien_invalide' => 'Недействительная ссылка.',
|
||||
'tvalidat_err_lien_expire' => 'Срок действия ссылки подтверждения истёк.',
|
||||
];
|
||||
@@ -0,0 +1,292 @@
|
||||
<?php
|
||||
declare (strict_types = 1);
|
||||
require_once __DIR__ . '/public/includes/init_auth.php';
|
||||
|
||||
function ui_exit(
|
||||
string $title,
|
||||
string $message,
|
||||
int $status = 400,
|
||||
string $type = 'error',
|
||||
?string $cta_href = '/',
|
||||
?string $cta_label = null
|
||||
): void {
|
||||
http_response_code($status);
|
||||
$title = htmlspecialchars($title, ENT_QUOTES, 'UTF-8');
|
||||
$message = nl2br(htmlspecialchars($message, ENT_QUOTES, 'UTF-8'));
|
||||
$cta_href = $cta_href ? htmlspecialchars($cta_href, ENT_QUOTES, 'UTF-8') : null;
|
||||
$cta_label = $cta_label ?? 'OK';
|
||||
|
||||
$iconBorder = '#B91C1C';
|
||||
$iconBg = 'rgba(185,28,28,.08)';
|
||||
$iconRing = 'rgba(185,28,28,.25)';
|
||||
$checkCSS = '';
|
||||
|
||||
if ($type === 'info') {
|
||||
$iconBorder = '#1D4ED8';
|
||||
$iconBg = 'rgba(29,78,216,.08)';
|
||||
$iconRing = 'rgba(29,78,216,.25)';
|
||||
} elseif ($type === 'success') {
|
||||
$iconBorder = '#1E8050';
|
||||
$iconBg = 'rgba(30,128,80,.08)';
|
||||
$iconRing = 'rgba(30,128,80,.20)';
|
||||
$checkCSS = 'width:48px;height:36px;border:5px solid #1e8050;border-top:0;border-left:0;transform:rotate(45deg);';
|
||||
}
|
||||
|
||||
echo '<!DOCTYPE html><html lang="fr"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">';
|
||||
echo '<title>' . $title . '</title>';
|
||||
echo '<style>
|
||||
html,body{height:100%;margin:0}
|
||||
body{display:flex;align-items:center;justify-content:center;background:#8CA9FF;font-family:Montserrat,system-ui,Segoe UI,Roboto,Arial}
|
||||
.card{max-width:720px;width:94%;background:#fff;border:1px solid #e5e7eb;border-radius:14px;box-shadow:0 8px 24px rgba(0,0,0,.08);padding:28px;text-align:center}
|
||||
h1{margin:6px 0 10px;font-size:clamp(22px,2.4vw,28px)}
|
||||
p{margin:0 0 18px;color:#6b7280;font-size:clamp(16px,2.2vw,18px)}
|
||||
.row{display:flex;gap:10px;justify-content:center;flex-wrap:wrap;margin-top:8px}
|
||||
.btn{padding:10px 14px;border-radius:10px;text-decoration:none;display:inline-block}
|
||||
.btn-primary{background:#2563eb;color:#fff}
|
||||
.btn-outline{border:1px solid #d1d5db;color:#374151;background:#fff}
|
||||
</style></head><body><div class="card">';
|
||||
|
||||
echo '<div style="width:102px;height:102px;border-radius:50%;background:' . $iconBg . ';border:3px solid ' . $iconRing . ';display:flex;align-items:center;justify-content:center;margin:0 auto 20px;">';
|
||||
if ($type === 'success') {
|
||||
echo '<div style="' . $checkCSS . '"></div>';
|
||||
} elseif ($type === 'info') {
|
||||
echo '<div style="width:14px;height:48px;border-left:5px solid ' . $iconBorder . ';position:relative;">
|
||||
<div style="position:absolute;top:-18px;left:-5px;width:10px;height:10px;background:' . $iconBorder . ';border-radius:50%;"></div>
|
||||
</div>';
|
||||
} else {
|
||||
echo '<div style="position:relative;width:48px;height:48px;">
|
||||
<div style="position:absolute;top:0;left:22px;width:5px;height:48px;background:' . $iconBorder . ';transform:rotate(45deg);border-radius:2px;"></div>
|
||||
<div style="position:absolute;top:0;left:22px;width:5px;height:48px;background:' . $iconBorder . ';transform:rotate(-45deg);border-radius:2px;"></div>
|
||||
</div>';
|
||||
}
|
||||
echo '</div>';
|
||||
|
||||
echo '<h1>' . $title . '</h1>';
|
||||
echo '<p>' . $message . '</p>';
|
||||
|
||||
if ($cta_href) {
|
||||
echo '<div class="row"><a class="btn btn-outline" href="' . $cta_href . '">' . $cta_label . '</a></div>';
|
||||
}
|
||||
|
||||
echo '</div></body></html>';
|
||||
exit;
|
||||
}
|
||||
|
||||
if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
|
||||
ui_exit(
|
||||
$translations['tvalidat_err_acces_titre'] ?? 'Accès incorrect',
|
||||
$translations['tvalidat_err_acces_msg'] ?? 'Accès invalide.',
|
||||
405,
|
||||
'info',
|
||||
'/connexion.php',
|
||||
$translations['btn_se_connecter'] ?? 'Se connecter'
|
||||
);
|
||||
}
|
||||
|
||||
$email = trim((string) ($_POST['email'] ?? ''));
|
||||
|
||||
if ($email === '' || ! filter_var($email, FILTER_VALIDATE_EMAIL) || preg_match('/[\r\n]/', $email)) {
|
||||
ui_exit(
|
||||
$translations['err_titre'] ?? 'Un problème est survenu',
|
||||
$translations['tvalidat_err_email_invalide'] ?? 'Email invalide.',
|
||||
400,
|
||||
'error',
|
||||
'/connexion.php',
|
||||
$translations['btn_se_connecter'] ?? 'Se connecter'
|
||||
);
|
||||
}
|
||||
|
||||
$pending = $_SESSION['pending_validation_resend'] ?? null;
|
||||
|
||||
if (
|
||||
empty($pending['authorized']) ||
|
||||
empty($pending['compte_id']) ||
|
||||
empty($pending['email']) ||
|
||||
empty($pending['expires_at']) ||
|
||||
(int) $pending['expires_at'] < time() ||
|
||||
! hash_equals((string) $pending['email'], $email)
|
||||
) {
|
||||
unset($_SESSION['pending_validation_resend']);
|
||||
|
||||
ui_exit(
|
||||
$translations['err_titre'] ?? 'Un problème est survenu',
|
||||
$translations['tvalidat_err_action_non_autorisee'] ?? 'Action non autorisée. Merci de vous reconnecter.',
|
||||
403,
|
||||
'error',
|
||||
'/connexion.php',
|
||||
$translations['btn_se_connecter'] ?? 'Se connecter'
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT id, email, role, reference_id, email_valide
|
||||
FROM compte
|
||||
WHERE id = :id AND email = :email
|
||||
LIMIT 1
|
||||
");
|
||||
$stmt->execute([
|
||||
'id' => (int) $pending['compte_id'],
|
||||
'email' => $email,
|
||||
]);
|
||||
$compte = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (! $compte) {
|
||||
unset($_SESSION['pending_validation_resend']);
|
||||
ui_exit(
|
||||
$translations['err_titre'] ?? 'Un problème est survenu',
|
||||
$translations['tvalidat_err_compte_introuvable'] ?? 'Compte introuvable.',
|
||||
404,
|
||||
'error',
|
||||
'/connexion.php',
|
||||
$translations['btn_se_connecter'] ?? 'Se connecter'
|
||||
);
|
||||
}
|
||||
|
||||
if (! empty($compte['email_valide'])) {
|
||||
unset($_SESSION['pending_validation_resend']);
|
||||
ui_exit(
|
||||
$translations['tvalidat_info_titre'] ?? 'Information',
|
||||
$translations['tvalidat_info_email_deja_valide'] ?? 'Votre adresse email est déjà validée.',
|
||||
200,
|
||||
'info',
|
||||
'/connexion.php',
|
||||
$translations['btn_se_connecter'] ?? 'Se connecter'
|
||||
);
|
||||
}
|
||||
|
||||
$prenom = '';
|
||||
$nom = '';
|
||||
|
||||
if (($compte['role'] ?? '') === 'praticien') {
|
||||
$stmt = $pdo->prepare("SELECT prenom, nom FROM praticien WHERE id = :id LIMIT 1");
|
||||
$stmt->execute(['id' => (int) $compte['reference_id']]);
|
||||
$profil = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
} elseif (($compte['role'] ?? '') === 'client') {
|
||||
$stmt = $pdo->prepare("SELECT prenom, nom FROM client WHERE id = :id LIMIT 1");
|
||||
$stmt->execute(['id' => (int) $compte['reference_id']]);
|
||||
$profil = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
} else {
|
||||
$profil = false;
|
||||
}
|
||||
|
||||
if ($profil) {
|
||||
$prenom = trim((string) ($profil['prenom'] ?? ''));
|
||||
$nom = trim((string) ($profil['nom'] ?? ''));
|
||||
}
|
||||
|
||||
$token = bin2hex(random_bytes(32));
|
||||
$token_validation_expire = (new DateTime('+6 hours'))->format('Y-m-d H:i:s');
|
||||
$token_autologin = bin2hex(random_bytes(32));
|
||||
$token_autologin_expire = (new DateTime('+60 minutes'))->format('Y-m-d H:i:s');
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
UPDATE compte
|
||||
SET token_validation = :token,
|
||||
token_validation_expire = :token_expire,
|
||||
token_autologin = :al,
|
||||
token_autologin_expire = :al_exp
|
||||
WHERE id = :id
|
||||
LIMIT 1
|
||||
");
|
||||
$stmt->execute([
|
||||
'token' => $token,
|
||||
'token_expire' => $token_validation_expire,
|
||||
'al' => $token_autologin,
|
||||
'al_exp' => $token_autologin_expire,
|
||||
'id' => (int) $compte['id'],
|
||||
]);
|
||||
|
||||
} catch (Throwable $e) {
|
||||
error_log('renvoyer_validat_mail.php - DB error: ' . $e->getMessage());
|
||||
ui_exit(
|
||||
$translations['err_titre'] ?? 'Un problème est survenu',
|
||||
$translations['err_serveur'] ?? 'Erreur serveur.',
|
||||
500,
|
||||
'error',
|
||||
'/connexion.php',
|
||||
$translations['btn_se_connecter'] ?? 'Se connecter'
|
||||
);
|
||||
}
|
||||
|
||||
$validation_url = "https://uslugi.fr/validation.php?token=" . urlencode($token) . "&al=" . urlencode($token_autologin);
|
||||
$validation_url_html = htmlspecialchars($validation_url, ENT_QUOTES, 'UTF-8');
|
||||
|
||||
$prenom_nom = trim($prenom . ' ' . $nom);
|
||||
$to_name = $prenom_nom !== '' ? $prenom_nom : $email;
|
||||
|
||||
$sujet = $translations['mail_valid_sujet'] ?? 'Validation de votre adresse email';
|
||||
|
||||
$html = "<!DOCTYPE html>
|
||||
<html lang='fr'>
|
||||
<head>
|
||||
<meta charset='UTF-8'>
|
||||
<meta http-equiv='X-UA-Compatible' content='IE=edge'>
|
||||
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; background-color:#f4f4f4; padding:20px; }
|
||||
.mail-container { max-width:600px; margin:auto; background:#fff; padding:30px; border-radius:8px; box-shadow:0 0 10px rgba(0,0,0,0.1); }
|
||||
.title { font-size:20px; font-weight:bold; color:#333; margin-bottom:20px; }
|
||||
.btn { display:inline-block; margin-top:20px; padding:12px 24px; background-color:#E03C3F; color:#fff; text-decoration:none; border-radius:999px; font-weight:bold; }
|
||||
.footer { margin-top:30px; font-size:13px; color:#888; }
|
||||
.link-fallback { word-break: break-all; font-size:13px; margin-top:15px; color:#555; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class='mail-container'>
|
||||
<div class='title'>" . htmlspecialchars(sprintf($translations['mail_valid_bonjour_nom'] ?? 'Bonjour %s,', $to_name), ENT_QUOTES, 'UTF-8') . "</div>
|
||||
<p>" . htmlspecialchars($translations['tvalidat_mail_intro'] ?? 'Voici un nouveau lien pour valider votre adresse email.', ENT_QUOTES, 'UTF-8') . "</p>
|
||||
<p>" . htmlspecialchars($translations['mail_valid_action'] ?? 'Cliquez sur le bouton ci-dessous pour confirmer votre adresse.', ENT_QUOTES, 'UTF-8') . "</p>
|
||||
<a href='{$validation_url_html}' class='btn'>" . htmlspecialchars($translations['mail_valid_btn'] ?? 'Valider mon adresse email', ENT_QUOTES, 'UTF-8') . "</a>
|
||||
<p class='link-fallback'>" . htmlspecialchars($translations['mail_valid_fallback'] ?? 'Si le bouton ne fonctionne pas, utilisez ce lien :', ENT_QUOTES, 'UTF-8') . "<br>
|
||||
<a href='{$validation_url_html}'>{$validation_url_html}</a>
|
||||
</p>
|
||||
<div class='footer'>
|
||||
" . htmlspecialchars($translations['mail_valid_expire'] ?? 'Ce lien expire prochainement.', ENT_QUOTES, 'UTF-8') . "<br>
|
||||
" . htmlspecialchars($translations['mail_valid_ignore'] ?? 'Si vous n’êtes pas à l’origine de cette demande, ignorez ce message.', ENT_QUOTES, 'UTF-8') . "<br><br>
|
||||
" . htmlspecialchars($translations['mail_valid_signature'] ?? 'USLUGI.FR', ENT_QUOTES, 'UTF-8') . "
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>";
|
||||
|
||||
$text = strip_tags(
|
||||
sprintf($translations['mail_valid_bonjour_nom'] ?? 'Bonjour %s,', $to_name) . "\n\n" .
|
||||
($translations['tvalidat_mail_intro'] ?? 'Voici un nouveau lien pour valider votre adresse email.') . "\n" .
|
||||
($translations['mail_valid_action'] ?? 'Cliquez sur le lien suivant pour confirmer votre adresse.') . "\n" .
|
||||
$validation_url . "\n\n" .
|
||||
($translations['mail_valid_expire'] ?? 'Ce lien expire prochainement.') . "\n" .
|
||||
($translations['mail_valid_ignore'] ?? 'Si vous n\'êtes pas à l\'origine de cette demande, ignorez ce message.') . "\n\n" .
|
||||
($translations['mail_valid_signature'] ?? 'USLUGI.FR')
|
||||
);
|
||||
|
||||
list($sent, $err) = uslugi_mail(
|
||||
$email,
|
||||
$sujet,
|
||||
$html,
|
||||
$text,
|
||||
['to_name' => $to_name]
|
||||
);
|
||||
|
||||
unset($_SESSION['pending_validation_resend']);
|
||||
|
||||
if (! $sent) {
|
||||
error_log("Renvoi validation mail NOT sent to {$email}: {$err}");
|
||||
ui_exit(
|
||||
$translations['err_titre'] ?? 'Un problème est survenu',
|
||||
$translations['tvalidat_err_envoi'] ?? 'Le mail n’a pas pu être envoyé. Merci de réessayer plus tard.',
|
||||
500,
|
||||
'error',
|
||||
'/connexion.php',
|
||||
$translations['btn_se_connecter'] ?? 'Se connecter'
|
||||
);
|
||||
}
|
||||
|
||||
ui_exit(
|
||||
$translations['tvalidat_success_titre'] ?? 'Email envoyé',
|
||||
$translations['tvalidat_success_msg'] ?? 'Un nouveau lien de validation vient d’être envoyé à votre adresse email.',
|
||||
200,
|
||||
'success',
|
||||
'/connexion.php',
|
||||
$translations['btn_se_connecter'] ?? 'Se connecter'
|
||||
);
|
||||
@@ -1,36 +1,37 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
declare (strict_types = 1);
|
||||
require_once __DIR__ . '/../includes/bootstrap_global.php';
|
||||
|
||||
/**
|
||||
* UI helper: affiche une jolie carte centrée puis exit.
|
||||
* $type: 'error' | 'info' | 'success'
|
||||
*/
|
||||
function ui_exit(string $title, string $message, int $status = 400, string $type = 'error', ?string $cta_href = '/', ?string $cta_label = null): void {
|
||||
function ui_exit(string $title, string $message, int $status = 400, string $type = 'error', ?string $cta_href = '/', ?string $cta_label = null): void
|
||||
{
|
||||
http_response_code($status);
|
||||
$title = htmlspecialchars($title, ENT_QUOTES, 'UTF-8');
|
||||
$message = nl2br(htmlspecialchars($message, ENT_QUOTES, 'UTF-8'));
|
||||
$cta_href = $cta_href ? htmlspecialchars($cta_href, ENT_QUOTES, 'UTF-8') : null;
|
||||
$cta_label = $cta_label ?? 'OK';
|
||||
|
||||
// Styles d'icône selon le type
|
||||
// Styles d'icône selon le type
|
||||
$iconBorder = '#B91C1C'; // rouge par défaut (error)
|
||||
$iconBg = 'rgba(185,28,28,.08)';
|
||||
$iconRing = 'rgba(185,28,28,.25)';
|
||||
$checkCSS = ''; // pour success
|
||||
if ($type === 'info') {
|
||||
$iconBorder = '#1D4ED8';
|
||||
$iconBg = 'rgba(29,78,216,.08)';
|
||||
$iconRing = 'rgba(29,78,216,.25)';
|
||||
$iconBg = 'rgba(29,78,216,.08)';
|
||||
$iconRing = 'rgba(29,78,216,.25)';
|
||||
} elseif ($type === 'success') {
|
||||
$iconBorder = '#1E8050';
|
||||
$iconBg = 'rgba(30,128,80,.08)';
|
||||
$iconRing = 'rgba(30,128,80,.20)';
|
||||
$checkCSS = 'width:48px;height:36px;border:5px solid #1e8050;border-top:0;border-left:0;transform:rotate(45deg);';
|
||||
$iconBg = 'rgba(30,128,80,.08)';
|
||||
$iconRing = 'rgba(30,128,80,.20)';
|
||||
$checkCSS = 'width:48px;height:36px;border:5px solid #1e8050;border-top:0;border-left:0;transform:rotate(45deg);';
|
||||
}
|
||||
|
||||
echo '<!DOCTYPE html><html lang="fr"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">';
|
||||
echo '<title>'.$title.'</title>';
|
||||
echo '<title>' . $title . '</title>';
|
||||
echo '<style>
|
||||
html,body{height:100%;margin:0}
|
||||
body{display:flex;align-items:center;justify-content:center;background:#8CA9FF;font-family:Montserrat,system-ui,Segoe UI,Roboto,Arial}
|
||||
@@ -44,26 +45,26 @@ function ui_exit(string $title, string $message, int $status = 400, string $type
|
||||
</style></head><body><div class="card">';
|
||||
|
||||
// Icône
|
||||
echo '<div style="width:102px;height:102px;border-radius:50%;background:'.$iconBg.';border:3px solid '.$iconRing.';display:flex;align-items:center;justify-content:center;margin:0 auto 20px;">';
|
||||
echo '<div style="width:102px;height:102px;border-radius:50%;background:' . $iconBg . ';border:3px solid ' . $iconRing . ';display:flex;align-items:center;justify-content:center;margin:0 auto 20px;">';
|
||||
if ($type === 'success') {
|
||||
echo '<div style="'.$checkCSS.'"></div>';
|
||||
echo '<div style="' . $checkCSS . '"></div>';
|
||||
} elseif ($type === 'info') {
|
||||
echo '<div style="width:14px;height:48px;border-left:5px solid '.$iconBorder.';position:relative;">
|
||||
<div style="position:absolute;top:-18px;left:-5px;width:10px;height:10px;background:'.$iconBorder.';border-radius:50%;"></div>
|
||||
echo '<div style="width:14px;height:48px;border-left:5px solid ' . $iconBorder . ';position:relative;">
|
||||
<div style="position:absolute;top:-18px;left:-5px;width:10px;height:10px;background:' . $iconBorder . ';border-radius:50%;"></div>
|
||||
</div>';
|
||||
} else { // error
|
||||
echo '<div style="position:relative;width:48px;height:48px;">
|
||||
<div style="position:absolute;top:0;left:22px;width:5px;height:48px;background:'.$iconBorder.';transform:rotate(45deg);border-radius:2px;"></div>
|
||||
<div style="position:absolute;top:0;left:22px;width:5px;height:48px;background:'.$iconBorder.';transform:rotate(-45deg);border-radius:2px;"></div>
|
||||
<div style="position:absolute;top:0;left:22px;width:5px;height:48px;background:' . $iconBorder . ';transform:rotate(45deg);border-radius:2px;"></div>
|
||||
<div style="position:absolute;top:0;left:22px;width:5px;height:48px;background:' . $iconBorder . ';transform:rotate(-45deg);border-radius:2px;"></div>
|
||||
</div>';
|
||||
}
|
||||
echo '</div>';
|
||||
|
||||
echo '<h1>'.$title.'</h1>';
|
||||
echo '<p>'.$message.'</p>';
|
||||
echo '<h1>' . $title . '</h1>';
|
||||
echo '<p>' . $message . '</p>';
|
||||
|
||||
if ($cta_href) {
|
||||
echo '<div class="row"><a class="btn btn-outline" href="'.$cta_href.'">'.$cta_label.'</a></div>';
|
||||
echo '<div class="row"><a class="btn btn-outline" href="' . $cta_href . '">' . $cta_label . '</a></div>';
|
||||
}
|
||||
|
||||
echo '</div></body></html>';
|
||||
@@ -76,7 +77,7 @@ function ui_exit(string $title, string $message, int $status = 400, string $type
|
||||
if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
|
||||
ui_exit(
|
||||
$translations['err_acces_incorrect_titre'] ?? 'Accès incorrect',
|
||||
$translations['err_acces_incorrect_msg'] ?? "Cette page doit être appelée depuis le formulaire d'inscription.",
|
||||
$translations['err_acces_incorrect_msg'] ?? "Cette page doit être appelée depuis le formulaire d'inscription.",
|
||||
405,
|
||||
'info',
|
||||
'inscription_client.php',
|
||||
@@ -105,7 +106,7 @@ if ($nom === '' || $prenom === '' || $email === '' || $mdp === '') {
|
||||
}
|
||||
|
||||
// Email valide + pas d’injection CR/LF
|
||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL) || preg_match('/[\r\n]/', $email)) {
|
||||
if (! filter_var($email, FILTER_VALIDATE_EMAIL) || preg_match('/[\r\n]/', $email)) {
|
||||
ui_exit(
|
||||
$translations['err_titre'] ?? 'Un problème est survenu',
|
||||
$translations['err_email_non_valide'] ?? 'Email invalide.',
|
||||
@@ -153,38 +154,60 @@ try {
|
||||
'nom' => $nom,
|
||||
'prenom' => $prenom,
|
||||
'email' => $email,
|
||||
'accepted_cgu' => $accept_cgu
|
||||
'accepted_cgu' => $accept_cgu,
|
||||
]);
|
||||
$client_id = (int)$pdo->lastInsertId();
|
||||
$client_id = (int) $pdo->lastInsertId();
|
||||
|
||||
// 2) Créer compte
|
||||
$mot_de_passe_hash = password_hash($mdp, PASSWORD_DEFAULT);
|
||||
$token = bin2hex(random_bytes(32));
|
||||
$token_autologin = bin2hex(random_bytes(32));
|
||||
$token_autologin_expire = (new DateTime('+60 minutes'))->format('Y-m-d H:i:s');
|
||||
|
||||
$mot_de_passe_hash = password_hash($mdp, PASSWORD_DEFAULT);
|
||||
$token = bin2hex(random_bytes(32));
|
||||
$token_validation_expire = (new DateTime('+6 hours'))->format('Y-m-d H:i:s');
|
||||
$token_autologin = bin2hex(random_bytes(32));
|
||||
$token_autologin_expire = (new DateTime('+60 minutes'))->format('Y-m-d H:i:s');
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO compte (email, mot_de_passe, role, reference_id, token_validation, email_valide, token_autologin, token_autologin_expire)
|
||||
VALUES (:email, :mdp, 'client', :ref_id, :token, 0, :al, :al_exp)
|
||||
INSERT INTO compte (
|
||||
email,
|
||||
mot_de_passe,
|
||||
role,
|
||||
reference_id,
|
||||
token_validation,
|
||||
token_validation_expire,
|
||||
email_valide,
|
||||
token_autologin,
|
||||
token_autologin_expire
|
||||
)
|
||||
VALUES (
|
||||
:email,
|
||||
:mdp,
|
||||
'client',
|
||||
:ref_id,
|
||||
:token,
|
||||
:token_expire,
|
||||
0,
|
||||
:al,
|
||||
:al_exp
|
||||
)
|
||||
");
|
||||
$stmt->execute([
|
||||
'email' => $email,
|
||||
'mdp' => $mot_de_passe_hash,
|
||||
'ref_id' => $client_id,
|
||||
'token' => $token,
|
||||
'al' => $token_autologin,
|
||||
'al_exp' => $token_autologin_expire,
|
||||
]);
|
||||
|
||||
|
||||
$stmt->execute([
|
||||
'email' => $email,
|
||||
'mdp' => $mot_de_passe_hash,
|
||||
'ref_id' => $client_id,
|
||||
'token' => $token,
|
||||
'token_expire' => $token_validation_expire,
|
||||
'al' => $token_autologin,
|
||||
'al_exp' => $token_autologin_expire,
|
||||
]);
|
||||
|
||||
// Commit avant l’envoi du mail
|
||||
$pdo->commit();
|
||||
|
||||
} catch (Throwable $e) {
|
||||
if ($pdo->inTransaction()) $pdo->rollBack();
|
||||
error_log('Inscription client - DB error: '.$e->getMessage());
|
||||
if ($pdo->inTransaction()) {
|
||||
$pdo->rollBack();
|
||||
}
|
||||
|
||||
error_log('Inscription client - DB error: ' . $e->getMessage());
|
||||
ui_exit(
|
||||
$translations['err_titre'] ?? 'Un problème est survenu',
|
||||
$translations['err_serveur'] ?? 'Erreur serveur.',
|
||||
@@ -197,7 +220,7 @@ $stmt->execute([
|
||||
// =====================
|
||||
// Email de validation
|
||||
// =====================
|
||||
$validation_url = "https://uslugi.fr/validation.php?token=" . urlencode($token) . "&al=" . urlencode($token_autologin);
|
||||
$validation_url = "https://uslugi.fr/validation.php?token=" . urlencode($token) . "&al=" . urlencode($token_autologin);
|
||||
$validation_url_html = htmlspecialchars($validation_url, ENT_QUOTES, 'UTF-8');
|
||||
$prenom_nom_safe = htmlspecialchars("$prenom $nom", ENT_QUOTES, 'UTF-8');
|
||||
|
||||
@@ -220,31 +243,31 @@ $html = "<!DOCTYPE html>
|
||||
</head>
|
||||
<body>
|
||||
<div class='mail-container'>
|
||||
<div class='title'>".sprintf($translations['mail_valid_bonjour_nom'], $prenom_nom_safe)."</div>
|
||||
<p>".$translations['mail_valid_merci']."</p>
|
||||
<p>".$translations['mail_valid_action']."</p>
|
||||
<a href='$validation_url_html' class='btn'>".$translations['mail_valid_btn']."</a>
|
||||
<div class='title'>" . sprintf($translations['mail_valid_bonjour_nom'], $prenom_nom_safe) . "</div>
|
||||
<p>" . $translations['mail_valid_merci'] . "</p>
|
||||
<p>" . $translations['mail_valid_action'] . "</p>
|
||||
<a href='$validation_url_html' class='btn'>" . $translations['mail_valid_btn'] . "</a>
|
||||
<p class='link-fallback'>
|
||||
".$translations['mail_valid_fallback']."<br>
|
||||
" . $translations['mail_valid_fallback'] . "<br>
|
||||
<a href='$validation_url_html'>$validation_url_html</a>
|
||||
</p>
|
||||
<div class='footer'>
|
||||
".$translations['mail_valid_expire']."<br>
|
||||
".$translations['mail_valid_ignore']."<br><br>
|
||||
".$translations['mail_valid_signature']."
|
||||
" . $translations['mail_valid_expire'] . "<br>
|
||||
" . $translations['mail_valid_ignore'] . "<br><br>
|
||||
" . $translations['mail_valid_signature'] . "
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>";
|
||||
|
||||
$text = strip_tags(
|
||||
sprintf($translations['mail_valid_bonjour_nom'], "$prenom $nom")."\n\n".
|
||||
$translations['mail_valid_merci']."\n".
|
||||
$translations['mail_valid_action']."\n".
|
||||
($translations['mail_valid_fallback'] ?? 'Si le bouton ne fonctionne pas, utilisez ce lien :')."\n".
|
||||
$validation_url."\n\n".
|
||||
$translations['mail_valid_expire']."\n".
|
||||
$translations['mail_valid_ignore']."\n".
|
||||
sprintf($translations['mail_valid_bonjour_nom'], "$prenom $nom") . "\n\n" .
|
||||
$translations['mail_valid_merci'] . "\n" .
|
||||
$translations['mail_valid_action'] . "\n" .
|
||||
($translations['mail_valid_fallback'] ?? 'Si le bouton ne fonctionne pas, utilisez ce lien :') . "\n" .
|
||||
$validation_url . "\n\n" .
|
||||
$translations['mail_valid_expire'] . "\n" .
|
||||
$translations['mail_valid_ignore'] . "\n" .
|
||||
($translations['mail_valid_signature'] ?? 'USLUGI.FR')
|
||||
);
|
||||
|
||||
@@ -256,7 +279,7 @@ list($sent, $err) = uslugi_mail(
|
||||
$text,
|
||||
['to_name' => "$prenom $nom"]
|
||||
);
|
||||
if (!$sent) {
|
||||
if (! $sent) {
|
||||
error_log("Validation mail NOT sent to $email : $err");
|
||||
// On ne bloque pas l'utilisateur.
|
||||
}
|
||||
@@ -266,7 +289,7 @@ if (!$sent) {
|
||||
// =====================
|
||||
echo '
|
||||
<!DOCTYPE html><html lang="fr"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>'.htmlspecialchars($translations['insc_merci_titre'] ?? 'Merci !', ENT_QUOTES, 'UTF-8').'</title>
|
||||
<title>' . htmlspecialchars($translations['insc_merci_titre'] ?? 'Merci !', ENT_QUOTES, 'UTF-8') . '</title>
|
||||
<style>
|
||||
html,body{height:100%;margin:0}
|
||||
body{display:flex;align-items:center;justify-content:center;background:#8CA9FF;font-family:Montserrat,system-ui,Segoe UI,Roboto,Arial}
|
||||
@@ -282,11 +305,11 @@ echo '
|
||||
<div style="width:102px;height:102px;border-radius:50%;background:rgba(30,128,80,.08);border:3px solid rgba(30,128,80,.20);display:flex;align-items:center;justify-content:center;margin:0 auto 20px;">
|
||||
<div style="width:48px;height:36px;border:5px solid #1e8050;border-top:0;border-left:0;transform:rotate(45deg);"></div>
|
||||
</div>
|
||||
<h1>'.htmlspecialchars($translations['insc_merci_titre'] ?? 'Merci !', ENT_QUOTES, 'UTF-8').'</h1>
|
||||
<p>'.htmlspecialchars($translations['insc_merci_ligne1'] ?? 'Votre compte a bien été créé.', ENT_QUOTES, 'UTF-8').'</p>
|
||||
<p>'.htmlspecialchars($translations['insc_merci_ligne2'] ?? 'Vérifiez vos emails pour valider votre adresse.', ENT_QUOTES, 'UTF-8').'</p>
|
||||
<h1>' . htmlspecialchars($translations['insc_merci_titre'] ?? 'Merci !', ENT_QUOTES, 'UTF-8') . '</h1>
|
||||
<p>' . htmlspecialchars($translations['insc_merci_ligne1'] ?? 'Votre compte a bien été créé.', ENT_QUOTES, 'UTF-8') . '</p>
|
||||
<p>' . htmlspecialchars($translations['insc_merci_ligne2'] ?? 'Vérifiez vos emails pour valider votre adresse.', ENT_QUOTES, 'UTF-8') . '</p>
|
||||
<div class="row">
|
||||
<a class="btn btn-outline" href="/">'.htmlspecialchars($translations['retour_acceuil'] ?? 'Retour à l’accueil', ENT_QUOTES, 'UTF-8').'</a>
|
||||
<a class="btn btn-outline" href="/">' . htmlspecialchars($translations['retour_acceuil'] ?? 'Retour à l’accueil', ENT_QUOTES, 'UTF-8') . '</a>
|
||||
</div>
|
||||
</div>
|
||||
</body></html>';
|
||||
@@ -0,0 +1,292 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/../includes/bootstrap_global.php';
|
||||
|
||||
/**
|
||||
* UI helper: affiche une jolie carte centrée puis exit.
|
||||
* $type: 'error' | 'info' | 'success'
|
||||
*/
|
||||
function ui_exit(string $title, string $message, int $status = 400, string $type = 'error', ?string $cta_href = '/', ?string $cta_label = null): void {
|
||||
http_response_code($status);
|
||||
$title = htmlspecialchars($title, ENT_QUOTES, 'UTF-8');
|
||||
$message = nl2br(htmlspecialchars($message, ENT_QUOTES, 'UTF-8'));
|
||||
$cta_href = $cta_href ? htmlspecialchars($cta_href, ENT_QUOTES, 'UTF-8') : null;
|
||||
$cta_label = $cta_label ?? 'OK';
|
||||
|
||||
// Styles d'icône selon le type
|
||||
$iconBorder = '#B91C1C'; // rouge par défaut (error)
|
||||
$iconBg = 'rgba(185,28,28,.08)';
|
||||
$iconRing = 'rgba(185,28,28,.25)';
|
||||
$checkCSS = ''; // pour success
|
||||
if ($type === 'info') {
|
||||
$iconBorder = '#1D4ED8';
|
||||
$iconBg = 'rgba(29,78,216,.08)';
|
||||
$iconRing = 'rgba(29,78,216,.25)';
|
||||
} elseif ($type === 'success') {
|
||||
$iconBorder = '#1E8050';
|
||||
$iconBg = 'rgba(30,128,80,.08)';
|
||||
$iconRing = 'rgba(30,128,80,.20)';
|
||||
$checkCSS = 'width:48px;height:36px;border:5px solid #1e8050;border-top:0;border-left:0;transform:rotate(45deg);';
|
||||
}
|
||||
|
||||
echo '<!DOCTYPE html><html lang="fr"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">';
|
||||
echo '<title>'.$title.'</title>';
|
||||
echo '<style>
|
||||
html,body{height:100%;margin:0}
|
||||
body{display:flex;align-items:center;justify-content:center;background:#8CA9FF;font-family:Montserrat,system-ui,Segoe UI,Roboto,Arial}
|
||||
.card{max-width:720px;width:94%;background:#fff;border:1px solid #e5e7eb;border-radius:14px;box-shadow:0 8px 24px rgba(0,0,0,.08);padding:28px;text-align:center}
|
||||
h1{margin:6px 0 10px;font-size:clamp(22px,2.4vw,28px)}
|
||||
p{margin:0 0 18px;color:#6b7280;font-size:clamp(16px,2.2vw,18px)}
|
||||
.row{display:flex;gap:10px;justify-content:center;flex-wrap:wrap;margin-top:8px}
|
||||
.btn{padding:10px 14px;border-radius:10px;text-decoration:none;display:inline-block}
|
||||
.btn-primary{background:#2563eb;color:#fff}
|
||||
.btn-outline{border:1px solid #d1d5db;color:#374151;background:#fff}
|
||||
</style></head><body><div class="card">';
|
||||
|
||||
// Icône
|
||||
echo '<div style="width:102px;height:102px;border-radius:50%;background:'.$iconBg.';border:3px solid '.$iconRing.';display:flex;align-items:center;justify-content:center;margin:0 auto 20px;">';
|
||||
if ($type === 'success') {
|
||||
echo '<div style="'.$checkCSS.'"></div>';
|
||||
} elseif ($type === 'info') {
|
||||
echo '<div style="width:14px;height:48px;border-left:5px solid '.$iconBorder.';position:relative;">
|
||||
<div style="position:absolute;top:-18px;left:-5px;width:10px;height:10px;background:'.$iconBorder.';border-radius:50%;"></div>
|
||||
</div>';
|
||||
} else { // error
|
||||
echo '<div style="position:relative;width:48px;height:48px;">
|
||||
<div style="position:absolute;top:0;left:22px;width:5px;height:48px;background:'.$iconBorder.';transform:rotate(45deg);border-radius:2px;"></div>
|
||||
<div style="position:absolute;top:0;left:22px;width:5px;height:48px;background:'.$iconBorder.';transform:rotate(-45deg);border-radius:2px;"></div>
|
||||
</div>';
|
||||
}
|
||||
echo '</div>';
|
||||
|
||||
echo '<h1>'.$title.'</h1>';
|
||||
echo '<p>'.$message.'</p>';
|
||||
|
||||
if ($cta_href) {
|
||||
echo '<div class="row"><a class="btn btn-outline" href="'.$cta_href.'">'.$cta_label.'</a></div>';
|
||||
}
|
||||
|
||||
echo '</div></body></html>';
|
||||
exit;
|
||||
}
|
||||
|
||||
// =====================
|
||||
// Accès direct interdit
|
||||
// =====================
|
||||
if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
|
||||
ui_exit(
|
||||
$translations['err_acces_incorrect_titre'] ?? 'Accès incorrect',
|
||||
$translations['err_acces_incorrect_msg'] ?? "Cette page doit être appelée depuis le formulaire d'inscription.",
|
||||
405,
|
||||
'info',
|
||||
'inscription_client.php',
|
||||
$translations['retour_formulaire'] ?? 'Retour au formulaire'
|
||||
);
|
||||
}
|
||||
|
||||
// =====================
|
||||
// Données & vérifs
|
||||
// =====================
|
||||
$nom = trim($_POST['nom'] ?? '');
|
||||
$prenom = trim($_POST['prenom'] ?? '');
|
||||
$email = trim($_POST['email'] ?? '');
|
||||
$mdp = $_POST['mot_de_passe'] ?? '';
|
||||
|
||||
// Champs obligatoires
|
||||
if ($nom === '' || $prenom === '' || $email === '' || $mdp === '') {
|
||||
ui_exit(
|
||||
$translations['err_titre'] ?? 'Un problème est survenu',
|
||||
$translations['err_champs_obligatoires'] ?? 'Merci de remplir tous les champs obligatoires.',
|
||||
400,
|
||||
'error',
|
||||
'inscription_client.php',
|
||||
$translations['retour_formulaire'] ?? 'Retour au formulaire'
|
||||
);
|
||||
}
|
||||
|
||||
// Email valide + pas d’injection CR/LF
|
||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL) || preg_match('/[\r\n]/', $email)) {
|
||||
ui_exit(
|
||||
$translations['err_titre'] ?? 'Un problème est survenu',
|
||||
$translations['err_email_non_valide'] ?? 'Email invalide.',
|
||||
400,
|
||||
'error',
|
||||
'inscription_client.php'
|
||||
);
|
||||
}
|
||||
|
||||
// Acceptation CGU
|
||||
$accept_cgu = isset($_POST['accept_cgu']) ? 1 : 0;
|
||||
if ($accept_cgu !== 1) {
|
||||
ui_exit(
|
||||
$translations['err_titre'] ?? 'Un problème est survenu',
|
||||
$translations['cgu_obligatoire'] ?? 'Vous devez accepter les CGU.',
|
||||
400,
|
||||
'info',
|
||||
'inscription_client.php'
|
||||
);
|
||||
}
|
||||
|
||||
// =====================
|
||||
// Transaction (client + compte)
|
||||
// =====================
|
||||
try {
|
||||
$pdo->beginTransaction();
|
||||
|
||||
// Email déjà pris ?
|
||||
$stmt = $pdo->prepare("SELECT id FROM compte WHERE email = :email LIMIT 1");
|
||||
$stmt->execute(['email' => $email]);
|
||||
if ($stmt->fetch()) {
|
||||
$pdo->rollBack();
|
||||
ui_exit(
|
||||
$translations['err_titre'] ?? 'Un problème est survenu',
|
||||
$translations['err_email_deja_utilise'] ?? 'Cet email est déjà utilisé.',
|
||||
409,
|
||||
'error',
|
||||
'inscription_client.php'
|
||||
);
|
||||
}
|
||||
|
||||
// 1) Insert client
|
||||
$stmt = $pdo->prepare("INSERT INTO client (nom, prenom, email, accepted_cgu) VALUES (:nom, :prenom, :email, :accepted_cgu)");
|
||||
$stmt->execute([
|
||||
'nom' => $nom,
|
||||
'prenom' => $prenom,
|
||||
'email' => $email,
|
||||
'accepted_cgu' => $accept_cgu
|
||||
]);
|
||||
$client_id = (int)$pdo->lastInsertId();
|
||||
|
||||
// 2) Créer compte
|
||||
$mot_de_passe_hash = password_hash($mdp, PASSWORD_DEFAULT);
|
||||
$token = bin2hex(random_bytes(32));
|
||||
$token_autologin = bin2hex(random_bytes(32));
|
||||
$token_autologin_expire = (new DateTime('+60 minutes'))->format('Y-m-d H:i:s');
|
||||
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO compte (email, mot_de_passe, role, reference_id, token_validation, email_valide, token_autologin, token_autologin_expire)
|
||||
VALUES (:email, :mdp, 'client', :ref_id, :token, 0, :al, :al_exp)
|
||||
");
|
||||
$stmt->execute([
|
||||
'email' => $email,
|
||||
'mdp' => $mot_de_passe_hash,
|
||||
'ref_id' => $client_id,
|
||||
'token' => $token,
|
||||
'al' => $token_autologin,
|
||||
'al_exp' => $token_autologin_expire,
|
||||
]);
|
||||
|
||||
|
||||
|
||||
// Commit avant l’envoi du mail
|
||||
$pdo->commit();
|
||||
|
||||
} catch (Throwable $e) {
|
||||
if ($pdo->inTransaction()) $pdo->rollBack();
|
||||
error_log('Inscription client - DB error: '.$e->getMessage());
|
||||
ui_exit(
|
||||
$translations['err_titre'] ?? 'Un problème est survenu',
|
||||
$translations['err_serveur'] ?? 'Erreur serveur.',
|
||||
500,
|
||||
'error',
|
||||
'/'
|
||||
);
|
||||
}
|
||||
|
||||
// =====================
|
||||
// Email de validation
|
||||
// =====================
|
||||
$validation_url = "https://uslugi.fr/validation.php?token=" . urlencode($token) . "&al=" . urlencode($token_autologin);
|
||||
$validation_url_html = htmlspecialchars($validation_url, ENT_QUOTES, 'UTF-8');
|
||||
$prenom_nom_safe = htmlspecialchars("$prenom $nom", ENT_QUOTES, 'UTF-8');
|
||||
|
||||
$sujet = $translations['mail_valid_sujet'];
|
||||
|
||||
$html = "<!DOCTYPE html>
|
||||
<html lang='fr'>
|
||||
<head>
|
||||
<meta charset='UTF-8'>
|
||||
<meta http-equiv='X-UA-Compatible' content='IE=edge'>
|
||||
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; background-color:#f4f4f4; padding:20px; }
|
||||
.mail-container { max-width:600px; margin:auto; background:#fff; padding:30px; border-radius:8px; box-shadow:0 0 10px rgba(0,0,0,0.1); }
|
||||
.title { font-size:20px; font-weight:bold; color:#333; margin-bottom:20px; }
|
||||
.btn { display:inline-block; margin-top:20px; padding:12px 24px; background-color:#E03C3F; color:#fff; text-decoration:none; border-radius:999px; font-weight:bold; }
|
||||
.footer { margin-top:30px; font-size:13px; color:#888; }
|
||||
.link-fallback { word-break: break-all; font-size:13px; margin-top:15px; color:#555; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class='mail-container'>
|
||||
<div class='title'>".sprintf($translations['mail_valid_bonjour_nom'], $prenom_nom_safe)."</div>
|
||||
<p>".$translations['mail_valid_merci']."</p>
|
||||
<p>".$translations['mail_valid_action']."</p>
|
||||
<a href='$validation_url_html' class='btn'>".$translations['mail_valid_btn']."</a>
|
||||
<p class='link-fallback'>
|
||||
".$translations['mail_valid_fallback']."<br>
|
||||
<a href='$validation_url_html'>$validation_url_html</a>
|
||||
</p>
|
||||
<div class='footer'>
|
||||
".$translations['mail_valid_expire']."<br>
|
||||
".$translations['mail_valid_ignore']."<br><br>
|
||||
".$translations['mail_valid_signature']."
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>";
|
||||
|
||||
$text = strip_tags(
|
||||
sprintf($translations['mail_valid_bonjour_nom'], "$prenom $nom")."\n\n".
|
||||
$translations['mail_valid_merci']."\n".
|
||||
$translations['mail_valid_action']."\n".
|
||||
($translations['mail_valid_fallback'] ?? 'Si le bouton ne fonctionne pas, utilisez ce lien :')."\n".
|
||||
$validation_url."\n\n".
|
||||
$translations['mail_valid_expire']."\n".
|
||||
$translations['mail_valid_ignore']."\n".
|
||||
($translations['mail_valid_signature'] ?? 'USLUGI.FR')
|
||||
);
|
||||
|
||||
// Envoi du mail (non bloquant)
|
||||
list($sent, $err) = uslugi_mail(
|
||||
$email,
|
||||
$sujet,
|
||||
$html,
|
||||
$text,
|
||||
['to_name' => "$prenom $nom"]
|
||||
);
|
||||
if (!$sent) {
|
||||
error_log("Validation mail NOT sent to $email : $err");
|
||||
// On ne bloque pas l'utilisateur.
|
||||
}
|
||||
|
||||
// =====================
|
||||
// Écran final (succès)
|
||||
// =====================
|
||||
echo '
|
||||
<!DOCTYPE html><html lang="fr"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>'.htmlspecialchars($translations['insc_merci_titre'] ?? 'Merci !', ENT_QUOTES, 'UTF-8').'</title>
|
||||
<style>
|
||||
html,body{height:100%;margin:0}
|
||||
body{display:flex;align-items:center;justify-content:center;background:#8CA9FF;font-family:Montserrat,system-ui,Segoe UI,Roboto,Arial}
|
||||
.card{max-width:600px;width:98%;background:#fff;border:1px solid #e5e7eb;border-radius:14px;box-shadow:0 8px 24px rgba(0,0,0,.08);padding:28px;text-align:center}
|
||||
h1{margin:6px 0 10px;font-size:clamp(22px,2.4vw,28px)}
|
||||
p{margin:0 0 18px;color:#6b7280;font-size:clamp(16px,2.2vw,18px)}
|
||||
.row{display:flex;gap:10px;justify-content:center;flex-wrap:wrap}
|
||||
.btn{padding:10px 14px;border-radius:10px;text-decoration:none;display:inline-block}
|
||||
.btn-primary{background:#2563eb;color:#fff}
|
||||
.btn-outline{border:1px solid #d1d5db;color:#374151;background:#fff}
|
||||
</style></head><body>
|
||||
<div class="card">
|
||||
<div style="width:102px;height:102px;border-radius:50%;background:rgba(30,128,80,.08);border:3px solid rgba(30,128,80,.20);display:flex;align-items:center;justify-content:center;margin:0 auto 20px;">
|
||||
<div style="width:48px;height:36px;border:5px solid #1e8050;border-top:0;border-left:0;transform:rotate(45deg);"></div>
|
||||
</div>
|
||||
<h1>'.htmlspecialchars($translations['insc_merci_titre'] ?? 'Merci !', ENT_QUOTES, 'UTF-8').'</h1>
|
||||
<p>'.htmlspecialchars($translations['insc_merci_ligne1'] ?? 'Votre compte a bien été créé.', ENT_QUOTES, 'UTF-8').'</p>
|
||||
<p>'.htmlspecialchars($translations['insc_merci_ligne2'] ?? 'Vérifiez vos emails pour valider votre adresse.', ENT_QUOTES, 'UTF-8').'</p>
|
||||
<div class="row">
|
||||
<a class="btn btn-outline" href="/">'.htmlspecialchars($translations['retour_acceuil'] ?? 'Retour à l’accueil', ENT_QUOTES, 'UTF-8').'</a>
|
||||
</div>
|
||||
</div>
|
||||
</body></html>';
|
||||
@@ -1,36 +1,37 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
declare (strict_types = 1);
|
||||
require_once __DIR__ . '/../includes/bootstrap_global.php';
|
||||
|
||||
/**
|
||||
* UI helper: affiche une jolie carte centrée puis exit.
|
||||
* $type: 'error' | 'info' | 'success'
|
||||
*/
|
||||
function ui_exit(string $title, string $message, int $status = 400, string $type = 'error', ?string $cta_href = '/', ?string $cta_label = null): void {
|
||||
function ui_exit(string $title, string $message, int $status = 400, string $type = 'error', ?string $cta_href = '/', ?string $cta_label = null): void
|
||||
{
|
||||
http_response_code($status);
|
||||
$title = htmlspecialchars($title, ENT_QUOTES, 'UTF-8');
|
||||
$message = nl2br(htmlspecialchars($message, ENT_QUOTES, 'UTF-8'));
|
||||
$title = htmlspecialchars($title, ENT_QUOTES, 'UTF-8');
|
||||
$message = nl2br(htmlspecialchars($message, ENT_QUOTES, 'UTF-8'));
|
||||
$cta_href = $cta_href ? htmlspecialchars($cta_href, ENT_QUOTES, 'UTF-8') : null;
|
||||
$cta_label = $cta_label ?? 'OK';
|
||||
|
||||
// Styles d'icône selon le type
|
||||
// Styles d'icône selon le type
|
||||
$iconBorder = '#B91C1C'; // rouge par défaut (error)
|
||||
$iconBg = 'rgba(185,28,28,.08)';
|
||||
$iconRing = 'rgba(185,28,28,.25)';
|
||||
$checkCSS = ''; // pour success
|
||||
if ($type === 'info') {
|
||||
$iconBorder = '#1D4ED8';
|
||||
$iconBg = 'rgba(29,78,216,.08)';
|
||||
$iconRing = 'rgba(29,78,216,.25)';
|
||||
$iconBg = 'rgba(29,78,216,.08)';
|
||||
$iconRing = 'rgba(29,78,216,.25)';
|
||||
} elseif ($type === 'success') {
|
||||
$iconBorder = '#1E8050';
|
||||
$iconBg = 'rgba(30,128,80,.08)';
|
||||
$iconRing = 'rgba(30,128,80,.20)';
|
||||
$checkCSS = 'width:48px;height:36px;border:5px solid #1e8050;border-top:0;border-left:0;transform:rotate(45deg);';
|
||||
$iconBg = 'rgba(30,128,80,.08)';
|
||||
$iconRing = 'rgba(30,128,80,.20)';
|
||||
$checkCSS = 'width:48px;height:36px;border:5px solid #1e8050;border-top:0;border-left:0;transform:rotate(45deg);';
|
||||
}
|
||||
|
||||
echo '<!DOCTYPE html><html lang="fr"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">';
|
||||
echo '<title>'.$title.'</title>';
|
||||
echo '<title>' . $title . '</title>';
|
||||
echo '<style>
|
||||
html,body{height:100%;margin:0}
|
||||
body{display:flex;align-items:center;justify-content:center;background:#8CA9FF;font-family:Montserrat,system-ui,Segoe UI,Roboto,Arial}
|
||||
@@ -44,26 +45,26 @@ function ui_exit(string $title, string $message, int $status = 400, string $type
|
||||
</style></head><body><div class="card">';
|
||||
|
||||
// Icône
|
||||
echo '<div style="width:102px;height:102px;border-radius:50%;background:'.$iconBg.';border:3px solid '.$iconRing.';display:flex;align-items:center;justify-content:center;margin:0 auto 20px;">';
|
||||
echo '<div style="width:102px;height:102px;border-radius:50%;background:' . $iconBg . ';border:3px solid ' . $iconRing . ';display:flex;align-items:center;justify-content:center;margin:0 auto 20px;">';
|
||||
if ($type === 'success') {
|
||||
echo '<div style="'.$checkCSS.'"></div>';
|
||||
echo '<div style="' . $checkCSS . '"></div>';
|
||||
} elseif ($type === 'info') {
|
||||
echo '<div style="width:14px;height:48px;border-left:5px solid '.$iconBorder.';position:relative;">
|
||||
<div style="position:absolute;top:-18px;left:-5px;width:10px;height:10px;background:'.$iconBorder.';border-radius:50%;"></div>
|
||||
echo '<div style="width:14px;height:48px;border-left:5px solid ' . $iconBorder . ';position:relative;">
|
||||
<div style="position:absolute;top:-18px;left:-5px;width:10px;height:10px;background:' . $iconBorder . ';border-radius:50%;"></div>
|
||||
</div>';
|
||||
} else { // error
|
||||
echo '<div style="position:relative;width:48px;height:48px;">
|
||||
<div style="position:absolute;top:0;left:22px;width:5px;height:48px;background:'.$iconBorder.';transform:rotate(45deg);border-radius:2px;"></div>
|
||||
<div style="position:absolute;top:0;left:22px;width:5px;height:48px;background:'.$iconBorder.';transform:rotate(-45deg);border-radius:2px;"></div>
|
||||
<div style="position:absolute;top:0;left:22px;width:5px;height:48px;background:' . $iconBorder . ';transform:rotate(45deg);border-radius:2px;"></div>
|
||||
<div style="position:absolute;top:0;left:22px;width:5px;height:48px;background:' . $iconBorder . ';transform:rotate(-45deg);border-radius:2px;"></div>
|
||||
</div>';
|
||||
}
|
||||
echo '</div>';
|
||||
|
||||
echo '<h1>'.$title.'</h1>';
|
||||
echo '<p>'.$message.'</p>';
|
||||
echo '<h1>' . $title . '</h1>';
|
||||
echo '<p>' . $message . '</p>';
|
||||
|
||||
if ($cta_href) {
|
||||
echo '<div class="row"><a class="btn btn-outline" href="'.$cta_href.'">'.$cta_label.'</a></div>';
|
||||
echo '<div class="row"><a class="btn btn-outline" href="' . $cta_href . '">' . $cta_label . '</a></div>';
|
||||
}
|
||||
|
||||
echo '</div></body></html>';
|
||||
@@ -76,7 +77,7 @@ function ui_exit(string $title, string $message, int $status = 400, string $type
|
||||
if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
|
||||
ui_exit(
|
||||
$translations['err_acces_incorrect_titre'] ?? 'Accès incorrect',
|
||||
$translations['err_acces_incorrect_msg'] ?? "Cette page doit être appelée depuis le formulaire d'inscription.",
|
||||
$translations['err_acces_incorrect_msg'] ?? "Cette page doit être appelée depuis le formulaire d'inscription.",
|
||||
405,
|
||||
'info',
|
||||
'inscription_praticien.php',
|
||||
@@ -106,7 +107,7 @@ if ($nom === '' || $prenom === '' || $email === '' || $mdp === '') {
|
||||
}
|
||||
|
||||
// Email valide + pas d'injection d'entêtes
|
||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL) || preg_match('/[\r\n]/', $email)) {
|
||||
if (! filter_var($email, FILTER_VALIDATE_EMAIL) || preg_match('/[\r\n]/', $email)) {
|
||||
ui_exit(
|
||||
$translations['err_titre'] ?? 'Un problème est survenu',
|
||||
$translations['err_email_non_valide'] ?? 'Email invalide.',
|
||||
@@ -128,20 +129,24 @@ if (strlen($siret) !== 14) {
|
||||
}
|
||||
|
||||
// Luhn
|
||||
function luhnCheck($num) {
|
||||
function luhnCheck($num)
|
||||
{
|
||||
$arr = str_split(strrev($num));
|
||||
$sum = 0;
|
||||
foreach ($arr as $i => $n) {
|
||||
$n = (int)$n;
|
||||
$n = (int) $n;
|
||||
if ($i % 2) {
|
||||
$n *= 2;
|
||||
if ($n > 9) $n -= 9;
|
||||
if ($n > 9) {
|
||||
$n -= 9;
|
||||
}
|
||||
|
||||
}
|
||||
$sum += $n;
|
||||
}
|
||||
return $sum % 10 === 0;
|
||||
}
|
||||
if (!luhnCheck($siret)) {
|
||||
if (! luhnCheck($siret)) {
|
||||
ui_exit(
|
||||
$translations['err_titre'] ?? 'Un problème est survenu',
|
||||
$translations['err_siret_invalide'] ?? 'Numéro SIRET invalide (échec vérification).',
|
||||
@@ -199,66 +204,89 @@ try {
|
||||
}
|
||||
|
||||
// 1) Préparer la date d'hier pour forcer l'expiration immédiate
|
||||
$hier = date('Y-m-d', strtotime('-1 day'));
|
||||
$hier = date('Y-m-d', strtotime('-1 day'));
|
||||
|
||||
// 2) Créer le praticien avec les champs d'abonnement initialisés
|
||||
$stmt = $pdo->prepare("
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO praticien (
|
||||
nom,
|
||||
prenom,
|
||||
email,
|
||||
siret,
|
||||
accepted_cgu,
|
||||
abonnement_id,
|
||||
nom,
|
||||
prenom,
|
||||
email,
|
||||
siret,
|
||||
accepted_cgu,
|
||||
abonnement_id,
|
||||
date_expiration
|
||||
) VALUES (
|
||||
:nom,
|
||||
:prenom,
|
||||
:email,
|
||||
:siret,
|
||||
:accepted_cgu,
|
||||
NULL,
|
||||
:nom,
|
||||
:prenom,
|
||||
:email,
|
||||
:siret,
|
||||
:accepted_cgu,
|
||||
NULL,
|
||||
:date_exp
|
||||
)
|
||||
");
|
||||
|
||||
$stmt->execute([
|
||||
'nom' => $nom,
|
||||
'prenom' => $prenom,
|
||||
'email' => $email,
|
||||
'siret' => $siret,
|
||||
'accepted_cgu' => $accept_cgu,
|
||||
'date_exp' => $hier // On met hier pour que le script de vérification dise "C'est expiré !"
|
||||
]);
|
||||
$praticien_id = (int)$pdo->lastInsertId();
|
||||
$stmt->execute([
|
||||
'nom' => $nom,
|
||||
'prenom' => $prenom,
|
||||
'email' => $email,
|
||||
'siret' => $siret,
|
||||
'accepted_cgu' => $accept_cgu,
|
||||
'date_exp' => $hier, // On met hier pour que le script de vérification dise "C'est expiré !"
|
||||
]);
|
||||
$praticien_id = (int) $pdo->lastInsertId();
|
||||
|
||||
// 2) Créer le compte
|
||||
$mot_de_passe_hash = password_hash($mdp, PASSWORD_DEFAULT);
|
||||
$token = bin2hex(random_bytes(32));
|
||||
$token_autologin = bin2hex(random_bytes(32));
|
||||
$token_autologin_expire = (new DateTime('+60 minutes'))->format('Y-m-d H:i:s');
|
||||
|
||||
$mot_de_passe_hash = password_hash($mdp, PASSWORD_DEFAULT);
|
||||
$token = bin2hex(random_bytes(32));
|
||||
$token_validation_expire = (new DateTime('+6 hours'))->format('Y-m-d H:i:s');
|
||||
$token_autologin = bin2hex(random_bytes(32));
|
||||
$token_autologin_expire = (new DateTime('+60 minutes'))->format('Y-m-d H:i:s');
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO compte (email, mot_de_passe, role, reference_id, token_validation, email_valide, token_autologin, token_autologin_expire)
|
||||
VALUES (:email, :mdp, 'praticien', :ref_id, :token, 0, :al, :al_exp)
|
||||
INSERT INTO compte (
|
||||
email,
|
||||
mot_de_passe,
|
||||
role,
|
||||
reference_id,
|
||||
token_validation,
|
||||
token_validation_expire,
|
||||
email_valide,
|
||||
token_autologin,
|
||||
token_autologin_expire
|
||||
)
|
||||
VALUES (
|
||||
:email,
|
||||
:mdp,
|
||||
'praticien',
|
||||
:ref_id,
|
||||
:token,
|
||||
:token_expire,
|
||||
0,
|
||||
:al,
|
||||
:al_exp
|
||||
)
|
||||
");
|
||||
$stmt->execute([
|
||||
'email' => $email,
|
||||
'mdp' => $mot_de_passe_hash,
|
||||
'ref_id' => $praticien_id,
|
||||
'token' => $token,
|
||||
'al' => $token_autologin,
|
||||
'al_exp' => $token_autologin_expire,
|
||||
]);
|
||||
|
||||
$stmt->execute([
|
||||
'email' => $email,
|
||||
'mdp' => $mot_de_passe_hash,
|
||||
'ref_id' => $praticien_id,
|
||||
'token' => $token,
|
||||
'token_expire' => $token_validation_expire,
|
||||
'al' => $token_autologin,
|
||||
'al_exp' => $token_autologin_expire,
|
||||
]);
|
||||
|
||||
// Commit avant envoi mail
|
||||
$pdo->commit();
|
||||
|
||||
} catch (Throwable $e) {
|
||||
if ($pdo->inTransaction()) $pdo->rollBack();
|
||||
error_log('Inscription praticien - DB error: '.$e->getMessage());
|
||||
if ($pdo->inTransaction()) {
|
||||
$pdo->rollBack();
|
||||
}
|
||||
|
||||
error_log('Inscription praticien - DB error: ' . $e->getMessage());
|
||||
ui_exit(
|
||||
$translations['err_titre'] ?? 'Un problème est survenu',
|
||||
$translations['err_serveur'] ?? 'Erreur serveur.',
|
||||
@@ -271,7 +299,7 @@ $stmt->execute([
|
||||
// =====================
|
||||
// Email de validation
|
||||
// =====================
|
||||
$validation_url = "https://uslugi.fr/validation.php?token=" . urlencode($token) . "&al=" . urlencode($token_autologin);
|
||||
$validation_url = "https://uslugi.fr/validation.php?token=" . urlencode($token) . "&al=" . urlencode($token_autologin);
|
||||
$validation_url_html = htmlspecialchars($validation_url, ENT_QUOTES, 'UTF-8');
|
||||
$prenom_nom = htmlspecialchars("$prenom $nom", ENT_QUOTES, 'UTF-8');
|
||||
|
||||
@@ -295,17 +323,17 @@ $html = "<!DOCTYPE html>
|
||||
</head>
|
||||
<body>
|
||||
<div class='mail-container'>
|
||||
<div class='title'>".sprintf($translations['mail_valid_bonjour_nom'], $prenom_nom)."</div>
|
||||
<p>".$translations['mail_valid_merci_praticien']."</p>
|
||||
<p>".$translations['mail_valid_action']."</p>
|
||||
<a href='$validation_url_html' class='btn'>".$translations['mail_valid_btn']."</a>
|
||||
<p class='link-fallback'>".$translations['mail_valid_fallback']."<br>
|
||||
<div class='title'>" . sprintf($translations['mail_valid_bonjour_nom'], $prenom_nom) . "</div>
|
||||
<p>" . $translations['mail_valid_merci_praticien'] . "</p>
|
||||
<p>" . $translations['mail_valid_action'] . "</p>
|
||||
<a href='$validation_url_html' class='btn'>" . $translations['mail_valid_btn'] . "</a>
|
||||
<p class='link-fallback'>" . $translations['mail_valid_fallback'] . "<br>
|
||||
<a href='$validation_url_html'>$validation_url_html</a>
|
||||
</p>
|
||||
<div class='footer'>
|
||||
".$translations['mail_valid_expire']."<br>
|
||||
".$translations['mail_valid_ignore']."<br><br>
|
||||
".$translations['mail_valid_signature']."
|
||||
" . $translations['mail_valid_expire'] . "<br>
|
||||
" . $translations['mail_valid_ignore'] . "<br><br>
|
||||
" . $translations['mail_valid_signature'] . "
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
@@ -313,13 +341,13 @@ $html = "<!DOCTYPE html>
|
||||
|
||||
// Texte alternative
|
||||
$text = strip_tags(
|
||||
sprintf($translations['mail_valid_bonjour_nom'], "$prenom $nom")."\n\n".
|
||||
$translations['mail_valid_merci_praticien']."\n".
|
||||
$translations['mail_valid_action']."\n".
|
||||
($translations['mail_valid_fallback'] ?? 'Si le bouton ne fonctionne pas, utilisez ce lien :')."\n".
|
||||
$validation_url."\n\n".
|
||||
$translations['mail_valid_expire']."\n".
|
||||
$translations['mail_valid_ignore']."\n".
|
||||
sprintf($translations['mail_valid_bonjour_nom'], "$prenom $nom") . "\n\n" .
|
||||
$translations['mail_valid_merci_praticien'] . "\n" .
|
||||
$translations['mail_valid_action'] . "\n" .
|
||||
($translations['mail_valid_fallback'] ?? 'Si le bouton ne fonctionne pas, utilisez ce lien :') . "\n" .
|
||||
$validation_url . "\n\n" .
|
||||
$translations['mail_valid_expire'] . "\n" .
|
||||
$translations['mail_valid_ignore'] . "\n" .
|
||||
($translations['mail_valid_signature'] ?? 'USLUGI.FR')
|
||||
);
|
||||
|
||||
@@ -334,7 +362,7 @@ list($sent, $err) = uslugi_mail(
|
||||
]
|
||||
);
|
||||
|
||||
if (!$sent) {
|
||||
if (! $sent) {
|
||||
error_log("Validation mail NOT sent to $email : $err");
|
||||
// On ne bloque pas l’utilisateur : il pourra redemander un mail.
|
||||
}
|
||||
@@ -344,7 +372,7 @@ if (!$sent) {
|
||||
// =====================
|
||||
echo '
|
||||
<!DOCTYPE html><html lang="fr"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>'.htmlspecialchars($translations['insc_merci_titre'] ?? 'Merci !', ENT_QUOTES, 'UTF-8').'</title>
|
||||
<title>' . htmlspecialchars($translations['insc_merci_titre'] ?? 'Merci !', ENT_QUOTES, 'UTF-8') . '</title>
|
||||
<style>
|
||||
html,body{height:100%;margin:0}
|
||||
body{display:flex;align-items:center;justify-content:center;background:#8CA9FF;font-family:Montserrat,system-ui,Segoe UI,Roboto,Arial}
|
||||
@@ -360,11 +388,11 @@ echo '
|
||||
<div style="width:102px;height:102px;border-radius:50%;background:rgba(30,128,80,.08);border:3px solid rgba(30,128,80,.20);display:flex;align-items:center;justify-content:center;margin:0 auto 20px;">
|
||||
<div style="width:48px;height:36px;border:5px solid #1e8050;border-top:0;border-left:0;transform:rotate(45deg);"></div>
|
||||
</div>
|
||||
<h1>'.htmlspecialchars($translations['insc_merci_titre'] ?? 'Merci !', ENT_QUOTES, 'UTF-8').'</h1>
|
||||
<p>'.htmlspecialchars($translations['insc_merci_ligne1'] ?? 'Votre compte a bien été créé.', ENT_QUOTES, 'UTF-8').'</p>
|
||||
<p>'.htmlspecialchars($translations['insc_merci_ligne2'] ?? 'Vérifiez vos emails pour valider votre adresse.', ENT_QUOTES, 'UTF-8').'</p>
|
||||
<h1>' . htmlspecialchars($translations['insc_merci_titre'] ?? 'Merci !', ENT_QUOTES, 'UTF-8') . '</h1>
|
||||
<p>' . htmlspecialchars($translations['insc_merci_ligne1'] ?? 'Votre compte a bien été créé.', ENT_QUOTES, 'UTF-8') . '</p>
|
||||
<p>' . htmlspecialchars($translations['insc_merci_ligne2'] ?? 'Vérifiez vos emails pour valider votre adresse.', ENT_QUOTES, 'UTF-8') . '</p>
|
||||
<div class="row">
|
||||
<a class="btn btn-outline" href="/">'.htmlspecialchars($translations['retour_acceuil'] ?? 'Retour à l’accueil', ENT_QUOTES, 'UTF-8').'</a>
|
||||
<a class="btn btn-outline" href="/">' . htmlspecialchars($translations['retour_acceuil'] ?? 'Retour à l’accueil', ENT_QUOTES, 'UTF-8') . '</a>
|
||||
</div>
|
||||
</div>
|
||||
</body></html>';
|
||||
@@ -0,0 +1,370 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/../includes/bootstrap_global.php';
|
||||
|
||||
/**
|
||||
* UI helper: affiche une jolie carte centrée puis exit.
|
||||
* $type: 'error' | 'info' | 'success'
|
||||
*/
|
||||
function ui_exit(string $title, string $message, int $status = 400, string $type = 'error', ?string $cta_href = '/', ?string $cta_label = null): void {
|
||||
http_response_code($status);
|
||||
$title = htmlspecialchars($title, ENT_QUOTES, 'UTF-8');
|
||||
$message = nl2br(htmlspecialchars($message, ENT_QUOTES, 'UTF-8'));
|
||||
$cta_href = $cta_href ? htmlspecialchars($cta_href, ENT_QUOTES, 'UTF-8') : null;
|
||||
$cta_label = $cta_label ?? 'OK';
|
||||
|
||||
// Styles d'icône selon le type
|
||||
$iconBorder = '#B91C1C'; // rouge par défaut (error)
|
||||
$iconBg = 'rgba(185,28,28,.08)';
|
||||
$iconRing = 'rgba(185,28,28,.25)';
|
||||
$checkCSS = ''; // pour success
|
||||
if ($type === 'info') {
|
||||
$iconBorder = '#1D4ED8';
|
||||
$iconBg = 'rgba(29,78,216,.08)';
|
||||
$iconRing = 'rgba(29,78,216,.25)';
|
||||
} elseif ($type === 'success') {
|
||||
$iconBorder = '#1E8050';
|
||||
$iconBg = 'rgba(30,128,80,.08)';
|
||||
$iconRing = 'rgba(30,128,80,.20)';
|
||||
$checkCSS = 'width:48px;height:36px;border:5px solid #1e8050;border-top:0;border-left:0;transform:rotate(45deg);';
|
||||
}
|
||||
|
||||
echo '<!DOCTYPE html><html lang="fr"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">';
|
||||
echo '<title>'.$title.'</title>';
|
||||
echo '<style>
|
||||
html,body{height:100%;margin:0}
|
||||
body{display:flex;align-items:center;justify-content:center;background:#8CA9FF;font-family:Montserrat,system-ui,Segoe UI,Roboto,Arial}
|
||||
.card{max-width:720px;width:94%;background:#fff;border:1px solid #e5e7eb;border-radius:14px;box-shadow:0 8px 24px rgba(0,0,0,.08);padding:28px;text-align:center}
|
||||
h1{margin:6px 0 10px;font-size:clamp(22px,2.4vw,28px)}
|
||||
p{margin:0 0 18px;color:#6b7280;font-size:clamp(16px,2.2vw,18px)}
|
||||
.row{display:flex;gap:10px;justify-content:center;flex-wrap:wrap;margin-top:8px}
|
||||
.btn{padding:10px 14px;border-radius:10px;text-decoration:none;display:inline-block}
|
||||
.btn-primary{background:#2563eb;color:#fff}
|
||||
.btn-outline{border:1px solid #d1d5db;color:#374151;background:#fff}
|
||||
</style></head><body><div class="card">';
|
||||
|
||||
// Icône
|
||||
echo '<div style="width:102px;height:102px;border-radius:50%;background:'.$iconBg.';border:3px solid '.$iconRing.';display:flex;align-items:center;justify-content:center;margin:0 auto 20px;">';
|
||||
if ($type === 'success') {
|
||||
echo '<div style="'.$checkCSS.'"></div>';
|
||||
} elseif ($type === 'info') {
|
||||
echo '<div style="width:14px;height:48px;border-left:5px solid '.$iconBorder.';position:relative;">
|
||||
<div style="position:absolute;top:-18px;left:-5px;width:10px;height:10px;background:'.$iconBorder.';border-radius:50%;"></div>
|
||||
</div>';
|
||||
} else { // error
|
||||
echo '<div style="position:relative;width:48px;height:48px;">
|
||||
<div style="position:absolute;top:0;left:22px;width:5px;height:48px;background:'.$iconBorder.';transform:rotate(45deg);border-radius:2px;"></div>
|
||||
<div style="position:absolute;top:0;left:22px;width:5px;height:48px;background:'.$iconBorder.';transform:rotate(-45deg);border-radius:2px;"></div>
|
||||
</div>';
|
||||
}
|
||||
echo '</div>';
|
||||
|
||||
echo '<h1>'.$title.'</h1>';
|
||||
echo '<p>'.$message.'</p>';
|
||||
|
||||
if ($cta_href) {
|
||||
echo '<div class="row"><a class="btn btn-outline" href="'.$cta_href.'">'.$cta_label.'</a></div>';
|
||||
}
|
||||
|
||||
echo '</div></body></html>';
|
||||
exit;
|
||||
}
|
||||
|
||||
// =====================
|
||||
// Sécurité de base
|
||||
// =====================
|
||||
if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
|
||||
ui_exit(
|
||||
$translations['err_acces_incorrect_titre'] ?? 'Accès incorrect',
|
||||
$translations['err_acces_incorrect_msg'] ?? "Cette page doit être appelée depuis le formulaire d'inscription.",
|
||||
405,
|
||||
'info',
|
||||
'inscription_praticien.php',
|
||||
$translations['retour_formulaire'] ?? 'Retour au formulaire'
|
||||
);
|
||||
}
|
||||
|
||||
// =====================
|
||||
// Récupération & checks
|
||||
// =====================
|
||||
$nom = trim($_POST['nom'] ?? '');
|
||||
$prenom = trim($_POST['prenom'] ?? '');
|
||||
$email = trim($_POST['email'] ?? '');
|
||||
$mdp = $_POST['mot_de_passe'] ?? '';
|
||||
$siret = preg_replace('/\D/', '', $_POST['siret'] ?? '');
|
||||
|
||||
// Champs obligatoires
|
||||
if ($nom === '' || $prenom === '' || $email === '' || $mdp === '') {
|
||||
ui_exit(
|
||||
$translations['err_titre'] ?? 'Un problème est survenu',
|
||||
$translations['err_champs_obligatoires'] ?? 'Merci de remplir tous les champs obligatoires.',
|
||||
400,
|
||||
'error',
|
||||
'inscription_praticien.php',
|
||||
$translations['retour_formulaire'] ?? 'Retour au formulaire'
|
||||
);
|
||||
}
|
||||
|
||||
// Email valide + pas d'injection d'entêtes
|
||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL) || preg_match('/[\r\n]/', $email)) {
|
||||
ui_exit(
|
||||
$translations['err_titre'] ?? 'Un problème est survenu',
|
||||
$translations['err_email_non_valide'] ?? 'Email invalide.',
|
||||
400,
|
||||
'error',
|
||||
'inscription_praticien.php'
|
||||
);
|
||||
}
|
||||
|
||||
// SIRET longueur
|
||||
if (strlen($siret) !== 14) {
|
||||
ui_exit(
|
||||
$translations['err_titre'] ?? 'Un problème est survenu',
|
||||
$translations['err_siret_invalide'] ?? 'Numéro SIRET invalide (14 chiffres requis).',
|
||||
400,
|
||||
'error',
|
||||
'inscription_praticien.php'
|
||||
);
|
||||
}
|
||||
|
||||
// Luhn
|
||||
function luhnCheck($num) {
|
||||
$arr = str_split(strrev($num));
|
||||
$sum = 0;
|
||||
foreach ($arr as $i => $n) {
|
||||
$n = (int)$n;
|
||||
if ($i % 2) {
|
||||
$n *= 2;
|
||||
if ($n > 9) $n -= 9;
|
||||
}
|
||||
$sum += $n;
|
||||
}
|
||||
return $sum % 10 === 0;
|
||||
}
|
||||
if (!luhnCheck($siret)) {
|
||||
ui_exit(
|
||||
$translations['err_titre'] ?? 'Un problème est survenu',
|
||||
$translations['err_siret_invalide'] ?? 'Numéro SIRET invalide (échec vérification).',
|
||||
400,
|
||||
'error',
|
||||
'inscription_praticien.php'
|
||||
);
|
||||
}
|
||||
|
||||
// =====================
|
||||
// Transaction
|
||||
// =====================
|
||||
try {
|
||||
$pdo->beginTransaction();
|
||||
|
||||
// Email déjà utilisé ?
|
||||
$stmt = $pdo->prepare("SELECT id FROM compte WHERE email = :email LIMIT 1");
|
||||
$stmt->execute(['email' => $email]);
|
||||
if ($stmt->fetch()) {
|
||||
$pdo->rollBack();
|
||||
ui_exit(
|
||||
$translations['err_titre'] ?? 'Un problème est survenu',
|
||||
$translations['err_email_deja_utilise'] ?? 'Cet email est déjà utilisé.',
|
||||
409,
|
||||
'error',
|
||||
'inscription_praticien.php'
|
||||
);
|
||||
}
|
||||
|
||||
// SIRET déjà utilisé ?
|
||||
$stmt = $pdo->prepare("SELECT id FROM praticien WHERE siret = :siret LIMIT 1");
|
||||
$stmt->execute(['siret' => $siret]);
|
||||
if ($stmt->fetch()) {
|
||||
$pdo->rollBack();
|
||||
ui_exit(
|
||||
$translations['err_titre'] ?? 'Un problème est survenu',
|
||||
$translations['err_siret_deja_utilise'] ?? 'Ce numéro SIRET est déjà enregistré.',
|
||||
409,
|
||||
'error',
|
||||
'inscription_praticien.php'
|
||||
);
|
||||
}
|
||||
|
||||
// Acceptation CGU
|
||||
$accept_cgu = isset($_POST['accept_cgu']) ? 1 : 0;
|
||||
if ($accept_cgu !== 1) {
|
||||
$pdo->rollBack();
|
||||
ui_exit(
|
||||
$translations['err_titre'] ?? 'Un problème est survenu',
|
||||
$translations['cgu_obligatoire'] ?? 'Vous devez accepter les CGU.',
|
||||
400,
|
||||
'info',
|
||||
'inscription_praticien.php'
|
||||
);
|
||||
}
|
||||
|
||||
// 1) Préparer la date d'hier pour forcer l'expiration immédiate
|
||||
$hier = date('Y-m-d', strtotime('-1 day'));
|
||||
|
||||
// 2) Créer le praticien avec les champs d'abonnement initialisés
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO praticien (
|
||||
nom,
|
||||
prenom,
|
||||
email,
|
||||
siret,
|
||||
accepted_cgu,
|
||||
abonnement_id,
|
||||
date_expiration
|
||||
) VALUES (
|
||||
:nom,
|
||||
:prenom,
|
||||
:email,
|
||||
:siret,
|
||||
:accepted_cgu,
|
||||
NULL,
|
||||
:date_exp
|
||||
)
|
||||
");
|
||||
|
||||
$stmt->execute([
|
||||
'nom' => $nom,
|
||||
'prenom' => $prenom,
|
||||
'email' => $email,
|
||||
'siret' => $siret,
|
||||
'accepted_cgu' => $accept_cgu,
|
||||
'date_exp' => $hier // On met hier pour que le script de vérification dise "C'est expiré !"
|
||||
]);
|
||||
$praticien_id = (int)$pdo->lastInsertId();
|
||||
|
||||
// 2) Créer le compte
|
||||
$mot_de_passe_hash = password_hash($mdp, PASSWORD_DEFAULT);
|
||||
$token = bin2hex(random_bytes(32));
|
||||
$token_autologin = bin2hex(random_bytes(32));
|
||||
$token_autologin_expire = (new DateTime('+60 minutes'))->format('Y-m-d H:i:s');
|
||||
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO compte (email, mot_de_passe, role, reference_id, token_validation, email_valide, token_autologin, token_autologin_expire)
|
||||
VALUES (:email, :mdp, 'praticien', :ref_id, :token, 0, :al, :al_exp)
|
||||
");
|
||||
$stmt->execute([
|
||||
'email' => $email,
|
||||
'mdp' => $mot_de_passe_hash,
|
||||
'ref_id' => $praticien_id,
|
||||
'token' => $token,
|
||||
'al' => $token_autologin,
|
||||
'al_exp' => $token_autologin_expire,
|
||||
]);
|
||||
|
||||
|
||||
// Commit avant envoi mail
|
||||
$pdo->commit();
|
||||
|
||||
} catch (Throwable $e) {
|
||||
if ($pdo->inTransaction()) $pdo->rollBack();
|
||||
error_log('Inscription praticien - DB error: '.$e->getMessage());
|
||||
ui_exit(
|
||||
$translations['err_titre'] ?? 'Un problème est survenu',
|
||||
$translations['err_serveur'] ?? 'Erreur serveur.',
|
||||
500,
|
||||
'error',
|
||||
'/'
|
||||
);
|
||||
}
|
||||
|
||||
// =====================
|
||||
// Email de validation
|
||||
// =====================
|
||||
$validation_url = "https://uslugi.fr/validation.php?token=" . urlencode($token) . "&al=" . urlencode($token_autologin);
|
||||
$validation_url_html = htmlspecialchars($validation_url, ENT_QUOTES, 'UTF-8');
|
||||
$prenom_nom = htmlspecialchars("$prenom $nom", ENT_QUOTES, 'UTF-8');
|
||||
|
||||
$sujet = $translations['mail_valid_sujet'];
|
||||
|
||||
// Corps HTML mail
|
||||
$html = "<!DOCTYPE html>
|
||||
<html lang='fr'>
|
||||
<head>
|
||||
<meta charset='UTF-8'>
|
||||
<meta http-equiv='X-UA-Compatible' content='IE=edge'>
|
||||
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; background-color:#f4f4f4; padding:20px; }
|
||||
.mail-container { max-width:600px; margin:auto; background:#fff; padding:30px; border-radius:8px; box-shadow:0 0 10px rgba(0,0,0,0.1); }
|
||||
.title { font-size:20px; font-weight:bold; color:#333; margin-bottom:20px; }
|
||||
.btn { display:inline-block; margin-top:20px; padding:12px 24px; background-color:#E03C3F; color:#fff; text-decoration:none; border-radius:999px; font-weight:bold; }
|
||||
.footer { margin-top:30px; font-size:13px; color:#888; }
|
||||
.link-fallback { word-break: break-all; font-size:13px; margin-top:15px; color:#555; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class='mail-container'>
|
||||
<div class='title'>".sprintf($translations['mail_valid_bonjour_nom'], $prenom_nom)."</div>
|
||||
<p>".$translations['mail_valid_merci_praticien']."</p>
|
||||
<p>".$translations['mail_valid_action']."</p>
|
||||
<a href='$validation_url_html' class='btn'>".$translations['mail_valid_btn']."</a>
|
||||
<p class='link-fallback'>".$translations['mail_valid_fallback']."<br>
|
||||
<a href='$validation_url_html'>$validation_url_html</a>
|
||||
</p>
|
||||
<div class='footer'>
|
||||
".$translations['mail_valid_expire']."<br>
|
||||
".$translations['mail_valid_ignore']."<br><br>
|
||||
".$translations['mail_valid_signature']."
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>";
|
||||
|
||||
// Texte alternative
|
||||
$text = strip_tags(
|
||||
sprintf($translations['mail_valid_bonjour_nom'], "$prenom $nom")."\n\n".
|
||||
$translations['mail_valid_merci_praticien']."\n".
|
||||
$translations['mail_valid_action']."\n".
|
||||
($translations['mail_valid_fallback'] ?? 'Si le bouton ne fonctionne pas, utilisez ce lien :')."\n".
|
||||
$validation_url."\n\n".
|
||||
$translations['mail_valid_expire']."\n".
|
||||
$translations['mail_valid_ignore']."\n".
|
||||
($translations['mail_valid_signature'] ?? 'USLUGI.FR')
|
||||
);
|
||||
|
||||
// Envoi
|
||||
list($sent, $err) = uslugi_mail(
|
||||
$email,
|
||||
$sujet,
|
||||
$html,
|
||||
$text,
|
||||
[
|
||||
'to_name' => "$prenom $nom",
|
||||
]
|
||||
);
|
||||
|
||||
if (!$sent) {
|
||||
error_log("Validation mail NOT sent to $email : $err");
|
||||
// On ne bloque pas l’utilisateur : il pourra redemander un mail.
|
||||
}
|
||||
|
||||
// =====================
|
||||
// Écran final succès
|
||||
// =====================
|
||||
echo '
|
||||
<!DOCTYPE html><html lang="fr"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>'.htmlspecialchars($translations['insc_merci_titre'] ?? 'Merci !', ENT_QUOTES, 'UTF-8').'</title>
|
||||
<style>
|
||||
html,body{height:100%;margin:0}
|
||||
body{display:flex;align-items:center;justify-content:center;background:#8CA9FF;font-family:Montserrat,system-ui,Segoe UI,Roboto,Arial}
|
||||
.card{max-width:600px;width:98%;background:#fff;border:1px solid #e5e7eb;border-radius:14px;box-shadow:0 8px 24px rgba(0,0,0,.08);padding:28px;text-align:center}
|
||||
h1{margin:6px 0 10px;font-size:clamp(22px,2.4vw,28px)}
|
||||
p{margin:0 0 18px;color:#6b7280;font-size:clamp(16px,2.2vw,18px)}
|
||||
.row{display:flex;gap:10px;justify-content:center;flex-wrap:wrap}
|
||||
.btn{padding:10px 14px;border-radius:10px;text-decoration:none;display:inline-block}
|
||||
.btn-primary{background:#2563eb;color:#fff}
|
||||
.btn-outline{border:1px solid #d1d5db;color:#374151;background:#fff}
|
||||
</style></head><body>
|
||||
<div class="card">
|
||||
<div style="width:102px;height:102px;border-radius:50%;background:rgba(30,128,80,.08);border:3px solid rgba(30,128,80,.20);display:flex;align-items:center;justify-content:center;margin:0 auto 20px;">
|
||||
<div style="width:48px;height:36px;border:5px solid #1e8050;border-top:0;border-left:0;transform:rotate(45deg);"></div>
|
||||
</div>
|
||||
<h1>'.htmlspecialchars($translations['insc_merci_titre'] ?? 'Merci !', ENT_QUOTES, 'UTF-8').'</h1>
|
||||
<p>'.htmlspecialchars($translations['insc_merci_ligne1'] ?? 'Votre compte a bien été créé.', ENT_QUOTES, 'UTF-8').'</p>
|
||||
<p>'.htmlspecialchars($translations['insc_merci_ligne2'] ?? 'Vérifiez vos emails pour valider votre adresse.', ENT_QUOTES, 'UTF-8').'</p>
|
||||
<div class="row">
|
||||
<a class="btn btn-outline" href="/">'.htmlspecialchars($translations['retour_acceuil'] ?? 'Retour à l’accueil', ENT_QUOTES, 'UTF-8').'</a>
|
||||
</div>
|
||||
</div>
|
||||
</body></html>';
|
||||
+58
-79
@@ -1,36 +1,37 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
declare (strict_types = 1);
|
||||
require_once __DIR__ . '/../includes/bootstrap_global.php';
|
||||
|
||||
/**
|
||||
* UI helper: affiche une jolie carte centrée puis exit.
|
||||
* $type: 'error' | 'info' | 'success'
|
||||
*/
|
||||
function ui_exit(string $title, string $message, int $status = 400, string $type = 'error', ?string $cta_href = '/', ?string $cta_label = null): void {
|
||||
function ui_exit(string $title, string $message, int $status = 400, string $type = 'error', ?string $cta_href = '/', ?string $cta_label = null): void
|
||||
{
|
||||
http_response_code($status);
|
||||
$title = htmlspecialchars($title, ENT_QUOTES, 'UTF-8');
|
||||
$message = nl2br(htmlspecialchars($message, ENT_QUOTES, 'UTF-8'));
|
||||
$cta_href = $cta_href ? htmlspecialchars($cta_href, ENT_QUOTES, 'UTF-8') : null;
|
||||
$cta_label = $cta_label ?? 'OK';
|
||||
|
||||
// Styles d'icône selon le type
|
||||
// Styles d'icône selon le type
|
||||
$iconBorder = '#B91C1C'; // rouge par défaut (error)
|
||||
$iconBg = 'rgba(185,28,28,.08)';
|
||||
$iconRing = 'rgba(185,28,28,.25)';
|
||||
$checkCSS = ''; // pour success
|
||||
if ($type === 'info') {
|
||||
$iconBorder = '#1D4ED8';
|
||||
$iconBg = 'rgba(29,78,216,.08)';
|
||||
$iconRing = 'rgba(29,78,216,.25)';
|
||||
$iconBg = 'rgba(29,78,216,.08)';
|
||||
$iconRing = 'rgba(29,78,216,.25)';
|
||||
} elseif ($type === 'success') {
|
||||
$iconBorder = '#1E8050';
|
||||
$iconBg = 'rgba(30,128,80,.08)';
|
||||
$iconRing = 'rgba(30,128,80,.20)';
|
||||
$checkCSS = 'width:48px;height:36px;border:5px solid #1e8050;border-top:0;border-left:0;transform:rotate(45deg);';
|
||||
$iconBg = 'rgba(30,128,80,.08)';
|
||||
$iconRing = 'rgba(30,128,80,.20)';
|
||||
$checkCSS = 'width:48px;height:36px;border:5px solid #1e8050;border-top:0;border-left:0;transform:rotate(45deg);';
|
||||
}
|
||||
|
||||
echo '<!DOCTYPE html><html lang="fr"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">';
|
||||
echo '<title>'.$title.'</title>';
|
||||
echo '<title>' . $title . '</title>';
|
||||
echo '<style>
|
||||
html,body{height:100%;margin:0}
|
||||
body{display:flex;align-items:center;justify-content:center;background:#8CA9FF;font-family:Montserrat,system-ui,Segoe UI,Roboto,Arial}
|
||||
@@ -44,26 +45,26 @@ function ui_exit(string $title, string $message, int $status = 400, string $type
|
||||
</style></head><body><div class="card">';
|
||||
|
||||
// Icône
|
||||
echo '<div style="width:102px;height:102px;border-radius:50%;background:'.$iconBg.';border:3px solid '.$iconRing.';display:flex;align-items:center;justify-content:center;margin:0 auto 20px;">';
|
||||
echo '<div style="width:102px;height:102px;border-radius:50%;background:' . $iconBg . ';border:3px solid ' . $iconRing . ';display:flex;align-items:center;justify-content:center;margin:0 auto 20px;">';
|
||||
if ($type === 'success') {
|
||||
echo '<div style="'.$checkCSS.'"></div>';
|
||||
echo '<div style="' . $checkCSS . '"></div>';
|
||||
} elseif ($type === 'info') {
|
||||
echo '<div style="width:14px;height:48px;border-left:5px solid '.$iconBorder.';position:relative;">
|
||||
<div style="position:absolute;top:-18px;left:-5px;width:10px;height:10px;background:'.$iconBorder.';border-radius:50%;"></div>
|
||||
echo '<div style="width:14px;height:48px;border-left:5px solid ' . $iconBorder . ';position:relative;">
|
||||
<div style="position:absolute;top:-18px;left:-5px;width:10px;height:10px;background:' . $iconBorder . ';border-radius:50%;"></div>
|
||||
</div>';
|
||||
} else { // error
|
||||
echo '<div style="position:relative;width:48px;height:48px;">
|
||||
<div style="position:absolute;top:0;left:22px;width:5px;height:48px;background:'.$iconBorder.';transform:rotate(45deg);border-radius:2px;"></div>
|
||||
<div style="position:absolute;top:0;left:22px;width:5px;height:48px;background:'.$iconBorder.';transform:rotate(-45deg);border-radius:2px;"></div>
|
||||
<div style="position:absolute;top:0;left:22px;width:5px;height:48px;background:' . $iconBorder . ';transform:rotate(45deg);border-radius:2px;"></div>
|
||||
<div style="position:absolute;top:0;left:22px;width:5px;height:48px;background:' . $iconBorder . ';transform:rotate(-45deg);border-radius:2px;"></div>
|
||||
</div>';
|
||||
}
|
||||
echo '</div>';
|
||||
|
||||
echo '<h1>'.$title.'</h1>';
|
||||
echo '<p>'.$message.'</p>';
|
||||
echo '<h1>' . $title . '</h1>';
|
||||
echo '<p>' . $message . '</p>';
|
||||
|
||||
if ($cta_href) {
|
||||
echo '<div class="row"><a class="btn btn-outline" href="'.$cta_href.'">'.$cta_label.'</a></div>';
|
||||
echo '<div class="row"><a class="btn btn-outline" href="' . $cta_href . '">' . $cta_label . '</a></div>';
|
||||
}
|
||||
|
||||
echo '</div></body></html>';
|
||||
@@ -73,10 +74,10 @@ function ui_exit(string $title, string $message, int $status = 400, string $type
|
||||
// =====================
|
||||
// 1) Token présent & valide
|
||||
// =====================
|
||||
$rawToken = trim((string)($_GET['token'] ?? ''));
|
||||
$rawAL = trim((string)($_GET['al'] ?? ''));
|
||||
if ($rawAL !== '' && !preg_match('/^[A-Fa-f0-9]{64}$/', $rawAL)) {
|
||||
$rawAL = ''; // on ignore si format suspect
|
||||
$rawToken = trim((string) ($_GET['token'] ?? ''));
|
||||
$rawAL = trim((string) ($_GET['al'] ?? ''));
|
||||
if ($rawAL !== '' && ! preg_match('/^[A-Fa-f0-9]{64}$/', $rawAL)) {
|
||||
$rawAL = ''; // on ignore si format suspect
|
||||
}
|
||||
|
||||
if ($rawToken === '') {
|
||||
@@ -90,7 +91,7 @@ if ($rawToken === '') {
|
||||
);
|
||||
}
|
||||
// Optionnel : on vérifie le format attendu (bin2hex(32) => 64 hexdigits)
|
||||
if (!preg_match('/^[A-Fa-f0-9]{64}$/', $rawToken)) {
|
||||
if (! preg_match('/^[A-Fa-f0-9]{64}$/', $rawToken)) {
|
||||
ui_exit(
|
||||
$translations['err_titre'] ?? 'Un problème est survenu',
|
||||
$translations['err_lien_invalide'] ?? 'Lien invalide.',
|
||||
@@ -108,7 +109,7 @@ $stmt = $pdo->prepare("SELECT * FROM compte WHERE token_validation = :token LIMI
|
||||
$stmt->execute(['token' => $rawToken]);
|
||||
$compte = $stmt->fetch();
|
||||
|
||||
if (!$compte) {
|
||||
if (! $compte) {
|
||||
ui_exit(
|
||||
$translations['err_titre'] ?? 'Un problème est survenu',
|
||||
$translations['err_token_invalide'] ?? 'Token invalide.',
|
||||
@@ -123,56 +124,31 @@ if (!$compte) {
|
||||
// 3) Validité temporelle (6h)
|
||||
// =====================
|
||||
try {
|
||||
$created_at = new DateTime($compte['cree_le']);
|
||||
if (empty($compte['token_validation_expire'])) {
|
||||
throw new RuntimeException('token_validation_expire manquant');
|
||||
}
|
||||
$expireAt = new DateTime((string) $compte['token_validation_expire']);
|
||||
} catch (Throwable $e) {
|
||||
// Par prudence : si la date est illisible, on refuse
|
||||
ui_exit(
|
||||
$translations['err_titre'] ?? 'Un problème est survenu',
|
||||
$translations['err_lien_invalide'] ?? 'Lien invalide.',
|
||||
$translations['tvalidat_err_lien_invalide'] ?? $translations['err_lien_invalide'] ?? 'Lien invalide.',
|
||||
400,
|
||||
'error',
|
||||
'/',
|
||||
$translations['retour_acceuil'] ?? 'Retour à l’accueil'
|
||||
);
|
||||
}
|
||||
|
||||
$now = new DateTime();
|
||||
$diffH = ($now->getTimestamp() - $created_at->getTimestamp()) / 3600;
|
||||
|
||||
if ($diffH > 6) {
|
||||
// Supprimer les enregistrements expirés
|
||||
try {
|
||||
$pdo->beginTransaction();
|
||||
|
||||
$pdo->prepare("DELETE FROM compte WHERE id = :id")->execute(['id' => $compte['id']]);
|
||||
|
||||
$ref_id = (int)$compte['reference_id'];
|
||||
if (($compte['role'] ?? '') === 'praticien') {
|
||||
$pdo->prepare("DELETE FROM praticien WHERE id = :id")->execute(['id' => $ref_id]);
|
||||
} else {
|
||||
$pdo->prepare("DELETE FROM client WHERE id = :id")->execute(['id' => $ref_id]);
|
||||
}
|
||||
|
||||
$pdo->commit();
|
||||
} catch (Throwable $e) {
|
||||
if ($pdo->inTransaction()) $pdo->rollBack();
|
||||
error_log('Validation email - purge expirée échouée: '.$e->getMessage());
|
||||
ui_exit(
|
||||
$translations['err_titre'] ?? 'Un problème est survenu',
|
||||
$translations['err_serveur'] ?? 'Erreur serveur.',
|
||||
500,
|
||||
'error',
|
||||
'/',
|
||||
$translations['retour_acceuil'] ?? 'Retour à l’accueil'
|
||||
);
|
||||
}
|
||||
|
||||
if ($now > $expireAt) {
|
||||
ui_exit(
|
||||
$translations['err_titre'] ?? 'Un problème est survenu',
|
||||
$translations['err_lien_expire'] ?? 'Le lien de validation a expiré.',
|
||||
$translations['tvalidat_err_lien_expire'] ?? $translations['err_lien_expire'] ?? 'Le lien de validation a expiré.',
|
||||
410,
|
||||
'info',
|
||||
'/',
|
||||
$translations['retour_acceuil'] ?? 'Retour à l’accueil'
|
||||
'/connexion.php',
|
||||
$translations['btn_se_connecter'] ?? 'Se connecter'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -180,10 +156,16 @@ if ($diffH > 6) {
|
||||
// 4) Valider l'email (maj compte)
|
||||
// =====================
|
||||
try {
|
||||
$stmt = $pdo->prepare("UPDATE compte SET email_valide = 1, token_validation = NULL WHERE id = :id");
|
||||
$stmt = $pdo->prepare("
|
||||
UPDATE compte
|
||||
SET email_valide = 1,
|
||||
token_validation = NULL,
|
||||
token_validation_expire = NULL
|
||||
WHERE id = :id
|
||||
");
|
||||
$stmt->execute(['id' => $compte['id']]);
|
||||
} catch (Throwable $e) {
|
||||
error_log('Validation email - UPDATE échouée: '.$e->getMessage());
|
||||
error_log('Validation email - UPDATE échouée: ' . $e->getMessage());
|
||||
ui_exit(
|
||||
$translations['err_titre'] ?? 'Un problème est survenu',
|
||||
$translations['err_serveur'] ?? 'Erreur serveur.',
|
||||
@@ -203,16 +185,16 @@ if ($rawAL !== '') {
|
||||
FROM compte
|
||||
WHERE id = :id
|
||||
LIMIT 1");
|
||||
$stmt->execute(['id' => (int)$compte['id']]);
|
||||
$stmt->execute(['id' => (int) $compte['id']]);
|
||||
$c = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($c && !empty($c['token_autologin']) && hash_equals((string)$c['token_autologin'], $rawAL)) {
|
||||
if ($c && ! empty($c['token_autologin']) && hash_equals((string) $c['token_autologin'], $rawAL)) {
|
||||
|
||||
// Expiration (obligatoire)
|
||||
$expOk = false;
|
||||
if (!empty($c['token_autologin_expire'])) {
|
||||
if (! empty($c['token_autologin_expire'])) {
|
||||
try {
|
||||
$exp = new DateTime((string)$c['token_autologin_expire']);
|
||||
$exp = new DateTime((string) $c['token_autologin_expire']);
|
||||
$expOk = (new DateTime()) <= $exp;
|
||||
} catch (Throwable $e) {
|
||||
$expOk = false;
|
||||
@@ -224,23 +206,23 @@ if ($rawAL !== '') {
|
||||
$pdo->prepare("UPDATE compte
|
||||
SET token_autologin = NULL, token_autologin_expire = NULL
|
||||
WHERE id = :id")
|
||||
->execute(['id' => (int)$c['id']]);
|
||||
->execute(['id' => (int) $c['id']]);
|
||||
|
||||
// Connexion session (même logique pour client/praticien)
|
||||
session_regenerate_id(true);
|
||||
$_SESSION['compte_id'] = (int)$c['id']; // <-- IMPORTANT (secure.php l'exige)
|
||||
$_SESSION['role'] = (string)$c['role'];
|
||||
$_SESSION['reference_id'] = (int)$c['reference_id'];
|
||||
$_SESSION['compte_id'] = (int) $c['id']; // <-- IMPORTANT (secure.php l'exige)
|
||||
$_SESSION['role'] = (string) $c['role'];
|
||||
$_SESSION['reference_id'] = (int) $c['reference_id'];
|
||||
|
||||
if ($_SESSION['role'] === 'client') {
|
||||
$_SESSION['client_id'] = (int)$c['reference_id'];
|
||||
header('Location: /public/client/informations.php');
|
||||
exit;
|
||||
$_SESSION['client_id'] = (int) $c['reference_id'];
|
||||
header('Location: /public/client/informations.php');
|
||||
exit;
|
||||
}
|
||||
if ($_SESSION['role'] === 'praticien') {
|
||||
$_SESSION['praticien_id'] = (int)$c['reference_id'];
|
||||
header('Location: /public/praticien/informations.php');
|
||||
exit;
|
||||
$_SESSION['praticien_id'] = (int) $c['reference_id'];
|
||||
header('Location: /public/praticien/informations.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
// fallback si role inconnu
|
||||
@@ -250,13 +232,12 @@ if ($rawAL !== '') {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// =====================
|
||||
// 5) Écran final (succès)
|
||||
// =====================
|
||||
// Le texte de succès chez toi contient un lien HTML ; on affiche un message “texte” + un vrai bouton vers la connexion.
|
||||
$successMsg = isset($translations['success_validation_texte'])
|
||||
? strip_tags((string)$translations['success_validation_texte'])
|
||||
? strip_tags((string) $translations['success_validation_texte'])
|
||||
: 'Votre compte est confirmé. Vous pouvez maintenant vous connecter.';
|
||||
|
||||
ui_exit(
|
||||
@@ -267,5 +248,3 @@ ui_exit(
|
||||
'/connexion.php',
|
||||
$translations['btn_se_connecter'] ?? 'Se connecter'
|
||||
);
|
||||
|
||||
|
||||
@@ -0,0 +1,271 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/../includes/bootstrap_global.php';
|
||||
|
||||
/**
|
||||
* UI helper: affiche une jolie carte centrée puis exit.
|
||||
* $type: 'error' | 'info' | 'success'
|
||||
*/
|
||||
function ui_exit(string $title, string $message, int $status = 400, string $type = 'error', ?string $cta_href = '/', ?string $cta_label = null): void {
|
||||
http_response_code($status);
|
||||
$title = htmlspecialchars($title, ENT_QUOTES, 'UTF-8');
|
||||
$message = nl2br(htmlspecialchars($message, ENT_QUOTES, 'UTF-8'));
|
||||
$cta_href = $cta_href ? htmlspecialchars($cta_href, ENT_QUOTES, 'UTF-8') : null;
|
||||
$cta_label = $cta_label ?? 'OK';
|
||||
|
||||
// Styles d'icône selon le type
|
||||
$iconBorder = '#B91C1C'; // rouge par défaut (error)
|
||||
$iconBg = 'rgba(185,28,28,.08)';
|
||||
$iconRing = 'rgba(185,28,28,.25)';
|
||||
$checkCSS = ''; // pour success
|
||||
if ($type === 'info') {
|
||||
$iconBorder = '#1D4ED8';
|
||||
$iconBg = 'rgba(29,78,216,.08)';
|
||||
$iconRing = 'rgba(29,78,216,.25)';
|
||||
} elseif ($type === 'success') {
|
||||
$iconBorder = '#1E8050';
|
||||
$iconBg = 'rgba(30,128,80,.08)';
|
||||
$iconRing = 'rgba(30,128,80,.20)';
|
||||
$checkCSS = 'width:48px;height:36px;border:5px solid #1e8050;border-top:0;border-left:0;transform:rotate(45deg);';
|
||||
}
|
||||
|
||||
echo '<!DOCTYPE html><html lang="fr"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">';
|
||||
echo '<title>'.$title.'</title>';
|
||||
echo '<style>
|
||||
html,body{height:100%;margin:0}
|
||||
body{display:flex;align-items:center;justify-content:center;background:#8CA9FF;font-family:Montserrat,system-ui,Segoe UI,Roboto,Arial}
|
||||
.card{max-width:720px;width:94%;background:#fff;border:1px solid #e5e7eb;border-radius:14px;box-shadow:0 8px 24px rgba(0,0,0,.08);padding:28px;text-align:center}
|
||||
h1{margin:6px 0 10px;font-size:clamp(22px,2.4vw,28px)}
|
||||
p{margin:0 0 18px;color:#6b7280;font-size:clamp(16px,2.2vw,18px)}
|
||||
.row{display:flex;gap:10px;justify-content:center;flex-wrap:wrap;margin-top:8px}
|
||||
.btn{padding:10px 14px;border-radius:10px;text-decoration:none;display:inline-block}
|
||||
.btn-primary{background:#2563eb;color:#fff}
|
||||
.btn-outline{border:1px solid #d1d5db;color:#374151;background:#fff}
|
||||
</style></head><body><div class="card">';
|
||||
|
||||
// Icône
|
||||
echo '<div style="width:102px;height:102px;border-radius:50%;background:'.$iconBg.';border:3px solid '.$iconRing.';display:flex;align-items:center;justify-content:center;margin:0 auto 20px;">';
|
||||
if ($type === 'success') {
|
||||
echo '<div style="'.$checkCSS.'"></div>';
|
||||
} elseif ($type === 'info') {
|
||||
echo '<div style="width:14px;height:48px;border-left:5px solid '.$iconBorder.';position:relative;">
|
||||
<div style="position:absolute;top:-18px;left:-5px;width:10px;height:10px;background:'.$iconBorder.';border-radius:50%;"></div>
|
||||
</div>';
|
||||
} else { // error
|
||||
echo '<div style="position:relative;width:48px;height:48px;">
|
||||
<div style="position:absolute;top:0;left:22px;width:5px;height:48px;background:'.$iconBorder.';transform:rotate(45deg);border-radius:2px;"></div>
|
||||
<div style="position:absolute;top:0;left:22px;width:5px;height:48px;background:'.$iconBorder.';transform:rotate(-45deg);border-radius:2px;"></div>
|
||||
</div>';
|
||||
}
|
||||
echo '</div>';
|
||||
|
||||
echo '<h1>'.$title.'</h1>';
|
||||
echo '<p>'.$message.'</p>';
|
||||
|
||||
if ($cta_href) {
|
||||
echo '<div class="row"><a class="btn btn-outline" href="'.$cta_href.'">'.$cta_label.'</a></div>';
|
||||
}
|
||||
|
||||
echo '</div></body></html>';
|
||||
exit;
|
||||
}
|
||||
|
||||
// =====================
|
||||
// 1) Token présent & valide
|
||||
// =====================
|
||||
$rawToken = trim((string)($_GET['token'] ?? ''));
|
||||
$rawAL = trim((string)($_GET['al'] ?? ''));
|
||||
if ($rawAL !== '' && !preg_match('/^[A-Fa-f0-9]{64}$/', $rawAL)) {
|
||||
$rawAL = ''; // on ignore si format suspect
|
||||
}
|
||||
|
||||
if ($rawToken === '') {
|
||||
ui_exit(
|
||||
$translations['err_titre'] ?? 'Un problème est survenu',
|
||||
$translations['err_lien_invalide'] ?? 'Lien invalide.',
|
||||
400,
|
||||
'error',
|
||||
'/',
|
||||
$translations['retour_acceuil'] ?? 'Retour à l’accueil'
|
||||
);
|
||||
}
|
||||
// Optionnel : on vérifie le format attendu (bin2hex(32) => 64 hexdigits)
|
||||
if (!preg_match('/^[A-Fa-f0-9]{64}$/', $rawToken)) {
|
||||
ui_exit(
|
||||
$translations['err_titre'] ?? 'Un problème est survenu',
|
||||
$translations['err_lien_invalide'] ?? 'Lien invalide.',
|
||||
400,
|
||||
'error',
|
||||
'/',
|
||||
$translations['retour_acceuil'] ?? 'Retour à l’accueil'
|
||||
);
|
||||
}
|
||||
|
||||
// =====================
|
||||
// 2) Récupération du compte
|
||||
// =====================
|
||||
$stmt = $pdo->prepare("SELECT * FROM compte WHERE token_validation = :token LIMIT 1");
|
||||
$stmt->execute(['token' => $rawToken]);
|
||||
$compte = $stmt->fetch();
|
||||
|
||||
if (!$compte) {
|
||||
ui_exit(
|
||||
$translations['err_titre'] ?? 'Un problème est survenu',
|
||||
$translations['err_token_invalide'] ?? 'Token invalide.',
|
||||
400,
|
||||
'error',
|
||||
'/',
|
||||
$translations['retour_acceuil'] ?? 'Retour à l’accueil'
|
||||
);
|
||||
}
|
||||
|
||||
// =====================
|
||||
// 3) Validité temporelle (6h)
|
||||
// =====================
|
||||
try {
|
||||
$created_at = new DateTime($compte['cree_le']);
|
||||
} catch (Throwable $e) {
|
||||
// Par prudence : si la date est illisible, on refuse
|
||||
ui_exit(
|
||||
$translations['err_titre'] ?? 'Un problème est survenu',
|
||||
$translations['err_lien_invalide'] ?? 'Lien invalide.',
|
||||
400,
|
||||
'error',
|
||||
'/',
|
||||
$translations['retour_acceuil'] ?? 'Retour à l’accueil'
|
||||
);
|
||||
}
|
||||
$now = new DateTime();
|
||||
$diffH = ($now->getTimestamp() - $created_at->getTimestamp()) / 3600;
|
||||
|
||||
if ($diffH > 6) {
|
||||
// Supprimer les enregistrements expirés
|
||||
try {
|
||||
$pdo->beginTransaction();
|
||||
|
||||
$pdo->prepare("DELETE FROM compte WHERE id = :id")->execute(['id' => $compte['id']]);
|
||||
|
||||
$ref_id = (int)$compte['reference_id'];
|
||||
if (($compte['role'] ?? '') === 'praticien') {
|
||||
$pdo->prepare("DELETE FROM praticien WHERE id = :id")->execute(['id' => $ref_id]);
|
||||
} else {
|
||||
$pdo->prepare("DELETE FROM client WHERE id = :id")->execute(['id' => $ref_id]);
|
||||
}
|
||||
|
||||
$pdo->commit();
|
||||
} catch (Throwable $e) {
|
||||
if ($pdo->inTransaction()) $pdo->rollBack();
|
||||
error_log('Validation email - purge expirée échouée: '.$e->getMessage());
|
||||
ui_exit(
|
||||
$translations['err_titre'] ?? 'Un problème est survenu',
|
||||
$translations['err_serveur'] ?? 'Erreur serveur.',
|
||||
500,
|
||||
'error',
|
||||
'/',
|
||||
$translations['retour_acceuil'] ?? 'Retour à l’accueil'
|
||||
);
|
||||
}
|
||||
|
||||
ui_exit(
|
||||
$translations['err_titre'] ?? 'Un problème est survenu',
|
||||
$translations['err_lien_expire'] ?? 'Le lien de validation a expiré.',
|
||||
410,
|
||||
'info',
|
||||
'/',
|
||||
$translations['retour_acceuil'] ?? 'Retour à l’accueil'
|
||||
);
|
||||
}
|
||||
|
||||
// =====================
|
||||
// 4) Valider l'email (maj compte)
|
||||
// =====================
|
||||
try {
|
||||
$stmt = $pdo->prepare("UPDATE compte SET email_valide = 1, token_validation = NULL WHERE id = :id");
|
||||
$stmt->execute(['id' => $compte['id']]);
|
||||
} catch (Throwable $e) {
|
||||
error_log('Validation email - UPDATE échouée: '.$e->getMessage());
|
||||
ui_exit(
|
||||
$translations['err_titre'] ?? 'Un problème est survenu',
|
||||
$translations['err_serveur'] ?? 'Erreur serveur.',
|
||||
500,
|
||||
'error',
|
||||
'/',
|
||||
$translations['retour_acceuil'] ?? 'Retour à l’accueil'
|
||||
);
|
||||
}
|
||||
|
||||
// =====================
|
||||
// 4bis) Auto-login (si al fourni et valide)
|
||||
// =====================
|
||||
if ($rawAL !== '') {
|
||||
// On recharge le compte pour lire token_autologin + expire (plus propre)
|
||||
$stmt = $pdo->prepare("SELECT id, role, reference_id, token_autologin, token_autologin_expire
|
||||
FROM compte
|
||||
WHERE id = :id
|
||||
LIMIT 1");
|
||||
$stmt->execute(['id' => (int)$compte['id']]);
|
||||
$c = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($c && !empty($c['token_autologin']) && hash_equals((string)$c['token_autologin'], $rawAL)) {
|
||||
|
||||
// Expiration (obligatoire)
|
||||
$expOk = false;
|
||||
if (!empty($c['token_autologin_expire'])) {
|
||||
try {
|
||||
$exp = new DateTime((string)$c['token_autologin_expire']);
|
||||
$expOk = (new DateTime()) <= $exp;
|
||||
} catch (Throwable $e) {
|
||||
$expOk = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($expOk) {
|
||||
// One-time : on invalide le token autologin
|
||||
$pdo->prepare("UPDATE compte
|
||||
SET token_autologin = NULL, token_autologin_expire = NULL
|
||||
WHERE id = :id")
|
||||
->execute(['id' => (int)$c['id']]);
|
||||
|
||||
// Connexion session (même logique pour client/praticien)
|
||||
session_regenerate_id(true);
|
||||
$_SESSION['compte_id'] = (int)$c['id']; // <-- IMPORTANT (secure.php l'exige)
|
||||
$_SESSION['role'] = (string)$c['role'];
|
||||
$_SESSION['reference_id'] = (int)$c['reference_id'];
|
||||
|
||||
if ($_SESSION['role'] === 'client') {
|
||||
$_SESSION['client_id'] = (int)$c['reference_id'];
|
||||
header('Location: /public/client/informations.php');
|
||||
exit;
|
||||
}
|
||||
if ($_SESSION['role'] === 'praticien') {
|
||||
$_SESSION['praticien_id'] = (int)$c['reference_id'];
|
||||
header('Location: /public/praticien/informations.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
// fallback si role inconnu
|
||||
header('Location: /connexion.php');
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// =====================
|
||||
// 5) Écran final (succès)
|
||||
// =====================
|
||||
// Le texte de succès chez toi contient un lien HTML ; on affiche un message “texte” + un vrai bouton vers la connexion.
|
||||
$successMsg = isset($translations['success_validation_texte'])
|
||||
? strip_tags((string)$translations['success_validation_texte'])
|
||||
: 'Votre compte est confirmé. Vous pouvez maintenant vous connecter.';
|
||||
|
||||
ui_exit(
|
||||
$translations['success_validation_titre'] ?? 'Compte confirmé !',
|
||||
$successMsg,
|
||||
200,
|
||||
'success',
|
||||
'/connexion.php',
|
||||
$translations['btn_se_connecter'] ?? 'Se connecter'
|
||||
);
|
||||
|
||||
|
||||
Reference in new issue
Block a user