272 lines
10 KiB
PHP
272 lines
10 KiB
PHP
<?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'
|
||
);
|
||
|
||
|