Init Git Prod Mars 2026
This commit is contained in:
commit
d81efe93b1
362 files changed
+86627
No files matched your search
@@ -0,0 +1,321 @@
|
||||
<?php
|
||||
// /public/contact_admin.php (SAFE to include in header)
|
||||
// - Si appelé directement par URL : répond aux GET ?meta=1 et POST (JSON) et exit.
|
||||
// - Si inclus (ex. dans header.php) : n'injecte le JS que pour des pages HTML GET,
|
||||
// ne touche jamais au Content-Type ni ne fait exit.
|
||||
|
||||
declare(strict_types=1);
|
||||
$IS_DIRECT = isset($_SERVER['SCRIPT_FILENAME'])
|
||||
&& realpath(__FILE__) === realpath($_SERVER['SCRIPT_FILENAME']);
|
||||
require_once __DIR__ . '/../../includes/bootstrap_global.php';
|
||||
|
||||
/* ======================= Helpers ======================= */
|
||||
$h = static fn($s)=>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) {
|
||||
?>
|
||||
<script>
|
||||
// charge SweetAlert2 si absent
|
||||
(function(){
|
||||
if (!window.Swal) {
|
||||
var s = document.createElement('script');
|
||||
s.src = "https://cdn.jsdelivr.net/npm/sweetalert2@11";
|
||||
s.defer = true;
|
||||
document.head.appendChild(s);
|
||||
}
|
||||
})();
|
||||
|
||||
// inject style une seule fois
|
||||
(function(){
|
||||
if(!document.getElementById('caf-style')){
|
||||
const css = `
|
||||
.swal2-popup{border-radius:18px;padding:0}
|
||||
.swal2-title{font-size:1.15rem;font-weight:600;margin:0;padding:18px 22px;color:#111;border-bottom:1px solid #eee}
|
||||
.caf-body{padding:18px 22px}
|
||||
.caf-grid{display:grid;gap:10px}
|
||||
.caf-row{display:grid;grid-template-columns:1fr 1fr;gap:10px}
|
||||
.caf-input,.caf-textarea{
|
||||
width:100%;border:1px solid #e5e7eb;border-radius:12px;padding:10px 12px;font-size:14px;outline:none;
|
||||
}
|
||||
.caf-input:focus,.caf-textarea:focus{border-color:#38378B;box-shadow:0 0 0 3px rgba(56,55,139,.12)}
|
||||
.caf-footer{display:flex;gap:10px;justify-content:flex-end;padding:14px 18px;border-top:1px solid #eee}
|
||||
.btn-primary{
|
||||
background:#38378B;color:#fff;border:none;border-radius:999px;padding:10px 14px;font-weight:600;cursor:pointer
|
||||
}
|
||||
.btn-secondary{
|
||||
background:#f3f4f6;color:#111;border:none;border-radius:999px;padding:10px 14px;cursor:pointer
|
||||
}`;
|
||||
const s=document.createElement('style'); s.id='caf-style'; s.textContent=css; document.head.appendChild(s);
|
||||
}
|
||||
})();
|
||||
|
||||
// bouton #open-contact optionnel : si présent on ouvre, sinon tu peux appeler window.openContactAdmin() toi-même
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const btn = document.getElementById('open-contact');
|
||||
if (btn) {
|
||||
btn.addEventListener('click', function(e){
|
||||
e.preventDefault(); window.openContactAdmin && window.openContactAdmin();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
window.openContactAdmin = async function(){
|
||||
try{
|
||||
const res = await fetch('/public/contact_admin.php?meta=1', { credentials: 'same-origin', cache: 'no-store' });
|
||||
const meta = await res.json();
|
||||
if (!meta.ok) { return Swal.fire({icon:'error', text: meta.msg || <?= json_encode($translations['contact_admin_js_error'], JSON_UNESCAPED_UNICODE) ?>}); }
|
||||
const L = meta.labels;
|
||||
const hasIdentity = (meta.prefill?.prenom||'').trim() && (meta.prefill?.nom||'').trim() && (meta.prefill?.email||'').trim();
|
||||
|
||||
const identityBlock = hasIdentity
|
||||
? `<div class="caf-grid" style="margin-bottom:8px">
|
||||
<div class="caf-row">
|
||||
<input id="caf-pre" class="caf-input" value="${meta.prefill.prenom||''}" readonly>
|
||||
<input id="caf-nom" class="caf-input" value="${meta.prefill.nom||''}" readonly>
|
||||
</div>
|
||||
<input id="caf-mail" class="caf-input" value="${meta.prefill.email||''}" readonly>
|
||||
</div>`
|
||||
: `<div class="caf-grid" style="margin-bottom:8px">
|
||||
<div class="caf-row">
|
||||
<input id="caf-pre" class="caf-input" placeholder="${L.firstname}">
|
||||
<input id="caf-nom" class="caf-input" placeholder="${L.lastname}">
|
||||
</div>
|
||||
<input id="caf-mail" class="caf-input" placeholder="${L.email}" type="email">
|
||||
</div>`;
|
||||
|
||||
// attendre Swal si chargé dynamiquement
|
||||
if (!window.Swal) await new Promise(r => setTimeout(r, 50));
|
||||
|
||||
await Swal.fire({
|
||||
title: meta.title,
|
||||
html: `<div class="caf-body">
|
||||
${identityBlock}
|
||||
<div class="caf-grid">
|
||||
<input id="caf-subj" class="caf-input" placeholder="${L.subject}">
|
||||
<textarea id="caf-msg" class="caf-textarea" rows="5" placeholder="${L.message}"></textarea>
|
||||
</div>
|
||||
</div>`,
|
||||
showConfirmButton: false,
|
||||
showCancelButton: false,
|
||||
didOpen: () => {
|
||||
const footer = document.createElement('div');
|
||||
footer.className = 'caf-footer';
|
||||
footer.innerHTML = `
|
||||
<button type="button" class="btn-secondary" id="caf-cancel"><?= $h($translations['contact_admin_close']) ?></button>
|
||||
<button type="button" class="btn-primary" id="caf-send">${L.send}</button>`;
|
||||
document.querySelector('.swal2-popup').appendChild(footer);
|
||||
|
||||
document.getElementById('caf-cancel').onclick = () => Swal.close();
|
||||
document.getElementById('caf-send').onclick = async () => {
|
||||
const prenom = document.getElementById('caf-pre').value.trim();
|
||||
const nom = document.getElementById('caf-nom').value.trim();
|
||||
const email = document.getElementById('caf-mail').value.trim();
|
||||
const sujet = document.getElementById('caf-subj').value.trim();
|
||||
const message = document.getElementById('caf-msg').value.trim();
|
||||
|
||||
if (!prenom || !nom || !email || !sujet || !message) {
|
||||
return Swal.showValidationMessage(L.required);
|
||||
}
|
||||
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
||||
return Swal.showValidationMessage(L.invalid);
|
||||
}
|
||||
|
||||
Swal.showLoading();
|
||||
const fd = new FormData();
|
||||
fd.set('csrf', meta.csrf);
|
||||
fd.set('prenom', prenom);
|
||||
fd.set('nom', nom);
|
||||
fd.set('email', email);
|
||||
fd.set('sujet', sujet);
|
||||
fd.set('message', message);
|
||||
|
||||
const resp = await fetch(meta.endpoint, {
|
||||
method:'POST',
|
||||
headers:{'X-Requested-With':'XMLHttpRequest'},
|
||||
body: fd,
|
||||
credentials:'same-origin'
|
||||
});
|
||||
const j = await resp.json();
|
||||
if (j.ok) Swal.fire({ icon:'success', text: j.msg });
|
||||
else Swal.fire({ icon:'error', text: j.msg || <?= json_encode($translations['contact_admin_js_error'], JSON_UNESCAPED_UNICODE) ?> });
|
||||
};
|
||||
}
|
||||
});
|
||||
}catch(e){
|
||||
console.error(e);
|
||||
alert(<?= json_encode($translations['contact_admin_network_error'], JSON_UNESCAPED_UNICODE) ?>);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<?php
|
||||
}
|
||||
// si inclus dans un contexte non-HTML (JSON/API), ne rien envoyer.
|
||||
|
||||
Reference in new issue
Block a user