isSMTP();
$m->Host = $MAILCFG['smtp']['host']; $m->Port = (int)$MAILCFG['smtp']['port'];
$m->SMTPSecure = $MAILCFG['smtp']['secure'] ?? PHPMailer::ENCRYPTION_STARTTLS;
$m->SMTPAuth = (bool)($MAILCFG['smtp']['auth'] ?? true);
$m->Username = $MAILCFG['smtp']['username'] ?? '';
$m->Password = $MAILCFG['smtp']['password'] ?? '';
$m->CharSet = 'UTF-8'; $m->Encoding = 'base64'; $m->XMailer = '';
if (!empty($MAILCFG['bounce'])) $m->Sender = $MAILCFG['bounce'];
$from = $MAILCFG['from'] ?? ['no-reply@uslugi.fr','USLUGI.FR'];
$rpto = $MAILCFG['reply_to'] ?? ['contact@uslugi.fr','USLUGI.FR'];
$m->setFrom($from[0], $from[1] ?? '');
if (!empty($rpto[0])) $m->addReplyTo($rpto[0], $rpto[1] ?? '');
$m->addAddress($to, $toName);
if (!empty($MAILCFG['suppress_auto'])) {
$m->addCustomHeader('Auto-Submitted','auto-generated');
$m->addCustomHeader('X-Auto-Response-Suppress','All');
}
$m->isHTML(true); $m->Subject = $subject; $m->Body = $html; $m->AltBody = $text;
if ($icsName && $icsContent !== null && $icsMethod) {
$m->addStringAttachment($icsContent, $icsName, 'base64', 'text/calendar; method='.$icsMethod.'; charset=UTF-8');
}
$m->send(); return true;
} catch(Throwable $e){ error_log('send_mail_ics: '.$e->getMessage()); return false; }
}
/* ====== Email template (même rendu pour client/praticien) ====== */
function email_tpl_html(array $d, array $t): string {
$brand = ' ';
$brand_bas ='УСЛУГИ.ФР';
return '
USLUGI.FR
'.$brand.'
'.nl2br(htmlspecialchars($d['hello'])).'
'.nl2br(htmlspecialchars($d['intro'])).'
'.htmlspecialchars($d['who_label']).' '.htmlspecialchars($d['who_value']).'
'.htmlspecialchars($t['date_label'] ?? 'Date').' '.htmlspecialchars($d['when']).' '.
(!empty($d['format_line']) ? ''.htmlspecialchars($t['format_label'] ?? 'Формат').' '.htmlspecialchars($d['format_line']).' ' : '').
(!empty($d['address_line']) ? ''.htmlspecialchars($t['adresse_label'] ?? 'Adresse').' '.htmlspecialchars($d['address_line']).' ' : '').
'
'.
(!empty($d['cta_url']) ? ''.htmlspecialchars($d['cta_label']).' ' : '').
''.nl2br(htmlspecialchars($d['footer'])).'
© '.date('Y').' '.$brand_bas.' — '.htmlspecialchars($t['merci_simple'] ?? 'Merci').'
';
}
function email_tpl_text(array $d, array $t): string {
$L=[]; $L[]=$d['hello']; $L[]=''; $L[]=$d['intro']; $L[]='';
$L[]=($d['who_label']).': '.$d['who_value'];
$L[]=($t['date_label'] ?? 'Date').': '.$d['when'];
if (!empty($d['format_line'])) $L[]=($t['format_label'] ?? 'Формат').': '.$d['format_line'];
if (!empty($d['address_line'])) $L[]=($t['adresse_label'] ?? 'Adresse').': '.$d['address_line'];
if (!empty($d['cta_url'])) { $L[]=''; $L[]=($d['cta_label'] ?? 'Voir les détails').': '.$d['cta_url']; }
$L[]=''; $L[]=$d['footer']; return implode("\n",$L);
}
/* ------- ICS helpers ------- */
function ics_escape(string $s): string {
$s = str_replace("\\","\\\\",$s);
$s = str_replace(";","\;",$s);
$s = str_replace(",","\,",$s);
return preg_replace("/\r\n|\r|\n/","\\n",$s);
}
function ics_build(
string $uid, DateTime $start, DateTime $end, string $tz, string $summary,
string $desc, string $loc, array $attendees, string $method='REQUEST', string $status='CONFIRMED'
): string {
$L=[];
$L[]="BEGIN:VCALENDAR";
$L[]="VERSION:2.0";
$L[]="PRODID:-//УСЛУГИ.ФР//RDV//FR";
$L[]="CALSCALE:GREGORIAN";
$L[]="METHOD:{$method}";
$L[]="BEGIN:VEVENT";
$L[]="UID:{$uid}";
$L[]="DTSTAMP:".gmdate('Ymd\THis\Z');
$L[]="DTSTART;TZID={$tz}:".$start->format('Ymd\THis');
$L[]="DTEND;TZID={$tz}:".$end->format('Ymd\THis');
if ($summary) $L[]="SUMMARY:".ics_escape($summary);
if ($desc) $L[]="DESCRIPTION:".ics_escape($desc);
if ($loc) $L[]="LOCATION:".ics_escape($loc);
$L[]="ORGANIZER;CN=УСЛУГИ.ФР:mailto:rdv@uslugi.fr";
foreach ($attendees as $a) {
if (empty($a['email'])) continue;
$cn = ics_escape($a['cn'] ?? '');
$L[]="ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;CN={$cn};RSVP=TRUE:mailto:".$a['email'];
}
$L[]="STATUS:{$status}";
$L[]="END:VEVENT";
$L[]="END:VCALENDAR";
return implode("\r\n",$L)."\r\n";
}
/* ================== Entrées ================== */
$secretaire_user_id = (int)($_SESSION['compte_id'] ?? 0);
$praticien_id = isset($_GET['praticien_id']) ? (int)$_GET['praticien_id'] : (int)($_POST['praticien_id'] ?? 0);
$startIso = $_GET['start'] ?? ($_POST['start'] ?? null);
$mode_rdv_in = $_GET['mode_rdv'] ?? ($_POST['mode_rdv'] ?? null);
$client_id = isset($_GET['client_id']) ? (int)$_GET['client_id'] : (isset($_POST['client_id']) ? (int)$_POST['client_id'] : 0);
/* ================== Vérif pivot secrétaire ↔ praticien ================== */
$chk = $pdo->prepare("
SELECT can_manage_rdv
FROM secretaire_praticien
WHERE secretaire_user_id = :u
AND praticien_id = :p
AND is_active = 1
LIMIT 1
");
$chk->execute([':u'=>$secretaire_user_id, ':p'=>$praticien_id]);
$pivot = $chk->fetch(PDO::FETCH_ASSOC);
if (!$pivot) { http_response_code(403); exit('Forbidden
'); }
if (((int)($pivot['can_manage_rdv'] ?? 0)) !== 1) {
http_response_code(403);
exit(''.h($translations['erreur_droits_reservation'] ?? 'Droits insuffisants pour réserver').'
');
}
/* ================== Récup praticien ================== */
$stmt = $pdo->prepare("SELECT nom, prenom, duree_rdv, adresse, ville, code_postal, email FROM praticien WHERE id=?");
$stmt->execute([$praticien_id]);
$praticien = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$praticien) { http_response_code(404); exit('Praticien introuvable'); }
$duree = (int)($praticien['duree_rdv'] ?? 60);
$has_address =
!empty(trim((string)($praticien['adresse'] ?? ''))) &&
!empty(trim((string)($praticien['ville'] ?? ''))) &&
!empty(trim((string)($praticien['code_postal'] ?? '')));
/* ================== Mode POST: AJAX (recherche / création / validation) ================== */
if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST') {
/* ----- Recherche dynamique de client plateforme non lié ----- */
// ----- Recherche dynamique de client plateforme non lié -----
if (($_POST['action'] ?? '') === 'search_client') {
header('Content-Type: application/json; charset=utf-8');
if (!hash_equals($_SESSION['csrf_token'] ?? '', $_POST['csrf'] ?? '')) {
echo json_encode(['ok'=>false, 'items'=>[], 'msg'=>'CSRF']); exit;
}
$q = trim((string)($_POST['q'] ?? ''));
if ($q === '' || mb_strlen($q) < 2) { // évite spam / vide
echo json_encode(['ok'=>true, 'items'=>[]]); exit;
}
try {
$like = '%'.$q.'%';
// NOTE: si votre table client n'a PAS adresse/ville/code_postal,
// remplacez la ligne SELECT par: SELECT c.id, c.prenom, c.nom, c.email, c.telephone
$sql = "
SELECT c.id, c.prenom, c.nom, c.email, c.telephone, c.adresse, c.ville, c.code_postal
FROM client c
WHERE (
c.email LIKE :like_email
OR CONCAT(c.prenom,' ',c.nom) LIKE :like_np1
OR CONCAT(c.nom,' ',c.prenom) LIKE :like_np2
OR c.telephone LIKE :like_tel
)
AND NOT EXISTS (
SELECT 1 FROM client_dossiers d
WHERE d.client_id = c.id
AND d.praticien_id = :pid
)
ORDER BY c.prenom, c.nom
LIMIT 12
";
$s = $pdo->prepare($sql);
$s->execute([
':like_email' => $like,
':like_np1' => $like,
':like_np2' => $like,
':like_tel' => $like,
':pid' => $praticien_id,
]);
$items = $s->fetchAll(PDO::FETCH_ASSOC) ?: [];
echo json_encode(['ok'=>true, 'items'=>$items]); exit;
} catch (Throwable $e) {
error_log('search_client: '.$e->getMessage());
echo json_encode(['ok'=>false, 'items'=>[], 'msg'=>'Erreur serveur']); exit;
}
}
// création client rapide ?
if (($_POST['action'] ?? '') === 'create_client') {
header('Content-Type: application/json; charset=utf-8');
if (!hash_equals($_SESSION['csrf_token'] ?? '', $_POST['csrf'] ?? '')) {
http_response_code(400);
exit(json_encode(['ok'=>false,'msg'=>'CSRF']));
}
$nom = trim((string)($_POST['nom'] ?? ''));
$prenom = trim((string)($_POST['prenom'] ?? ''));
$email = email_norm((string)($_POST['email'] ?? ''));
$tel = trim((string)($_POST['telephone'] ?? ''));
$dob = trim((string)($_POST['date_naissance'] ?? '')); // YYYY-MM-DD
if ($nom === '' || $prenom === '' || $email === '' || $tel === '') {
exit(json_encode(['ok'=>false,'msg'=>($translations['donnees_manquantes_reservation'] ?? 'Champs manquants')]));
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
exit(json_encode(['ok'=>false,'msg'=>($translations['email_invalide'] ?? 'Email invalide')]));
}
try {
$pdo->beginTransaction();
// Si un compte existe déjà pour cet email
$acc = $pdo->prepare("SELECT id, role, reference_id FROM compte WHERE email = ? LIMIT 1");
$acc->execute([$email]);
$exist = $acc->fetch(PDO::FETCH_ASSOC);
if ($exist) {
if (($exist['role'] ?? '') !== 'client') {
// Email déjà utilisé par praticien/admin/secretaire => refuser création
$pdo->rollBack();
exit(json_encode(['ok'=>false,'msg'=>($translations['email_deja_utilise'] ?? 'Cet email est déjà utilisé.')]));
}
// Récupérer le client lié via reference_id
$cid = (int)($exist['reference_id'] ?? 0);
if ($cid > 0) {
// Pas de re-création, on renvoie le client existant pour réserver directement
$pdo->commit();
exit(json_encode(['ok'=>true,'client_id'=>$cid,'msg'=>($translations['client_existant_utilise'] ?? 'Client existant sélectionné.')]));
}
// Compte client sans fiche client (rare) -> on crée la fiche et on lie le compte
$insC = $pdo->prepare("
INSERT INTO client (nom, prenom, email, telephone, date_naissance, accepted_cgu)
VALUES (?,?,?,?,?,1)
");
$insC->execute([$nom, $prenom, $email, $tel, ($dob ?: null)]);
$newClientId = (int)$pdo->lastInsertId();
$upd = $pdo->prepare("UPDATE compte SET reference_id = ? WHERE id = ? LIMIT 1");
$upd->execute([$newClientId, (int)$exist['id']]);
$pdo->commit();
exit(json_encode(['ok'=>true,'client_id'=>$newClientId,'msg'=>($translations['client_cree_ok'] ?? 'Client créé.')]));
}
// Sinon : créer la fiche client
$ins = $pdo->prepare("
INSERT INTO client (nom, prenom, email, telephone, date_naissance, accepted_cgu)
VALUES (?,?,?,?,?,1)
");
$ins->execute([$nom, $prenom, $email, $tel, ($dob ?: null)]);
$newClientId = (int)$pdo->lastInsertId();
// Créer le compte avec MDP provisoire hashé + token de validation email
$provisional = 'Uslugi_11992288';
$hash = password_hash($provisional, PASSWORD_DEFAULT);
$token = bin2hex(random_bytes(32)); // TTL 24h géré côté endpoint (via cree_le ou table dédiée)
$accIns = $pdo->prepare("
INSERT INTO compte (email, mot_de_passe, role, reference_id, token_validation, email_valide, cree_le, modifie_le)
VALUES (?,?,?,?,?,0,NOW(),NOW())
");
$accIns->execute([$email, $hash, 'client', $newClientId, $token]);
$pdo->commit();
// Envoyer l'email d'activation (sans ICS)
try {
$activationUrl = 'https://uslugi.fr/valider_email_cabinet.php?token='.urlencode($token).'&email='.urlencode($email);
$subj_a = $translations['activation_compte_sujet'] ?? 'Активируйте ваш аккаунт / Activez votre compte';
$helloName = trim($prenom.' '.$nom);
$dataA = [
'hello' => sprintf($translations['mail_valid_bonjour_nom'] ?? 'Bonjour %s', $helloName),
'intro' => $translations['activation_compte_intro'] ??
"Чтобы завершить создание аккаунта, пожалуйста подтвердите ваш e-mail и задайте новый пароль.\nPour finaliser la création de votre compte, confirmez votre e-mail et définissez votre mot de passe.",
'who_label' => $translations['email_label'] ?? 'E-mail',
'who_value' => $email,
'when' => date('d/m/Y H:i'),
'format_line' => '',
'address_line'=> '',
'cta_url' => $activationUrl,
'cta_label' => $translations['placeholder_nouveau_mdp'] ?? 'Définir le mot de passe',
'footer' => $translations['merci_simple'] ?? 'Merci.',
];
$text_a = email_tpl_text($dataA, $translations);
$html_a = email_tpl_html($dataA, $translations);
// Pas de pièce jointe ICS ici
send_mail_ics($email, $helloName, $subj_a, $text_a, $html_a, null, null, null);
} catch(Throwable $e) {
error_log('activation email send failed: '.$e->getMessage());
}
exit(json_encode([
'ok' => true,
'client_id' => $newClientId,
'msg' => ($translations['client_cree_ok'] ?? 'Client créé.')
]));
} catch (Throwable $e) {
if ($pdo->inTransaction()) $pdo->rollBack();
error_log('create_client: '.$e->getMessage());
exit(json_encode(['ok'=>false,'msg'=>'Erreur']));
}
}
// validation RDV
if (($_POST['action'] ?? '') === 'validate') {
header('Content-Type: application/json; charset=utf-8');
if (!hash_equals($_SESSION['csrf_token'] ?? '', $_POST['csrf'] ?? '')) {
echo json_encode(['ok'=>false,'html'=>'
CSRF ']); exit;
}
$client_id = (int)($_POST['client_id'] ?? 0);
$startIso = $_POST['start'] ?? null;
$mode_in = $_POST['mode_rdv'] ?? null;
if ($client_id <= 0 || !$startIso || $praticien_id <= 0) {
echo json_encode(['ok'=>false,'html'=>'
'.h($translations['donnees_manquantes_reservation'] ?? 'Données manquantes').' ']); exit;
}
// vérifie que le client existe
$c = $pdo->prepare("SELECT id, email, nom, prenom, telephone FROM client WHERE id=?");
$c->execute([$client_id]);
$client = $c->fetch(PDO::FETCH_ASSOC);
if (!$client) {
echo json_encode(['ok'=>false,'html'=>'
Client introuvable ']); exit;
}
// Optionnel: lier au praticien ici si tu veux (sinon, tu as dit que ça se fera à la confirmation)
// $pl = $pdo->prepare("INSERT IGNORE INTO client_dossiers (praticien_id, client_id, cree_le) VALUES (?,?,NOW())");
// $pl->execute([$praticien_id, $client_id]);
// créneau libre ?
$date_sql = (new DateTime($startIso))->format('Y-m-d H:i:s');
$dup = $pdo->prepare("SELECT COUNT(*) FROM rendez_vous WHERE praticien_id=? AND date_rdv=?");
$dup->execute([$praticien_id, $date_sql]);
if ((int)$dup->fetchColumn() > 0) {
echo json_encode(['ok'=>false,'html'=>'
'.h($translations['creneau_deja_reserve'] ?? 'Créneau déjà réservé').' ']); exit;
}
// mode / location
$mode = ($mode_in === $GLOBALS['c_offline']) ? 'offline' : 'online';
if ($mode === 'offline' && !$GLOBALS['has_address']) $mode = 'online';
// insert RDV (statut 'prévu')
$uuid = bin2hex(random_bytes(16));
$ins = $pdo->prepare("
INSERT INTO rendez_vous (praticien_id, client_id, date_rdv, duree_minutes, lieu, statut, commentaire, uuid_ical, created_at)
VALUES (?, ?, ?, ?, ?, 'prévu', '', ?, NOW())
");
$ins->execute([$praticien_id, $client_id, $date_sql, $duree, $mode, $uuid]);
// =========================
// LOG RDV (rdv_actions_log) : create (secrétaire)
// =========================
$rdvIdCreated = (int)$pdo->lastInsertId();
// ✅ compte_id client + praticien (pour badge counters)
$clientCompteId = 0;
$praticienCompteId = 0;
$st = $pdo->prepare("SELECT id FROM compte WHERE role='client' AND reference_id=? LIMIT 1");
$st->execute([(int)$client_id]);
$clientCompteId = (int)($st->fetchColumn() ?: 0);
$st = $pdo->prepare("SELECT id FROM compte WHERE role='praticien' AND reference_id=? LIMIT 1");
$st->execute([(int)$praticien_id]);
$praticienCompteId = (int)($st->fetchColumn() ?: 0);
// ✅ Badge RDV : +1 pour le client (RDV créé par secrétaire)
if ($clientCompteId > 0) {
$pdo->prepare("
INSERT INTO push_badge_counters (compte_id, msg_unread, rdv_unread)
VALUES (:cid, 0, 1)
ON DUPLICATE KEY UPDATE
rdv_unread = LEAST(99, rdv_unread + 1),
updated_at = NOW()
")->execute([':cid' => $clientCompteId]);
}
// ✅ Badge RDV : +1 pour le praticien (nouveau RDV à confirmer/voir)
if ($praticienCompteId > 0) {
$pdo->prepare("
INSERT INTO push_badge_counters (compte_id, msg_unread, rdv_unread)
VALUES (:pid, 0, 1)
ON DUPLICATE KEY UPDATE
rdv_unread = LEAST(99, rdv_unread + 1),
updated_at = NOW()
")->execute([':pid' => $praticienCompteId]);
}
$afterJson = json_encode([
'statut' => 'prévu',
'date_rdv' => $date_sql,
'duree_minutes' => (int)$duree,
'lieu' => $mode, // 'online'/'offline'
'praticien_id' => (int)$praticien_id,
'client_id' => (int)$client_id,
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$logStmt = $pdo->prepare("
INSERT INTO rdv_actions_log
(rdv_id, action, by_user_id, by_role, before_json, after_json, created_at)
VALUES
(?, 'create', ?, 'secretaire', NULL, ?, NOW())
");
$logStmt->execute([$rdvIdCreated, $secretaire_user_id, $afterJson]);
// =========================
// =========================
// PUSH RDV — création (par secrétaire)
// =========================
try {
// 🔔 Client : nouveau RDV créé
push_rdv_to_client(
$pdo,
(int)$client_id,
$translations['push_rdv_created_title'] ?? 'Новая запись',
$translations['push_rdv_created_body'] ?? 'Для вас создана новая запись.',
'/public/client/mes_rendez_vous.php',
[
'rdv_id' => (int)$rdvIdCreated,
'action' => 'create',
'from_role' => 'secretaire',
'praticien_id' => (int)$praticien_id,
'client_id' => (int)$client_id,
'date_rdv' => $date_sql,
]
);
// 🔔 Praticien : nouveau RDV créé par secrétaire
push_rdv_to_praticien(
$pdo,
(int)$praticien_id,
$translations['push_rdv_created_praticien_title'] ?? 'Новая запись',
$translations['push_rdv_created_praticien_body'] ?? 'Секретарь создал(а) новую запись.',
'/public/praticien/rendez_vous.php',
[
'rdv_id' => (int)$rdvIdCreated,
'action' => 'create',
'from_role' => 'secretaire',
'secretaire_id' => (int)$secretaire_user_id,
'praticien_id' => (int)$praticien_id,
'client_id' => (int)$client_id,
'date_rdv' => $date_sql,
]
);
} catch (Throwable $e) {
error_log('push rdv create (secretaire) failed: '.$e->getMessage());
}
// Récup infos complètes pour e-mails
$q = $pdo->prepare("
SELECT rv.id AS rdv_id, rv.date_rdv, rv.duree_minutes, rv.lieu,
p.nom AS nom_praticien, p.prenom AS prenom_praticien, p.email AS email_praticien, p.telephone AS tel_praticien,
p.adresse AS p_adresse, p.ville AS p_ville, p.code_postal AS p_cp,
c.email AS email_client, c.nom AS nom_client, c.prenom AS prenom_client, c.telephone AS tel_client
FROM rendez_vous rv
JOIN praticien p ON rv.praticien_id = p.id
JOIN client c ON rv.client_id = c.id
WHERE rv.uuid_ical = ?
LIMIT 1
");
$q->execute([$uuid]);
$rdv = $q->fetch(PDO::FETCH_ASSOC);
if (!$rdv) {
echo json_encode(['ok'=>false,'html'=>'
RDV introuvable ']); exit;
}
// Construire la réponse UI immédiatement
$tz='Europe/Paris'; $tzObj = new DateTimeZone($tz);
$start = new DateTime($rdv['date_rdv'], $tzObj);
$end = (clone $start)->modify('+'.(int)$rdv['duree_minutes'].' minutes');
$when_a = $start->format('d/m/Y') . ' ' . ($translations['a'] ?? 'à') . ' ' . $start->format('H:i');
$html = '
'.h($translations['block_1'] ?? 'Спасибо за запись').'
'.h($when_a).'
'.h($translations['block_2'] ?? 'Ожидает подтверждения').'
';
// envoyer la réponse puis continuer (si FastCGI)
session_write_close();
echo json_encode(['ok'=>true,'html'=>$html]);
if (function_exists('fastcgi_finish_request')) fastcgi_finish_request();
ignore_user_abort(true);
// e-mails + ICS en arrière-plan
try {
$addr = trim(implode(', ', array_filter([
trim((string)$rdv['p_adresse']),
trim((string)$rdv['p_cp'].' '.$rdv['p_ville'])
])));
$is_offline = ($rdv['lieu'] === 'offline');
$loc = $is_offline ? ($addr ?: ($translations['option_cabinet'] ?? 'Очно')) : ($translations['option_online'] ?? 'Онлайн');
$uid = $uuid;
$ics_client = ics_build(
$uid,$start,$end,$tz,
($translations['rdv_avec'] ?? 'Rendez-vous avec').' '.($rdv['prenom_praticien'].' '.$rdv['nom_praticien']),
'', $loc,
[
['cn'=>$rdv['prenom_praticien'].' '.$rdv['nom_praticien'],'email'=>$rdv['email_praticien']],
['cn'=>$rdv['prenom_client'].' '.$rdv['nom_client'],'email'=>$rdv['email_client']]
],
'REQUEST','CONFIRMED'
);
$ics_prat = ics_build(
$uid,$start,$end,$tz,
($translations['rdv_avec'] ?? 'Rendez-vous avec').' '.($rdv['prenom_client'].' '.$rdv['nom_client']),
'', $loc,
[
['cn'=>$rdv['prenom_client'].' '.$rdv['nom_client'],'email'=>$rdv['email_client']],
['cn'=>$rdv['prenom_praticien'].' '.$rdv['nom_praticien'],'email'=>$rdv['email_praticien']]
],
'REQUEST','CONFIRMED'
);
$cta_client_url='/public/client/mes_rendez_vous.php';
$cta_prat_url ='/public/praticien/rendez_vous.php?id='.(int)$rdv['rdv_id'];
$cta_label = $translations['consultez_espace_pour_details'] ?? 'Voir les détails';
// Client — même template propre que pour le praticien
$subj_c = $translations['email_confirmation_rdv_sujet'] ?? 'Confirmation de rendez-vous';
$dataC = [
'hello' => sprintf($translations['mail_valid_bonjour_nom'] ?? 'Bonjour %s',
trim(($rdv['prenom_client'] ?? '').' '.($rdv['nom_client'] ?? ''))),
'intro' => ($translations['votre_rdv_attente'] ?? 'Votre rendez-vous est en attente de confirmation').' '.
($translations['email_avec'] ?? 'avec').' '.
trim(($rdv['prenom_praticien'] ?? '').' '.($rdv['nom_praticien'] ?? '')).'.',
'who_label' => $translations['praticien_label'] ?? 'Praticien',
'who_value' => trim(($rdv['prenom_praticien'] ?? '').' '.($rdv['nom_praticien'] ?? '')),
'when' => $when_a,
'format_line' => ($rdv['lieu'] === 'offline')
? ($translations['option_cabinet'] ?? 'В кабинете')
: ($translations['option_online'] ?? 'Онлайн'),
'address_line'=> ($rdv['lieu'] === 'offline') ? ($addr ?: '') : '',
'cta_url' => $cta_client_url,
'cta_label' => $translations['consultez_espace_pour_details'] ?? 'Voir les détails',
'footer' => $translations['merci_simple'] ?? 'Merci.',
];
$text_c = email_tpl_text($dataC, $translations);
$html_c = email_tpl_html($dataC, $translations);
send_mail_ics(
(string)$rdv['email_client'],
(string)trim(($rdv['prenom_client'] ?? '').' '.($rdv['nom_client'] ?? '')),
$subj_c, $text_c, $html_c,
'rdv_'.$uid.'_client.ics', $ics_client, 'REQUEST'
);
// Praticien
$subj_p = ($translations['nouveau_rdv_avec'] ?? 'Nouveau rendez-vous avec').' '.($rdv['prenom_client'].' '.$rdv['nom_client']);
$dataP = [
'hello' => ($translations['email_bonjour_dr'] ?? 'Bonjour').' '.($rdv['prenom_praticien'].' '.$rdv['nom_praticien']),
'intro' => ($translations['nouveau_rdv_pris_avec'] ?? 'Un nouveau rendez-vous a été pris.'),
'who_label' => $translations['client_label'] ?? 'Client',
'who_value' => ($rdv['prenom_client'].' '.$rdv['nom_client']),
'when' => $when_a,
'format_line' => ($rdv['lieu'] === 'offline')
? ($translations['option_cabinet'] ?? 'В кабинете')
: ($translations['option_online'] ?? 'Онлайн'),
'address_line'=> ($rdv['lieu'] === 'offline') ? ($addr ?: '') : '',
'cta_url' => $cta_prat_url,
'cta_label' => $cta_label,
'footer' => $translations['merci_simple'] ?? 'Merci.',
];
$text_p = email_tpl_text($dataP, $translations);
$html_p = email_tpl_html($dataP, $translations);
send_mail_ics(
(string)$rdv['email_praticien'],
(string)($rdv['prenom_praticien'].' '.$rdv['nom_praticien']),
$subj_p, $text_p, $html_p,
'rdv_'.$uid.'_praticien.ics', $ics_prat, 'REQUEST'
);
// WhatsApp côté praticien (optionnel)
if (function_exists('Sms\\notify_rdv_en_attente')) {
$waTo = (string)($rdv['tel_praticien'] ?? '');
if ($waTo) {
try { \Sms\notify_rdv_en_attente($waTo, trim($rdv['prenom_client'].' '.$rdv['nom_client']), $start->format('d/m/Y H:i'), (int)$rdv['rdv_id']); }
catch(Throwable $e){ error_log('WA notify error: '.$e->getMessage()); }
}
}
} catch(Throwable $e){
error_log('cabinet valider_rdv bg: '.$e->getMessage());
}
exit;
}
http_response_code(400);
exit('Bad request');
}
/* ================== Mode GET : UI ================== */
$title = $translations['titre_validation_secretaire'] ?? 'Validation du rendez-vous (secrétaire)';
?>
= h($title) ?>
= h($title) ?>
= h(($praticien['prenom'] ?? '').' '.($praticien['nom'] ?? '')) ?> — = h($startIso ?: '') ?>
= h($translations['donnees_manquantes_reservation'] ?? 'Données manquantes pour la réservation.') ?>
0): ?>
prepare("
SELECT c.id, c.prenom, c.nom, c.email, c.telephone
FROM client_dossiers d
JOIN client c ON c.id = d.client_id
WHERE d.praticien_id = ?
ORDER BY c.prenom, c.nom
");
$cli->execute([$praticien_id]);
$clients = $cli->fetchAll(PDO::FETCH_ASSOC);
?>
= h($translations['selectionner_client'] ?? 'Sélectionner un client') ?>
= h($translations['client_existant_placeholder'] ?? '— Choisir un client existant —') ?>
= h(trim(($c['prenom'] ?? '').' '.($c['nom'] ?? ''))) ?>