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 = 'USLUGI.FR'; $brand_bas ='УСЛУГИ.ФР'; return 'USLUGI.FR
'. (!empty($d['cta_url']) ? '' : ''). '
'.$brand.'
'.nl2br(htmlspecialchars($d['hello'])).'
'.nl2br(htmlspecialchars($d['intro'])).'
'. (!empty($d['format_line']) ? '' : ''). (!empty($d['address_line']) ? '' : ''). '
'.htmlspecialchars($d['who_label']).''.htmlspecialchars($d['who_value']).'
'.htmlspecialchars($t['date_label'] ?? 'Date').''.htmlspecialchars($d['when']).'
'.htmlspecialchars($t['format_label'] ?? 'Формат').''.htmlspecialchars($d['format_line']).'
'.htmlspecialchars($t['adresse_label'] ?? 'Adresse').''.htmlspecialchars($d['address_line']).'
'.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'] ?? 'Ожидает подтверждения').'

'.h($translations['retour_a_la_liste'] ?? 'Retour à la liste').'
'; // 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) ?>

—

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); ?>