htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); $ipToBinary = static function (?string $ip): ?string { if (!$ip) return null; $bin = @inet_pton($ip); return $bin === false ? null : $bin; }; $nz = static function($a, $b) { $a = trim((string)($a ?? '')); $b = trim((string)($b ?? '')); return $a !== '' ? $a : $b; }; /* ======================= CSRF ======================= */ if (empty($_SESSION['pub_csrf'])) $_SESSION['pub_csrf'] = bin2hex(random_bytes(32)); $CSRF = $_SESSION['pub_csrf']; /* ======================= Auth / Roles ======================= */ $ALLOWED_ROLES = ['client','praticien','admin', 'secretaire', 'admin-praticien']; $isLogged = !empty($_SESSION['compte_id']); $user = ['compte_id'=>null,'role'=>null,'ref_id'=>null,'email'=>null]; if ($isLogged) { try { $st = $pdo->prepare("SELECT id, email, role, reference_id FROM compte WHERE id=? LIMIT 1"); $st->execute([(int)$_SESSION['compte_id']]); if ($row = $st->fetch(PDO::FETCH_ASSOC)) { $user = [ 'compte_id' => (int)$row['id'], 'role' => (string)$row['role'], 'ref_id' => (int)$row['reference_id'], 'email' => (string)$row['email'], ]; } } catch(Throwable $e) {} } $roleAllowed = $isLogged && in_array((string)$user['role'], $ALLOWED_ROLES, true); /* ======================= DB Utils ======================= */ $tableExists = static function(PDO $pdo, string $t): bool { $st = $pdo->prepare("SELECT 1 FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = ?"); $st->execute([$t]); return (bool)$st->fetchColumn(); }; /* ======================= DIRECT: POST handler ======================= */ if ($IS_DIRECT && $_SERVER['REQUEST_METHOD'] === 'POST') { header('Content-Type: application/json; charset=utf-8'); $out = ['ok'=>false,'msg'=>'']; try { if (!$roleAllowed) throw new Exception('FORBIDDEN'); // anti-spam 10s $last = (int)($_SESSION['last_contact_at'] ?? 0); if (time() - $last < 10) throw new Exception('TOO_MANY'); // CSRF if (!hash_equals($CSRF, $_POST['csrf'] ?? '')) throw new Exception('CSRF'); // Fallback intelligent: POST sinon PREF (mais pas vide) $nom = $nz($_POST['nom'] ?? null, $_SESSION['_caf_nom'] ?? ''); $prenom = $nz($_POST['prenom'] ?? null, $_SESSION['_caf_prenom'] ?? ''); $email = $nz($_POST['email'] ?? null, $_SESSION['_caf_email'] ?? ''); $sujet = trim((string)($_POST['sujet'] ?? '')); $msg = trim((string)($_POST['message'] ?? '')); if ($nom==='' || $prenom==='' || $sujet==='' || $msg==='') throw new Exception('REQUIRED'); if (!filter_var($email, FILTER_VALIDATE_EMAIL)) throw new Exception('EMAIL'); if (!$tableExists($pdo, 'contact_messages')) throw new Exception('TABLE_MISSING'); $ip = $_SERVER['REMOTE_ADDR'] ?? ''; $ua = substr($_SERVER['HTTP_USER_AGENT'] ?? '', 0, 255); $st = $pdo->prepare( "INSERT INTO contact_messages (user_type,user_id,nom,prenom,email,sujet,message,statut,locale,ip,user_agent,created_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,NOW())" ); $userType = in_array($user['role'], ['client','praticien'], true) ? $user['role'] : 'invite'; $st->execute([ $userType, $user['ref_id'] ?: null, $nom, $prenom, $email, $sujet, $msg, 'nouveau', 'ru', $ipToBinary($ip), $ua ]); $_SESSION['last_contact_at'] = time(); $out['ok'] = true; $out['msg'] = $translations['contact_admin_sent_success']; } catch (Throwable $ex) { $code = $ex->getMessage(); if ($code==='FORBIDDEN') $out['msg'] = $translations['contact_admin_forbidden']; elseif ($code==='TOO_MANY') $out['msg'] = $translations['contact_admin_too_many']; elseif ($code==='CSRF') $out['msg'] = $translations['contact_admin_csrf']; elseif ($code==='REQUIRED') $out['msg'] = $translations['contact_admin_required_all']; elseif ($code==='EMAIL') $out['msg'] = $translations['contact_admin_invalid_email']; elseif ($code==='TABLE_MISSING') $out['msg'] = $translations['contact_admin_table_missing']; else $out['msg'] = $translations['contact_admin_server_error']; } echo json_encode($out, JSON_UNESCAPED_UNICODE); exit; } /* ======================= DIRECT: GET ?meta=1 ======================= */ if ($IS_DIRECT && isset($_GET['meta']) && $_GET['meta'] == '1') { header('Content-Type: application/json; charset=utf-8'); // Empêcher tout cache (SW, proxy, navigateur) header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0'); header('Pragma: no-cache'); header('Expires: 0'); header('Vary: Cookie'); if (!$roleAllowed) { echo json_encode(['ok'=>false,'msg'=>$translations['contact_admin_forbidden']], JSON_UNESCAPED_UNICODE); exit; } // Token **frais** à chaque ouverture $_SESSION['pub_csrf'] = bin2hex(random_bytes(32)); $CSRF = $_SESSION['pub_csrf']; // Préremplissage léger stocké en session pour fallback POST $pref = ['nom'=>'','prenom'=>'','email'=>'']; try { if ($user['role'] === 'client') { $st = $pdo->prepare("SELECT prenom, nom, email FROM client WHERE id=? LIMIT 1"); $st->execute([$user['ref_id']]); if ($r = $st->fetch(PDO::FETCH_ASSOC)) { $pref['prenom']=$r['prenom']??''; $pref['nom']=$r['nom']??''; } $pref['email'] = $user['email'] ?? ''; } elseif (strpos($user['role'], 'praticien') !== false) { $st = $pdo->prepare("SELECT prenom, nom, email FROM praticien WHERE id=? LIMIT 1"); $st->execute([$user['ref_id']]); if ($r = $st->fetch(PDO::FETCH_ASSOC)) { $pref['prenom']=$r['prenom']??''; $pref['nom']=$r['nom']??''; } $pref['email'] = $user['email'] ?? ''; } else { $pref['email'] = $user['email'] ?? ''; } } catch(Throwable $e) {} $_SESSION['_caf_nom'] = $pref['nom']; $_SESSION['_caf_prenom'] = $pref['prenom']; $_SESSION['_caf_email'] = $pref['email']; echo json_encode([ 'ok' => true, 'title' => $translations['contact_admin_title'], 'labels' => [ 'firstname' => $translations['contact_admin_firstname'], 'lastname' => $translations['contact_admin_lastname'], 'email' => $translations['contact_admin_email'], 'subject' => $translations['contact_admin_subject'], 'message' => $translations['contact_admin_message'], 'send' => $translations['contact_admin_send'], 'required' => $translations['contact_admin_required_mandatory'], 'invalid' => $translations['contact_admin_invalid_email'] ], 'prefill' => $pref, 'csrf' => $CSRF, 'endpoint' => '/public/contact_admin.php' ], JSON_UNESCAPED_UNICODE); exit; } /* ======================= INCLUDED: inject widget only on HTML GET ======================= */ $accept = (string)($_SERVER['HTTP_ACCEPT'] ?? ''); $isHtmlLike = stripos($accept, 'text/html') !== false || empty($accept); $isGet = ($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'GET'; if (!$IS_DIRECT && $isGet && $isHtmlLike) { ?>