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 = ? LIMIT 1 "); $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 simple : 10 secondes $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 POST sinon session $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'); } // Limites de taille côté serveur if ( mb_strlen($nom) > 100 || mb_strlen($prenom) > 100 || mb_strlen($email) > 190 || mb_strlen($sujet) > 180 || mb_strlen($msg) > 3000 ) { throw new Exception('TOO_LONG'); } if (! $tableExists($pdo, 'contact_messages')) { throw new Exception('TABLE_MISSING'); } $ip = $_SERVER['REMOTE_ADDR'] ?? ''; $ua = substr($_SERVER['HTTP_USER_AGENT'] ?? '', 0, 255); $userType = in_array($user['role'], ['client', 'praticien'], true) ? $user['role'] : 'invite'; $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()) "); $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'] ?? 'Message envoyé.'; } catch (Throwable $ex) { $code = $ex->getMessage(); if ($code === 'FORBIDDEN') { $out['msg'] = $translations['contact_admin_forbidden'] ?? 'Accès interdit.'; } elseif ($code === 'TOO_MANY') { $out['msg'] = $translations['contact_admin_too_many'] ?? 'Merci de patienter avant de renvoyer un message.'; } elseif ($code === 'CSRF') { $out['msg'] = $translations['contact_admin_csrf'] ?? 'Session expirée. Rechargez la page.'; } elseif ($code === 'REQUIRED') { $out['msg'] = $translations['contact_admin_required_all'] ?? 'Tous les champs sont obligatoires.'; } elseif ($code === 'TOO_LONG') { $out['msg'] = $translations['contact_admin_too_long'] ?? 'Le message est trop long.'; } elseif ($code === 'EMAIL') { $out['msg'] = $translations['contact_admin_invalid_email'] ?? 'Adresse email invalide.'; } elseif ($code === 'TABLE_MISSING') { $out['msg'] = $translations['contact_admin_table_missing'] ?? 'Table manquante.'; } else { $out['msg'] = $translations['contact_admin_server_error'] ?? 'Erreur serveur.'; } } 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'); 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'] ?? 'Accès interdit.', ], JSON_UNESCAPED_UNICODE); exit; } // Token frais à chaque ouverture $_SESSION['pub_csrf'] = bin2hex(random_bytes(32)); $CSRF = $_SESSION['pub_csrf']; $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((string) $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'] ?? 'Contacter l’administration', 'labels' => [ 'firstname' => $translations['contact_admin_firstname'] ?? 'Prénom', 'lastname' => $translations['contact_admin_lastname'] ?? 'Nom', 'email' => $translations['contact_admin_email'] ?? 'Email', 'subject' => $translations['contact_admin_subject'] ?? 'Sujet', 'message' => $translations['contact_admin_message'] ?? 'Message', 'send' => $translations['contact_admin_send'] ?? 'Envoyer', 'required' => $translations['contact_admin_required_mandatory'] ?? 'Tous les champs sont obligatoires.', 'invalid' => $translations['contact_admin_invalid_email'] ?? 'Email invalide.', ], '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) { ?>