Version stable, aprelic, connexion CFRS et autres corrections
This commit is contained in:
1 parent
865de8a976
commit
e9d0010451
62 files changed
+17483
-7256
No files matched your search
@@ -1,27 +1,88 @@
|
||||
<?php
|
||||
declare (strict_types = 1);
|
||||
|
||||
require_once __DIR__ . '/../includes/init_client.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
|
||||
|
||||
$client_id = $_SESSION['reference_id'];
|
||||
$praticien_id = intval($_POST['praticien_id'] ?? 0);
|
||||
|
||||
if ($praticien_id > 0) {
|
||||
|
||||
|
||||
// Évite les doublons
|
||||
$check = $pdo->prepare("SELECT * FROM praticien_client WHERE client_id = ? AND praticien_id = ?");
|
||||
$check->execute([$client_id, $praticien_id]);
|
||||
|
||||
if (!$check->fetch()) {
|
||||
$stmt = $pdo->prepare("INSERT INTO praticien_client (client_id, praticien_id) VALUES (?, ?)");
|
||||
$stmt->execute([$client_id, $praticien_id]);
|
||||
echo $translations['praticien_ajoute_favoris'];
|
||||
} else {
|
||||
echo $translations['praticien_deja_ajoute'];
|
||||
}
|
||||
} else {
|
||||
echo $translations['praticien_id_invalide'];
|
||||
if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
|
||||
echo json_encode([
|
||||
'ok' => false,
|
||||
'message' => 'Méthode non autorisée.',
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
require_post_csrf();
|
||||
|
||||
$client_id = (int) ($_SESSION['reference_id'] ?? 0);
|
||||
$praticien_id = (int) ($_POST['praticien_id'] ?? 0);
|
||||
|
||||
if ($client_id <= 0) {
|
||||
echo json_encode([
|
||||
'ok' => false,
|
||||
'message' => $translations['erreur_generique'] ?? 'Erreur.',
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($praticien_id <= 0) {
|
||||
echo json_encode([
|
||||
'ok' => false,
|
||||
'message' => $translations['praticien_id_invalide'] ?? 'ID praticien invalide.',
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
// Vérifie que le praticien existe
|
||||
$exists = $pdo->prepare("SELECT id FROM praticien WHERE id = ? LIMIT 1");
|
||||
$exists->execute([$praticien_id]);
|
||||
|
||||
if (! $exists->fetchColumn()) {
|
||||
echo json_encode([
|
||||
'ok' => false,
|
||||
'message' => $translations['praticien_id_invalide'] ?? 'ID praticien invalide.',
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Évite les doublons
|
||||
$check = $pdo->prepare("
|
||||
SELECT 1
|
||||
FROM praticien_client
|
||||
WHERE client_id = ? AND praticien_id = ?
|
||||
LIMIT 1
|
||||
");
|
||||
$check->execute([$client_id, $praticien_id]);
|
||||
|
||||
if ($check->fetchColumn()) {
|
||||
echo json_encode([
|
||||
'ok' => true,
|
||||
'already_exists' => true,
|
||||
'message' => $translations['praticien_deja_ajoute'] ?? 'Déjà ajouté aux favoris.',
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO praticien_client (client_id, praticien_id)
|
||||
VALUES (?, ?)
|
||||
");
|
||||
$stmt->execute([$client_id, $praticien_id]);
|
||||
|
||||
echo json_encode([
|
||||
'ok' => true,
|
||||
'already_exists' => false,
|
||||
'message' => $translations['praticien_ajoute_favoris'] ?? 'Praticien ajouté aux favoris.',
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
|
||||
} catch (Throwable $e) {
|
||||
error_log('ajouter_favorit.php: ' . $e->getMessage());
|
||||
echo json_encode([
|
||||
'ok' => false,
|
||||
'message' => $translations['erreur_generique'] ?? 'Erreur serveur.',
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
+347
-318
@@ -1,310 +1,333 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../includes/init_client.php';
|
||||
require_once __DIR__ . '/../../push/push_endpoint.php';
|
||||
|
||||
require_post_csrf();
|
||||
|
||||
// PHPMailer (config centralisée)
|
||||
|
||||
|
||||
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
use PHPMailer\PHPMailer\Exception;
|
||||
|
||||
// --------- Réponse JSON utilitaire ----------
|
||||
function json_out(bool $ok, string $msg, array $extra = []): never {
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode(['ok'=>$ok, 'message'=>$msg] + $extra);
|
||||
exit;
|
||||
function json_out(bool $ok, string $msg, array $extra = []): never
|
||||
{
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode(['ok' => $ok, 'message' => $msg] + $extra);
|
||||
exit;
|
||||
}
|
||||
|
||||
// --------- Envoi e-mail via PHPMailer (avec ICS optionnel + fallback) ----------
|
||||
function send_email_phpmailer(
|
||||
string $toEmail,
|
||||
string $subject,
|
||||
string $textBody,
|
||||
string $htmlBody,
|
||||
?string $icsFilename = null,
|
||||
?string $icsContent = null
|
||||
string $toEmail,
|
||||
string $subject,
|
||||
string $textBody,
|
||||
string $htmlBody,
|
||||
?string $icsFilename = null,
|
||||
?string $icsContent = null
|
||||
): array {
|
||||
global $MAILCFG;
|
||||
global $MAILCFG;
|
||||
|
||||
$mail = new PHPMailer(true);
|
||||
try {
|
||||
// SMTP
|
||||
$mail->isSMTP();
|
||||
$mail->Host = $MAILCFG['smtp']['host'];
|
||||
$mail->Port = $MAILCFG['smtp']['port'];
|
||||
$mail->SMTPSecure = $MAILCFG['smtp']['secure'];
|
||||
$mail->SMTPAuth = $MAILCFG['smtp']['auth'];
|
||||
$mail->Username = $MAILCFG['smtp']['username'];
|
||||
$mail->Password = $MAILCFG['smtp']['password'];
|
||||
|
||||
$mail->CharSet = 'UTF-8';
|
||||
$mail->Encoding = 'base64';
|
||||
$mail->XMailer = '';
|
||||
|
||||
// Debug ponctuel : ?debug_mail=1
|
||||
if (!empty($_GET['debug_mail'])) {
|
||||
$mail->SMTPDebug = 2;
|
||||
$mail->Debugoutput = function($str,$lvl){ error_log("SMTP[$lvl] ".$str); };
|
||||
}
|
||||
|
||||
if (!empty($MAILCFG['bounce'])) $mail->Sender = $MAILCFG['bounce'];
|
||||
|
||||
$from = $MAILCFG['from'] ?? ['no-reply@uslugi.fr','USLUGI.FR'];
|
||||
$rpto = $MAILCFG['reply_to'] ?? ['contact@uslugi.fr','USLUGI.FR'];
|
||||
$mail->setFrom($from[0], $from[1] ?? '');
|
||||
if (!empty($rpto[0])) $mail->addReplyTo($rpto[0], $rpto[1] ?? '');
|
||||
|
||||
$mail->addAddress($toEmail);
|
||||
|
||||
if (!empty($MAILCFG['suppress_auto'])) {
|
||||
$mail->addCustomHeader('Auto-Submitted', 'auto-generated');
|
||||
$mail->addCustomHeader('X-Auto-Response-Suppress', 'All');
|
||||
}
|
||||
|
||||
$mail->isHTML(true);
|
||||
$mail->Subject = $subject;
|
||||
$mail->Body = $htmlBody;
|
||||
$mail->AltBody = $textBody;
|
||||
|
||||
if ($icsFilename && $icsContent !== null && empty($_GET['no_ics'])) {
|
||||
$mail->addStringAttachment(
|
||||
$icsContent,
|
||||
$icsFilename,
|
||||
'base64',
|
||||
'text/calendar; method=CANCEL; charset=UTF-8'
|
||||
);
|
||||
}
|
||||
|
||||
$mail->send();
|
||||
return [true, null];
|
||||
|
||||
} catch (Throwable $e) {
|
||||
$err = 'PHPMailer error: '.$e->getMessage();
|
||||
if (!empty($mail->ErrorInfo)) $err .= ' | ErrorInfo: '.$mail->ErrorInfo;
|
||||
error_log($err);
|
||||
|
||||
// Fallback : réessaye SANS ICS
|
||||
$mail = new PHPMailer(true);
|
||||
try {
|
||||
if ($icsFilename && $icsContent !== null && empty($_GET['no_ics'])) {
|
||||
$mail->clearAttachments();
|
||||
// SMTP
|
||||
$mail->isSMTP();
|
||||
$mail->Host = $MAILCFG['smtp']['host'];
|
||||
$mail->Port = $MAILCFG['smtp']['port'];
|
||||
$mail->SMTPSecure = $MAILCFG['smtp']['secure'];
|
||||
$mail->SMTPAuth = $MAILCFG['smtp']['auth'];
|
||||
$mail->Username = $MAILCFG['smtp']['username'];
|
||||
$mail->Password = $MAILCFG['smtp']['password'];
|
||||
|
||||
$mail->CharSet = 'UTF-8';
|
||||
$mail->Encoding = 'base64';
|
||||
$mail->XMailer = '';
|
||||
|
||||
// Debug ponctuel : ?debug_mail=1
|
||||
if (! empty($_GET['debug_mail'])) {
|
||||
$mail->SMTPDebug = 2;
|
||||
$mail->Debugoutput = function ($str, $lvl) {error_log("SMTP[$lvl] " . $str);};
|
||||
}
|
||||
|
||||
if (! empty($MAILCFG['bounce'])) {
|
||||
$mail->Sender = $MAILCFG['bounce'];
|
||||
}
|
||||
|
||||
$from = $MAILCFG['from'] ?? ['no-reply@uslugi.fr', 'USLUGI.FR'];
|
||||
$rpto = $MAILCFG['reply_to'] ?? ['contact@uslugi.fr', 'USLUGI.FR'];
|
||||
$mail->setFrom($from[0], $from[1] ?? '');
|
||||
if (! empty($rpto[0])) {
|
||||
$mail->addReplyTo($rpto[0], $rpto[1] ?? '');
|
||||
}
|
||||
|
||||
$mail->addAddress($toEmail);
|
||||
|
||||
if (! empty($MAILCFG['suppress_auto'])) {
|
||||
$mail->addCustomHeader('Auto-Submitted', 'auto-generated');
|
||||
$mail->addCustomHeader('X-Auto-Response-Suppress', 'All');
|
||||
}
|
||||
|
||||
$mail->isHTML(true);
|
||||
$mail->Subject = $subject;
|
||||
$mail->Body = $htmlBody;
|
||||
$mail->AltBody = $textBody;
|
||||
|
||||
if ($icsFilename && $icsContent !== null && empty($_GET['no_ics'])) {
|
||||
$mail->addStringAttachment(
|
||||
$icsContent,
|
||||
$icsFilename,
|
||||
'base64',
|
||||
'text/calendar; method=CANCEL; charset=UTF-8'
|
||||
);
|
||||
}
|
||||
|
||||
$mail->send();
|
||||
return [true, 'sent_without_ics'];
|
||||
}
|
||||
} catch (Throwable $e2) {
|
||||
$err2 = 'PHPMailer fallback error: '.$e2->getMessage();
|
||||
if (!empty($mail->ErrorInfo)) $err2 .= ' | ErrorInfo: '.$mail->ErrorInfo;
|
||||
error_log($err2);
|
||||
return [true, null];
|
||||
|
||||
} catch (Throwable $e) {
|
||||
$err = 'PHPMailer error: ' . $e->getMessage();
|
||||
if (! empty($mail->ErrorInfo)) {
|
||||
$err .= ' | ErrorInfo: ' . $mail->ErrorInfo;
|
||||
}
|
||||
|
||||
error_log($err);
|
||||
|
||||
// Fallback : réessaye SANS ICS
|
||||
try {
|
||||
if ($icsFilename && $icsContent !== null && empty($_GET['no_ics'])) {
|
||||
$mail->clearAttachments();
|
||||
$mail->send();
|
||||
return [true, 'sent_without_ics'];
|
||||
}
|
||||
} catch (Throwable $e2) {
|
||||
$err2 = 'PHPMailer fallback error: ' . $e2->getMessage();
|
||||
if (! empty($mail->ErrorInfo)) {
|
||||
$err2 .= ' | ErrorInfo: ' . $mail->ErrorInfo;
|
||||
}
|
||||
|
||||
error_log($err2);
|
||||
}
|
||||
return [false, $err];
|
||||
}
|
||||
return [false, $err];
|
||||
}
|
||||
}
|
||||
|
||||
// --------- Helpers e-mail (HTML/Texte) ----------
|
||||
function email_html_cancel(array $data, array $t): string {
|
||||
$brand = '<img src="https://uslugi.fr/public/uploads/img/logo_mail.png" width="150" alt="USLUGI.FR">';
|
||||
$brand_bas ='УСЛУГИ.ФР';
|
||||
return '<!DOCTYPE html><html lang="ru"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>USLUGI.FR</title></head>
|
||||
function email_html_cancel(array $data, array $t): string
|
||||
{
|
||||
$brand = '<img src="https://uslugi.fr/public/uploads/img/logo_mail.png" width="150" alt="USLUGI.FR">';
|
||||
$brand_bas = 'УСЛУГИ.ФР';
|
||||
return '<!DOCTYPE html><html lang="ru"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>USLUGI.FR</title></head>
|
||||
<body style="margin:0;padding:0;background:#f4f6f8;font-family:Arial,Helvetica,sans-serif;">
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:#f4f6f8;padding:24px 0;">
|
||||
<tr><td align="center">
|
||||
<table role="presentation" width="600" cellpadding="0" cellspacing="0" style="background:#ffffff;border-radius:8px;overflow:hidden;">
|
||||
<tr><td style="background:#38378B;color:#ffffff;padding:20px 24px;font-size:18px;font-weight:600;">'.$brand.'</td></tr>
|
||||
<tr><td style="padding:24px 24px 8px 24px;color:#111;font-size:16px;line-height:1.5;">'.nl2br(htmlspecialchars($data['hello_line'])).'</td></tr>
|
||||
<tr><td style="padding:8px 24px;color:#111;font-size:16px;line-height:1.6;">'.nl2br(htmlspecialchars($data['intro'])).'</td></tr>
|
||||
<tr><td style="background:#38378B;color:#ffffff;padding:20px 24px;font-size:18px;font-weight:600;">' . $brand . '</td></tr>
|
||||
<tr><td style="padding:24px 24px 8px 24px;color:#111;font-size:16px;line-height:1.5;">' . nl2br(htmlspecialchars($data['hello_line'])) . '</td></tr>
|
||||
<tr><td style="padding:8px 24px;color:#111;font-size:16px;line-height:1.6;">' . nl2br(htmlspecialchars($data['intro'])) . '</td></tr>
|
||||
<tr><td style="padding:16px 24px;">
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="border:1px solid #e5e7eb;border-radius:8px;">
|
||||
<tr><td style="padding:14px 16px;font-size:14px;color:#6b7280;border-bottom:1px solid #e5e7eb;">'.htmlspecialchars($data['who_label']).'</td><td style="padding:14px 16px;font-size:14px;color:#111;">'.htmlspecialchars($data['who_value']).'</td></tr>
|
||||
<tr><td style="padding:14px 16px;font-size:14px;color:#6b7280;">'.htmlspecialchars($data['date_label']).'</td><td style="padding:14px 16px;font-size:14px;color:#111;">'.htmlspecialchars($data['when']).'</td></tr>
|
||||
<tr><td style="padding:14px 16px;font-size:14px;color:#6b7280;">'.htmlspecialchars($data['statut_label']).'</td><td style="padding:14px 16px;font-size:14px;color:#111;">'.htmlspecialchars($data['statut_value']).'</td></tr>
|
||||
<tr><td style="padding:14px 16px;font-size:14px;color:#6b7280;border-bottom:1px solid #e5e7eb;">' . htmlspecialchars($data['who_label']) . '</td><td style="padding:14px 16px;font-size:14px;color:#111;">' . htmlspecialchars($data['who_value']) . '</td></tr>
|
||||
<tr><td style="padding:14px 16px;font-size:14px;color:#6b7280;">' . htmlspecialchars($data['date_label']) . '</td><td style="padding:14px 16px;font-size:14px;color:#111;">' . htmlspecialchars($data['when']) . '</td></tr>
|
||||
<tr><td style="padding:14px 16px;font-size:14px;color:#6b7280;">' . htmlspecialchars($data['statut_label']) . '</td><td style="padding:14px 16px;font-size:14px;color:#111;">' . htmlspecialchars($data['statut_value']) . '</td></tr>
|
||||
</table>
|
||||
</td></tr>'.
|
||||
(!empty($data['cta_url']) ? '<tr><td align="center" style="padding:8px 24px 24px 24px;"><a href="'.htmlspecialchars($data['cta_url']).'" target="_blank" style="display:inline-block;text-decoration:none;padding:12px 20px;border-radius:999px;background:#E03C3F;color:#fff;font-weight:600;">'.htmlspecialchars($data['cta_label']).'</a></td></tr>' : '').
|
||||
'<tr><td style="padding:8px 24px 24px 24px;color:#111;font-size:16px;line-height:1.6;">'.nl2br(htmlspecialchars($data['footer'])).'</td></tr>
|
||||
</td></tr>' .
|
||||
(! empty($data['cta_url']) ? '<tr><td align="center" style="padding:8px 24px 24px 24px;"><a href="' . htmlspecialchars($data['cta_url']) . '" target="_blank" style="display:inline-block;text-decoration:none;padding:12px 20px;border-radius:999px;background:#E03C3F;color:#fff;font-weight:600;">' . htmlspecialchars($data['cta_label']) . '</a></td></tr>' : '') .
|
||||
'<tr><td style="padding:8px 24px 24px 24px;color:#111;font-size:16px;line-height:1.6;">' . nl2br(htmlspecialchars($data['footer'])) . '</td></tr>
|
||||
</table>
|
||||
<table role="presentation" width="600" cellpadding="0" cellspacing="0" style="margin-top:12px;"><tr><td style="text-align:center;color:#9ca3af;font-size:12px;padding:8px 0;">© '.date('Y').' '.$brand_bas.' — '.htmlspecialchars($t['merci_simple'] ?? 'Merci').'</td></tr></table>
|
||||
<table role="presentation" width="600" cellpadding="0" cellspacing="0" style="margin-top:12px;"><tr><td style="text-align:center;color:#9ca3af;font-size:12px;padding:8px 0;">© ' . date('Y') . ' ' . $brand_bas . ' — ' . htmlspecialchars($t['merci_simple'] ?? 'Merci') . '</td></tr></table>
|
||||
</td></tr>
|
||||
</table></body></html>';
|
||||
}
|
||||
function email_text_cancel(array $data, array $t): string {
|
||||
$lines = [];
|
||||
$lines[] = $data['hello_line']; $lines[]='';
|
||||
$lines[] = $data['intro']; $lines[]='';
|
||||
$lines[] = $data['who_label'].': '.$data['who_value'];
|
||||
$lines[] = $data['date_label'].': '.$data['when'];
|
||||
$lines[] = $data['statut_label'].': '.$data['statut_value'];
|
||||
if (!empty($data['cta_url'])) { $lines[]=''; $lines[] = $data['cta_label'].': '.$data['cta_url']; }
|
||||
$lines[]=''; $lines[] = $data['footer'];
|
||||
return implode("\n", $lines);
|
||||
function email_text_cancel(array $data, array $t): string
|
||||
{
|
||||
$lines = [];
|
||||
$lines[] = $data['hello_line'];
|
||||
$lines[] = '';
|
||||
$lines[] = $data['intro'];
|
||||
$lines[] = '';
|
||||
$lines[] = $data['who_label'] . ': ' . $data['who_value'];
|
||||
$lines[] = $data['date_label'] . ': ' . $data['when'];
|
||||
$lines[] = $data['statut_label'] . ': ' . $data['statut_value'];
|
||||
if (! empty($data['cta_url'])) {$lines[] = '';
|
||||
$lines[] = $data['cta_label'] . ': ' . $data['cta_url'];}
|
||||
$lines[] = '';
|
||||
$lines[] = $data['footer'];
|
||||
return implode("\n", $lines);
|
||||
}
|
||||
|
||||
// --------- ICS CANCEL ----------
|
||||
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_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 build_ics_cancel(
|
||||
string $uid,
|
||||
DateTime $start,
|
||||
?DateTime $end,
|
||||
string $timezone,
|
||||
string $summary,
|
||||
string $organizerEmail,
|
||||
array $attendees // [['cn'=>..., 'email'=>...]]
|
||||
string $uid,
|
||||
DateTime $start,
|
||||
?DateTime $end,
|
||||
string $timezone,
|
||||
string $summary,
|
||||
string $organizerEmail,
|
||||
array $attendees // [['cn'=>..., 'email'=>...]]
|
||||
): string {
|
||||
$lines=[];
|
||||
$lines[]="BEGIN:VCALENDAR";
|
||||
$lines[]="VERSION:2.0";
|
||||
$lines[]="PRODID:-//УСЛУГИ.ФР//RDV//FR";
|
||||
$lines[]="METHOD:CANCEL";
|
||||
$lines[]="BEGIN:VEVENT";
|
||||
$lines[]="UID:".$uid;
|
||||
$lines[]="DTSTAMP:".gmdate('Ymd\THis\Z');
|
||||
$lines[]="STATUS:CANCELLED";
|
||||
$lines[]="SEQUENCE:2";
|
||||
$lines[]="SUMMARY:".ics_escape($summary);
|
||||
if ($start) $lines[]="DTSTART;TZID={$timezone}:".$start->format('Ymd\THis');
|
||||
if ($end) $lines[]="DTEND;TZID={$timezone}:".$end->format('Ymd\THis');
|
||||
$lines[]="ORGANIZER:mailto:".$organizerEmail;
|
||||
foreach ($attendees as $a) {
|
||||
if (empty($a['email'])) continue;
|
||||
$cn = ics_escape($a['cn'] ?? '');
|
||||
$lines[]="ATTENDEE;ROLE=REQ-PARTICIPANT;CN={$cn}:mailto:".$a['email'];
|
||||
}
|
||||
$lines[]="END:VEVENT";
|
||||
$lines[]="END:VCALENDAR";
|
||||
return implode("\r\n",$lines)."\r\n";
|
||||
$lines = [];
|
||||
$lines[] = "BEGIN:VCALENDAR";
|
||||
$lines[] = "VERSION:2.0";
|
||||
$lines[] = "PRODID:-//УСЛУГИ.ФР//RDV//FR";
|
||||
$lines[] = "METHOD:CANCEL";
|
||||
$lines[] = "BEGIN:VEVENT";
|
||||
$lines[] = "UID:" . $uid;
|
||||
$lines[] = "DTSTAMP:" . gmdate('Ymd\THis\Z');
|
||||
$lines[] = "STATUS:CANCELLED";
|
||||
$lines[] = "SEQUENCE:2";
|
||||
$lines[] = "SUMMARY:" . ics_escape($summary);
|
||||
if ($start) {
|
||||
$lines[] = "DTSTART;TZID={$timezone}:" . $start->format('Ymd\THis');
|
||||
}
|
||||
|
||||
if ($end) {
|
||||
$lines[] = "DTEND;TZID={$timezone}:" . $end->format('Ymd\THis');
|
||||
}
|
||||
|
||||
$lines[] = "ORGANIZER:mailto:" . $organizerEmail;
|
||||
foreach ($attendees as $a) {
|
||||
if (empty($a['email'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$cn = ics_escape($a['cn'] ?? '');
|
||||
$lines[] = "ATTENDEE;ROLE=REQ-PARTICIPANT;CN={$cn}:mailto:" . $a['email'];
|
||||
}
|
||||
$lines[] = "END:VEVENT";
|
||||
$lines[] = "END:VCALENDAR";
|
||||
return implode("\r\n", $lines) . "\r\n";
|
||||
}
|
||||
|
||||
// =================== LOGIQUE ===================
|
||||
if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
|
||||
json_out(false, 'Méthode invalide.');
|
||||
json_out(false, 'Méthode invalide.');
|
||||
}
|
||||
|
||||
$client_id = $_SESSION['reference_id'] ?? null;
|
||||
$id = (int)($_POST['id'] ?? 0);
|
||||
if (!$client_id || $id <= 0) {
|
||||
json_out(false, $translations['erreur_generique'] ?? 'Erreur.');
|
||||
$id = (int) ($_POST['id'] ?? 0);
|
||||
if (! $client_id || $id <= 0) {
|
||||
json_out(false, $translations['erreur_generique'] ?? 'Erreur.');
|
||||
}
|
||||
|
||||
try {
|
||||
// =========================
|
||||
// SNAPSHOT AVANT ANNULATION
|
||||
// =========================
|
||||
$beforeStmt = $pdo->prepare("
|
||||
// SNAPSHOT AVANT ANNULATION
|
||||
// =========================
|
||||
$beforeStmt = $pdo->prepare("
|
||||
SELECT statut, date_rdv, duree_minutes, lieu, praticien_id, client_id
|
||||
FROM rendez_vous
|
||||
WHERE id = ? AND client_id = ?
|
||||
LIMIT 1
|
||||
");
|
||||
$beforeStmt->execute([$id, $client_id]);
|
||||
$beforeRow = $beforeStmt->fetch(PDO::FETCH_ASSOC);
|
||||
$beforeStmt->execute([$id, $client_id]);
|
||||
$beforeRow = $beforeStmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$beforeRow) {
|
||||
json_out(false, $translations['erreur_generique'] ?? 'Rendez-vous introuvable.');
|
||||
exit;
|
||||
}
|
||||
if (! $beforeRow) {
|
||||
json_out(false, $translations['erreur_generique'] ?? 'Rendez-vous introuvable.');
|
||||
exit;
|
||||
}
|
||||
// ✅ compte_id praticien + client (pour badge counters)
|
||||
$praticienCompteId = 0;
|
||||
$clientCompteId = 0;
|
||||
$praticienCompteId = 0;
|
||||
$clientCompteId = 0;
|
||||
|
||||
if (!empty($beforeRow['praticien_id'])) {
|
||||
$st = $pdo->prepare("SELECT id FROM compte WHERE role='praticien' AND reference_id=? LIMIT 1");
|
||||
$st->execute([(int)$beforeRow['praticien_id']]);
|
||||
$praticienCompteId = (int)($st->fetchColumn() ?: 0);
|
||||
}
|
||||
if (!empty($beforeRow['client_id'])) {
|
||||
$st = $pdo->prepare("SELECT id FROM compte WHERE role='client' AND reference_id=? LIMIT 1");
|
||||
$st->execute([(int)$beforeRow['client_id']]);
|
||||
$clientCompteId = (int)($st->fetchColumn() ?: 0);
|
||||
}
|
||||
if (! empty($beforeRow['praticien_id'])) {
|
||||
$st = $pdo->prepare("SELECT id FROM compte WHERE role='praticien' AND reference_id=? LIMIT 1");
|
||||
$st->execute([(int) $beforeRow['praticien_id']]);
|
||||
$praticienCompteId = (int) ($st->fetchColumn() ?: 0);
|
||||
}
|
||||
if (! empty($beforeRow['client_id'])) {
|
||||
$st = $pdo->prepare("SELECT id FROM compte WHERE role='client' AND reference_id=? LIMIT 1");
|
||||
$st->execute([(int) $beforeRow['client_id']]);
|
||||
$clientCompteId = (int) ($st->fetchColumn() ?: 0);
|
||||
}
|
||||
|
||||
// 1) Annuler (contrainte client_id)
|
||||
$stmt = $pdo->prepare("UPDATE rendez_vous SET statut = 'annulé' WHERE id = ? AND client_id = ?");
|
||||
$stmt->execute([$id, $client_id]);
|
||||
// 1) Annuler (contrainte client_id)
|
||||
$stmt = $pdo->prepare("UPDATE rendez_vous SET statut = 'annulé' WHERE id = ? AND client_id = ?");
|
||||
$stmt->execute([$id, $client_id]);
|
||||
|
||||
if ($stmt->rowCount() === 0) {
|
||||
// Rien modifié -> soit déjà annulé, soit pas à ce client, soit ID invalide
|
||||
json_out(false, $translations['aucune_modification'] ?? 'Aucune modification effectuée.');
|
||||
}
|
||||
if ($stmt->rowCount() === 0) {
|
||||
// Rien modifié -> soit déjà annulé, soit pas à ce client, soit ID invalide
|
||||
json_out(false, $translations['aucune_modification'] ?? 'Aucune modification effectuée.');
|
||||
}
|
||||
|
||||
// =========================
|
||||
// LOG RDV (rdv_actions_log) : cancel
|
||||
// =========================
|
||||
$beforeJson = json_encode([
|
||||
'statut' => $beforeRow['statut'],
|
||||
'date_rdv' => $beforeRow['date_rdv'],
|
||||
'duree_minutes' => (int)$beforeRow['duree_minutes'],
|
||||
'lieu' => $beforeRow['lieu'],
|
||||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
// =========================
|
||||
// LOG RDV (rdv_actions_log) : cancel
|
||||
// =========================
|
||||
$beforeJson = json_encode([
|
||||
'statut' => $beforeRow['statut'],
|
||||
'date_rdv' => $beforeRow['date_rdv'],
|
||||
'duree_minutes' => (int) $beforeRow['duree_minutes'],
|
||||
'lieu' => $beforeRow['lieu'],
|
||||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
|
||||
$afterJson = json_encode([
|
||||
'statut' => 'annulé',
|
||||
'date_rdv' => $beforeRow['date_rdv'],
|
||||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
$afterJson = json_encode([
|
||||
'statut' => 'annulé',
|
||||
'date_rdv' => $beforeRow['date_rdv'],
|
||||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
|
||||
$logStmt = $pdo->prepare("
|
||||
$logStmt = $pdo->prepare("
|
||||
INSERT INTO rdv_actions_log
|
||||
(rdv_id, action, by_user_id, by_role, before_json, after_json, created_at)
|
||||
VALUES
|
||||
(?, 'cancel', ?, 'client', ?, ?, NOW())
|
||||
");
|
||||
$logStmt->execute([(int)$id, (int)$client_id, $beforeJson, $afterJson]);
|
||||
// =========================
|
||||
$logStmt->execute([(int) $id, (int) $client_id, $beforeJson, $afterJson]);
|
||||
// =========================
|
||||
// PUSH RDV — annulation (client)
|
||||
// =========================
|
||||
try {
|
||||
// ✅ Badge RDV : +1 pour le praticien (cancel par client)
|
||||
if ($praticienCompteId > 0) {
|
||||
$pdo->prepare("
|
||||
try {
|
||||
// ✅ Badge RDV : +1 pour le praticien (cancel par client)
|
||||
if ($praticienCompteId > 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' => $praticienCompteId]);
|
||||
}
|
||||
}
|
||||
|
||||
// 🔔 Praticien : RDV annulé par le client
|
||||
push_rdv_to_praticien(
|
||||
$pdo,
|
||||
(int)$beforeRow['praticien_id'],
|
||||
$translations['push_rdv_cancel_praticien_title'] ?? 'Запись отменена',
|
||||
$translations['push_rdv_cancel_praticien_body'] ?? 'Клиент отменил запись.',
|
||||
'/public/praticien/rendez_vous.php?id='.(int)$id,
|
||||
[
|
||||
'rdv_id' => (int)$id,
|
||||
'action' => 'cancel',
|
||||
'from_role' => 'client',
|
||||
'client_id' => (int)$beforeRow['client_id'],
|
||||
'praticien_id' => (int)$beforeRow['praticien_id'],
|
||||
'date_rdv' => $beforeRow['date_rdv'],
|
||||
]
|
||||
);
|
||||
// 🔔 Praticien : RDV annulé par le client
|
||||
push_rdv_to_praticien(
|
||||
$pdo,
|
||||
(int) $beforeRow['praticien_id'],
|
||||
$translations['push_rdv_cancel_praticien_title'] ?? 'Запись отменена',
|
||||
$translations['push_rdv_cancel_praticien_body'] ?? 'Клиент отменил запись.',
|
||||
'/public/praticien/rendez_vous.php?id=' . (int) $id,
|
||||
[
|
||||
'rdv_id' => (int) $id,
|
||||
'action' => 'cancel',
|
||||
'from_role' => 'client',
|
||||
'client_id' => (int) $beforeRow['client_id'],
|
||||
'praticien_id' => (int) $beforeRow['praticien_id'],
|
||||
'date_rdv' => $beforeRow['date_rdv'],
|
||||
]
|
||||
);
|
||||
|
||||
// 🔔 Client : confirmation annulation
|
||||
push_rdv_to_client(
|
||||
$pdo,
|
||||
(int)$beforeRow['client_id'],
|
||||
$translations['push_rdv_cancel_client_title'] ?? 'Запись отменена',
|
||||
$translations['push_rdv_cancel_client_body'] ?? 'Ваша запись была успешно отменена.',
|
||||
'/public/client/mes_rendez_vous.php',
|
||||
[
|
||||
'rdv_id' => (int)$id,
|
||||
'action' => 'cancel'
|
||||
]
|
||||
);
|
||||
// 🔔 Client : confirmation annulation
|
||||
push_rdv_to_client(
|
||||
$pdo,
|
||||
(int) $beforeRow['client_id'],
|
||||
$translations['push_rdv_cancel_client_title'] ?? 'Запись отменена',
|
||||
$translations['push_rdv_cancel_client_body'] ?? 'Ваша запись была успешно отменена.',
|
||||
'/public/client/mes_rendez_vous.php',
|
||||
[
|
||||
'rdv_id' => (int) $id,
|
||||
'action' => 'cancel',
|
||||
]
|
||||
);
|
||||
|
||||
} catch (Throwable $e) {
|
||||
error_log('push rdv cancel (client) failed: '.$e->getMessage());
|
||||
}
|
||||
|
||||
} catch (Throwable $e) {
|
||||
error_log('push rdv cancel (client) failed: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
// 2) Infos RDV pour e-mails
|
||||
$info = $pdo->prepare("
|
||||
// 2) Infos RDV pour e-mails
|
||||
$info = $pdo->prepare("
|
||||
SELECT rv.date_rdv, rv.duree_minutes, rv.uuid_ical,
|
||||
p.email AS praticien_email, p.nom AS praticien_nom, p.prenom AS praticien_prenom,
|
||||
c.prenom AS client_prenom, c.nom AS client_nom, c.email AS client_email
|
||||
@@ -313,109 +336,115 @@ try {
|
||||
JOIN client c ON rv.client_id = c.id
|
||||
WHERE rv.id = ? AND rv.client_id = ?
|
||||
");
|
||||
$info->execute([$id, $client_id]);
|
||||
$r = $info->fetch(PDO::FETCH_ASSOC);
|
||||
$info->execute([$id, $client_id]);
|
||||
$r = $info->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$r) {
|
||||
json_out(false, $translations['erreur_generique'] ?? 'Erreur.');
|
||||
}
|
||||
if (! $r) {
|
||||
json_out(false, $translations['erreur_generique'] ?? 'Erreur.');
|
||||
}
|
||||
|
||||
// 3) Prépare mails
|
||||
$tz = new DateTimeZone('Europe/Paris');
|
||||
$start = new DateTime($r['date_rdv'], $tz);
|
||||
$end = (clone $start)->modify('+'.(int)($r['duree_minutes'] ?? 60).' minutes');
|
||||
$when = $start->format('d/m/Y H:i');
|
||||
// 3) Prépare mails
|
||||
$tz = new DateTimeZone('Europe/Paris');
|
||||
$start = new DateTime($r['date_rdv'], $tz);
|
||||
$end = (clone $start)->modify('+' . (int) ($r['duree_minutes'] ?? 60) . ' minutes');
|
||||
$when = $start->format('d/m/Y H:i');
|
||||
|
||||
$pratFull = $r['praticien_prenom'].' '.$r['praticien_nom'];
|
||||
$cliFull = $r['client_prenom'].' '.$r['client_nom'];
|
||||
$pratFull = $r['praticien_prenom'] . ' ' . $r['praticien_nom'];
|
||||
$cliFull = $r['client_prenom'] . ' ' . $r['client_nom'];
|
||||
|
||||
$cta_url_prat = 'https://uslugi.fr/public/praticien/agenda.php';
|
||||
$cta_url_cli = 'https://uslugi.fr/public/client/mes_rendez_vous.php';
|
||||
$cta_label = $translations['consultez_espace_pour_details'] ?? 'Voir les détails';
|
||||
$cta_url_prat = 'https://uslugi.fr/public/praticien/agenda.php';
|
||||
$cta_url_cli = 'https://uslugi.fr/public/client/mes_rendez_vous.php';
|
||||
$cta_label = $translations['consultez_espace_pour_details'] ?? 'Voir les détails';
|
||||
|
||||
$statut_label = $translations['statut_label'] ?? 'Statut';
|
||||
$statut_value = $translations['statut_annule'] ?? 'Annulé';
|
||||
$statut_label = $translations['statut_label'] ?? 'Statut';
|
||||
$statut_value = $translations['statut_annule'] ?? 'Annulé';
|
||||
|
||||
// ---- Mail praticien
|
||||
$subject_p = $translations['email_annulation_sujet'] ?? 'Rendez-vous annulé';
|
||||
$hello_p = ($translations['email_bonjour_dr'] ?? 'Bonjour Dr').' '.$pratFull.',';
|
||||
$intro_p = ($translations['email_rdv_du'] ?? 'Le rendez-vous du').' '.$when.' '.($translations['email_avec'] ?? 'avec').' '.$cliFull.' '.($translations['email_annule_par_client'] ?? 'a été annulé par le client.');
|
||||
// ---- Mail praticien
|
||||
$subject_p = $translations['email_annulation_sujet'] ?? 'Rendez-vous annulé';
|
||||
$hello_p = ($translations['email_bonjour_dr'] ?? 'Bonjour Dr') . ' ' . $pratFull . ',';
|
||||
$intro_p = ($translations['email_rdv_du'] ?? 'Le rendez-vous du') . ' ' . $when . ' ' . ($translations['email_avec'] ?? 'avec') . ' ' . $cliFull . ' ' . ($translations['email_annule_par_client'] ?? 'a été annulé par le client.');
|
||||
|
||||
$data_p = [
|
||||
'hello_line' => $hello_p,
|
||||
'intro' => $intro_p,
|
||||
'who_label' => $translations['client_label'] ?? 'Client',
|
||||
'who_value' => $cliFull,
|
||||
'date_label' => $translations['date_label'] ?? 'Date',
|
||||
'when' => $when,
|
||||
'statut_label'=> $statut_label,
|
||||
'statut_value'=> $statut_value,
|
||||
'cta_url' => $cta_url_prat,
|
||||
'cta_label' => $cta_label,
|
||||
'footer' => $translations['merci_simple'] ?? 'Merci.',
|
||||
];
|
||||
$text_p = email_text_cancel($data_p, $translations);
|
||||
$html_p = email_html_cancel($data_p, $translations);
|
||||
$data_p = [
|
||||
'hello_line' => $hello_p,
|
||||
'intro' => $intro_p,
|
||||
'who_label' => $translations['client_label'] ?? 'Client',
|
||||
'who_value' => $cliFull,
|
||||
'date_label' => $translations['date_label'] ?? 'Date',
|
||||
'when' => $when,
|
||||
'statut_label' => $statut_label,
|
||||
'statut_value' => $statut_value,
|
||||
'cta_url' => $cta_url_prat,
|
||||
'cta_label' => $cta_label,
|
||||
'footer' => $translations['merci_simple'] ?? 'Merci.',
|
||||
];
|
||||
$text_p = email_text_cancel($data_p, $translations);
|
||||
$html_p = email_html_cancel($data_p, $translations);
|
||||
|
||||
$ics_p = null; $ics_name_p = null;
|
||||
if (!empty($r['uuid_ical'])) {
|
||||
$ics_name_p = 'rdv_cancel_'.$r['uuid_ical'].'_praticien.ics';
|
||||
$ics_p = build_ics_cancel(
|
||||
$r['uuid_ical'], $start, $end, 'Europe/Paris',
|
||||
($translations['rdv_avec'] ?? 'Rendez-vous avec').' '.$cliFull,
|
||||
'rdv@uslugi.fr',
|
||||
[
|
||||
['cn'=>$cliFull, 'email'=>$r['client_email']],
|
||||
['cn'=>$pratFull, 'email'=>$r['praticien_email']],
|
||||
]
|
||||
);
|
||||
}
|
||||
[$ok_p, $err_p] = send_email_phpmailer($r['praticien_email'], $subject_p, $text_p, $html_p, $ics_name_p, $ics_p);
|
||||
if (!$ok_p) error_log("Annulation RDV: mail praticien NON envoyé: ".$err_p);
|
||||
$ics_p = null;
|
||||
$ics_name_p = null;
|
||||
if (! empty($r['uuid_ical'])) {
|
||||
$ics_name_p = 'rdv_cancel_' . $r['uuid_ical'] . '_praticien.ics';
|
||||
$ics_p = build_ics_cancel(
|
||||
$r['uuid_ical'], $start, $end, 'Europe/Paris',
|
||||
($translations['rdv_avec'] ?? 'Rendez-vous avec') . ' ' . $cliFull,
|
||||
'rdv@uslugi.fr',
|
||||
[
|
||||
['cn' => $cliFull, 'email' => $r['client_email']],
|
||||
['cn' => $pratFull, 'email' => $r['praticien_email']],
|
||||
]
|
||||
);
|
||||
}
|
||||
[$ok_p, $err_p] = send_email_phpmailer($r['praticien_email'], $subject_p, $text_p, $html_p, $ics_name_p, $ics_p);
|
||||
if (! $ok_p) {
|
||||
error_log("Annulation RDV: mail praticien NON envoyé: " . $err_p);
|
||||
}
|
||||
|
||||
// ---- Mail client
|
||||
$subject_c = $translations['email_annulation_client_sujet'] ?? 'Confirmation d’annulation de votre rendez-vous';
|
||||
$hello_c = sprintf($translations['mail_valid_bonjour_nom'] ?? 'Bonjour %s', $cliFull);
|
||||
$intro_c = ($translations['email_votre_rdv_du'] ?? 'Votre rendez-vous du').' '.$when.' '.($translations['email_avec'] ?? 'avec').' '.(($translations['titre_dr'] ?? 'Dr').' '.$pratFull).' '.($translations['email_a_ete_annule'] ?? 'a été annulé.');
|
||||
// ---- Mail client
|
||||
$subject_c = $translations['email_annulation_client_sujet'] ?? 'Confirmation d’annulation de votre rendez-vous';
|
||||
$hello_c = sprintf($translations['mail_valid_bonjour_nom'] ?? 'Bonjour %s', $cliFull);
|
||||
$intro_c = ($translations['email_votre_rdv_du'] ?? 'Votre rendez-vous du') . ' ' . $when . ' ' . ($translations['email_avec'] ?? 'avec') . ' ' . (($translations['titre_dr'] ?? 'Dr') . ' ' . $pratFull) . ' ' . ($translations['email_a_ete_annule'] ?? 'a été annulé.');
|
||||
|
||||
$who_label_cli = $translations['praticien_label'] ?? ($translations['titre_dr'] ?? 'Dr');
|
||||
$who_value_cli = ($translations['titre_dr'] ?? 'Dr').' '.$pratFull;
|
||||
$who_label_cli = $translations['praticien_label'] ?? ($translations['titre_dr'] ?? 'Dr');
|
||||
$who_value_cli = ($translations['titre_dr'] ?? 'Dr') . ' ' . $pratFull;
|
||||
|
||||
$data_c = [
|
||||
'hello_line' => $hello_c,
|
||||
'intro' => $intro_c,
|
||||
'who_label' => $who_label_cli,
|
||||
'who_value' => $who_value_cli,
|
||||
'date_label' => $translations['date_label'] ?? 'Date',
|
||||
'when' => $when,
|
||||
'statut_label'=> $statut_label,
|
||||
'statut_value'=> $statut_value,
|
||||
'cta_url' => $cta_url_cli,
|
||||
'cta_label' => $cta_label,
|
||||
'footer' => $translations['merci_simple'] ?? 'Merci.',
|
||||
];
|
||||
$text_c = email_text_cancel($data_c, $translations);
|
||||
$html_c = email_html_cancel($data_c, $translations);
|
||||
$data_c = [
|
||||
'hello_line' => $hello_c,
|
||||
'intro' => $intro_c,
|
||||
'who_label' => $who_label_cli,
|
||||
'who_value' => $who_value_cli,
|
||||
'date_label' => $translations['date_label'] ?? 'Date',
|
||||
'when' => $when,
|
||||
'statut_label' => $statut_label,
|
||||
'statut_value' => $statut_value,
|
||||
'cta_url' => $cta_url_cli,
|
||||
'cta_label' => $cta_label,
|
||||
'footer' => $translations['merci_simple'] ?? 'Merci.',
|
||||
];
|
||||
$text_c = email_text_cancel($data_c, $translations);
|
||||
$html_c = email_html_cancel($data_c, $translations);
|
||||
|
||||
$ics_c = null; $ics_name_c = null;
|
||||
if (!empty($r['uuid_ical'])) {
|
||||
$ics_name_c = 'rdv_cancel_'.$r['uuid_ical'].'_client.ics';
|
||||
$ics_c = build_ics_cancel(
|
||||
$r['uuid_ical'], $start, $end, 'Europe/Paris',
|
||||
($translations['rdv_avec'] ?? 'Rendez-vous avec').' '.(($translations['titre_dr'] ?? 'Dr').' '.$pratFull),
|
||||
'rdv@uslugi.fr',
|
||||
[
|
||||
['cn'=>$cliFull, 'email'=>$r['client_email']],
|
||||
['cn'=>$pratFull, 'email'=>$r['praticien_email']],
|
||||
]
|
||||
);
|
||||
}
|
||||
[$ok_c, $err_c] = send_email_phpmailer($r['client_email'], $subject_c, $text_c, $html_c, $ics_name_c, $ics_c);
|
||||
if (!$ok_c) error_log("Annulation RDV: mail client NON envoyé: ".$err_c);
|
||||
$ics_c = null;
|
||||
$ics_name_c = null;
|
||||
if (! empty($r['uuid_ical'])) {
|
||||
$ics_name_c = 'rdv_cancel_' . $r['uuid_ical'] . '_client.ics';
|
||||
$ics_c = build_ics_cancel(
|
||||
$r['uuid_ical'], $start, $end, 'Europe/Paris',
|
||||
($translations['rdv_avec'] ?? 'Rendez-vous avec') . ' ' . (($translations['titre_dr'] ?? 'Dr') . ' ' . $pratFull),
|
||||
'rdv@uslugi.fr',
|
||||
[
|
||||
['cn' => $cliFull, 'email' => $r['client_email']],
|
||||
['cn' => $pratFull, 'email' => $r['praticien_email']],
|
||||
]
|
||||
);
|
||||
}
|
||||
[$ok_c, $err_c] = send_email_phpmailer($r['client_email'], $subject_c, $text_c, $html_c, $ics_name_c, $ics_c);
|
||||
if (! $ok_c) {
|
||||
error_log("Annulation RDV: mail client NON envoyé: " . $err_c);
|
||||
}
|
||||
|
||||
json_out(true, $translations['rdv_annule_ok'] ?? 'Rendez-vous annulé.');
|
||||
json_out(true, $translations['rdv_annule_ok'] ?? 'Rendez-vous annulé.');
|
||||
|
||||
} catch (Throwable $e) {
|
||||
error_log('Annulation RDV error: '.$e->getMessage());
|
||||
json_out(false, $translations['erreur_generique'] ?? 'Erreur serveur.');
|
||||
error_log('Annulation RDV error: ' . $e->getMessage());
|
||||
json_out(false, $translations['erreur_generique'] ?? 'Erreur serveur.');
|
||||
}
|
||||
+56
-39
@@ -1,36 +1,40 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../includes/init_client.php';
|
||||
require_once __DIR__ . '/../includes/init_client.php';
|
||||
|
||||
$success = $erreur = '';
|
||||
$id = $_SESSION['compte_id'];
|
||||
|
||||
$stmt = $pdo->prepare("SELECT email, mot_de_passe FROM compte WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$compte = $stmt->fetch();
|
||||
|
||||
$success = $erreur = '';
|
||||
$id = $_SESSION['compte_id'];
|
||||
if (! $compte) {
|
||||
die($translations['compte_introuvable']);
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("SELECT email FROM compte WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$compte = $stmt->fetch();
|
||||
$email_actuel = $compte['email'];
|
||||
|
||||
if (!$compte) die($translations['compte_introuvable']);
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
require_post_csrf();
|
||||
|
||||
$email_actuel = $compte['email'];
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$nouvel_email = trim($_POST['email'] ?? '');
|
||||
$mdp1 = $_POST['mot_de_passe'] ?? '';
|
||||
$mdp2 = $_POST['confirm_mot_de_passe'] ?? '';
|
||||
$mdpActuel = $_POST['mot_de_passe_actuel'] ?? '';
|
||||
$mdp1 = $_POST['mot_de_passe'] ?? '';
|
||||
$mdp2 = $_POST['confirm_mot_de_passe'] ?? '';
|
||||
|
||||
if (!filter_var($nouvel_email, FILTER_VALIDATE_EMAIL)) {
|
||||
$erreur = $translations['erreur_email_invalide_format'];
|
||||
if (! password_verify($mdpActuel, (string) ($compte['mot_de_passe'] ?? ''))) {
|
||||
$erreur = $translations['erreur_mdp_actuel_incorrect'] ?? 'Mot de passe actuel incorrect.';
|
||||
} elseif (! filter_var($nouvel_email, FILTER_VALIDATE_EMAIL)) {
|
||||
$erreur = $translations['erreur_email_non_valide'];
|
||||
} elseif ($mdp1 && ($mdp1 !== $mdp2 || strlen($mdp1) < 8)) {
|
||||
$erreur = $translations['erreur_mdp_invalide_ou_non_confirme'];
|
||||
$erreur = $translations['erreur_mdp_invalides'];
|
||||
} else {
|
||||
// Vérifie que l'email n’est pas déjà pris (sauf pour soi)
|
||||
$stmt = $pdo->prepare("SELECT id FROM compte WHERE email = ? AND id != ?");
|
||||
$stmt->execute([$nouvel_email, $id]);
|
||||
|
||||
if ($stmt->fetch()) {
|
||||
$erreur = $translations['email_deja_utilise'];
|
||||
$erreur = $translations['err_email_deja_utilise'];
|
||||
} else {
|
||||
// Mise à jour
|
||||
if ($mdp1) {
|
||||
$hash = password_hash($mdp1, PASSWORD_DEFAULT);
|
||||
$stmt = $pdo->prepare("UPDATE compte SET email = ?, mot_de_passe = ? WHERE id = ?");
|
||||
@@ -39,35 +43,48 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$stmt = $pdo->prepare("UPDATE compte SET email = ? WHERE id = ?");
|
||||
$stmt->execute([$nouvel_email, $id]);
|
||||
}
|
||||
$success = $translations['maj_effectuee_succes'];
|
||||
|
||||
$success = $translations['success_compte_mis_a_jour'];
|
||||
$email_actuel = $nouvel_email;
|
||||
|
||||
// garder la valeur en mémoire pour la suite de la page
|
||||
$compte['mot_de_passe'] = $mdp1 ? $hash : $compte['mot_de_passe'];
|
||||
}
|
||||
}
|
||||
}
|
||||
require_once '../includes/header.php';
|
||||
}
|
||||
require_once '../includes/header.php';
|
||||
?>
|
||||
|
||||
<div class="main1">
|
||||
<div class="container py-5">
|
||||
|
||||
|
||||
|
||||
<?php if ($erreur): ?>
|
||||
<div class="alert alert-danger"><?= $erreur ?></div>
|
||||
<div class="alert alert-danger"><?php echo $erreur ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($success): ?>
|
||||
<div class="alert alert-success"><?= $success ?></div>
|
||||
<div class="alert alert-success"><?php echo $success ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="POST" class="shadow p-4 rounded bg-light">
|
||||
<h2 class="text-center mb-4"> <?= $translations['mes_infos_connexion'] ?></h2>
|
||||
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars(ensure_csrf_token(), ENT_QUOTES, 'UTF-8') ?>">
|
||||
<h2 class="text-center mb-4"> <?php echo $translations['mes_infos_connexion'] ?></h2>
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><?= $translations['label_email'] ?></label>
|
||||
<input type="email" name="email" value="<?= htmlspecialchars($email_actuel) ?>" required class="form-control">
|
||||
<label class="form-label"><?php echo $translations['label_email'] ?></label>
|
||||
<input type="email" name="email" value="<?php echo htmlspecialchars($email_actuel) ?>" required class="form-control">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><?php echo htmlspecialchars($translations['label_mdp_actuel'] ?? 'Mot de passe actuel') ?></label>
|
||||
<div class="field-wrapper">
|
||||
<input type="password" name="mot_de_passe_actuel" id="mot_de_passe_actuel" class="form-control"
|
||||
placeholder="<?php echo htmlspecialchars($translations['placeholder_mdp_actuel'] ?? 'Entrez votre mot de passe actuel') ?>" required>
|
||||
<span class="toggle-password" onclick="toggle('mot_de_passe_actuel')">👁️</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><?= $translations['label_nouveau_mdp_facultatif'] ?></label>
|
||||
<label class="form-label"><?php echo $translations['label_nouveau_mdp_facultatif'] ?></label>
|
||||
<div class="field-wrapper">
|
||||
<input type="password" name="mot_de_passe" id="mot_de_passe" placeholder="<?= $translations['nouveau_mdp_placeholder'] ?>" class="form-control">
|
||||
<input type="password" name="mot_de_passe" id="mot_de_passe" placeholder="<?php echo $translations['nouveau_mdp_placeholder'] ?>" class="form-control">
|
||||
<span class="toggle-password" onclick="toggle('mot_de_passe')">👁️</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -77,18 +94,18 @@ require_once '../includes/header.php';
|
||||
<div class="strength-fill"></div>
|
||||
<div class="strength-fill"></div>
|
||||
</div>
|
||||
<div id="niveau-securite" class="mb-3 text-muted"><?= $translations['securite_indetermine'] ?></div>
|
||||
<div id="niveau-securite" class="mb-3 text-muted"><?php echo $translations['securite_indetermine'] ?></div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="form-label"><?= $translations['label_confirmer_mdp'] ?></label>
|
||||
<label class="form-label"><?php echo $translations['label_confirmer_mdp'] ?></label>
|
||||
<div class="field-wrapper">
|
||||
<input type="password" name="confirm_mot_de_passe" id="confirm_mot_de_passe" placeholder="<?= $translations['confirm_mdp_placeholder'] ?>" class="form-control">
|
||||
<input type="password" name="confirm_mot_de_passe" id="confirm_mot_de_passe" placeholder="<?php echo $translations['confirm_mdp_placeholder'] ?>" class="form-control">
|
||||
<span class="toggle-password" onclick="toggle('confirm_mot_de_passe')">👁️</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-center">
|
||||
<button type="submit" class="btn btn-primary"> <?= $translations['btn_mettre_a_jour'] ?></button>
|
||||
<button type="submit" class="btn btn-primary"> <?php echo $translations['btn_mettre_a_jour'] ?></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -104,7 +121,7 @@ document.getElementById('mot_de_passe').addEventListener('input', function () {
|
||||
const niveauText = document.getElementById("niveau-securite");
|
||||
|
||||
segments.forEach(s => s.style.backgroundColor = '#e0e0e0');
|
||||
niveauText.textContent = <?= json_encode($translations['securite_indetermine']) ?>;
|
||||
niveauText.textContent = <?php echo json_encode($translations['securite_indetermine']) ?>;
|
||||
niveauText.className = 'text-muted';
|
||||
|
||||
const longOk = val.length >= 8;
|
||||
@@ -120,16 +137,16 @@ document.getElementById('mot_de_passe').addEventListener('input', function () {
|
||||
|
||||
if (force === 1) {
|
||||
segments[0].style.backgroundColor = 'red';
|
||||
niveauText.textContent = <?= json_encode($translations['securite_faible']) ?>;
|
||||
niveauText.textContent = <?php echo json_encode($translations['securite_faible']) ?>;
|
||||
niveauText.className = 'text-danger';
|
||||
} else if (force === 2) {
|
||||
segments[0].style.backgroundColor = 'orange';
|
||||
segments[1].style.backgroundColor = 'orange';
|
||||
niveauText.textContent = <?= json_encode($translations['securite_moyen']) ?>;
|
||||
niveauText.textContent = <?php echo json_encode($translations['securite_moyen']) ?>;
|
||||
niveauText.className = 'text-warning';
|
||||
} else if (force >= 3) {
|
||||
segments.forEach(s => s.style.backgroundColor = 'green');
|
||||
niveauText.textContent = <?= json_encode($translations['securite_fort']) ?>;
|
||||
niveauText.textContent = <?php echo json_encode($translations['securite_fort']) ?>;
|
||||
niveauText.className = 'text-success';
|
||||
}
|
||||
});
|
||||
@@ -137,7 +154,7 @@ document.getElementById('mot_de_passe').addEventListener('input', function () {
|
||||
</div>
|
||||
<footer class="site-footer">
|
||||
<div class="container">
|
||||
<p><?= $translations['copyr'] ?></p>
|
||||
<p><?php echo $translations['copyr'] ?></p>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
|
||||
+159
-115
@@ -1,60 +1,102 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../includes/init_client.php';
|
||||
require_once __DIR__ . '/../includes/init_client.php';
|
||||
|
||||
$praticien_id = (int) ($_GET['id'] ?? 0);
|
||||
if ($praticien_id <= 0) {echo "<div class='container py-5'>ID invalide.</div>";exit;}
|
||||
|
||||
$currentLang = $lang ?? 'ru';
|
||||
if (! in_array($currentLang, ['ru', 'fr', 'en'], true)) {
|
||||
$currentLang = 'ru';
|
||||
}
|
||||
|
||||
$praticien_id = (int)($_GET['id'] ?? 0);
|
||||
if ($praticien_id <= 0) { echo "<div class='container py-5'>ID invalide.</div>"; exit; }
|
||||
function h($s)
|
||||
{return htmlspecialchars((string) $s, ENT_QUOTES, 'UTF-8');}
|
||||
function percent_from_5($note)
|
||||
{$n = max(0, min(5, (float) $note));return (int) round($n / 5 * 100);}
|
||||
function pseudo_from($prenom, $nom)
|
||||
{
|
||||
$p = trim((string) $prenom);
|
||||
$n = trim((string) $nom);
|
||||
$p = $p !== '' ? mb_convert_case($p, MB_CASE_TITLE, 'UTF-8') : 'Client';
|
||||
$i = $n !== '' ? mb_strtoupper(mb_substr($nom, 0, 1, 'UTF-8'), 'UTF-8') : '';
|
||||
return $i ? ($p . '.' . $i) : $p;
|
||||
}
|
||||
function video_thumb_info(?string $url): ?array
|
||||
{
|
||||
if (! $url || ! trim($url)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function h($s){ return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); }
|
||||
function percent_from_5($note){ $n = max(0,min(5,(float)$note)); return (int)round($n/5*100); }
|
||||
function pseudo_from($prenom,$nom){
|
||||
$p = trim((string)$prenom); $n = trim((string)$nom);
|
||||
$p = $p!=='' ? mb_convert_case($p, MB_CASE_TITLE, 'UTF-8') : 'Client';
|
||||
$i = $n!=='' ? mb_strtoupper(mb_substr($nom,0,1,'UTF-8'),'UTF-8') : '';
|
||||
return $i ? ($p.'.'.$i) : $p;
|
||||
}
|
||||
function video_thumb_info(?string $url): ?array {
|
||||
if (!$url || !trim($url)) return null; $u = trim($url);
|
||||
if (preg_match('~(?:youtube\.com/(?:watch\?v=|embed/|shorts/)|youtu\.be/)([A-Za-z0-9_-]{6,})~i',$u,$m)){
|
||||
$id=$m[1]; return ['provider'=>'youtube','id'=>$id,'thumb'=>"https://i.ytimg.com/vi/$id/hqdefault.jpg"];
|
||||
}
|
||||
if (preg_match('~dailymotion\.com/video/([A-Za-z0-9]+)|dai\.ly/([A-Za-z0-9]+)~i',$u,$m)){
|
||||
$id=$m[1] ?: $m[2]; return ['provider'=>'dailymotion','id'=>$id,'thumb'=>"https://www.dailymotion.com/thumbnail/video/$id"];
|
||||
}
|
||||
$path=parse_url($u,PHP_URL_PATH)??''; if(preg_match('~\.(mp4|webm|ogg)$~i',$path)){ return ['provider'=>'file','id'=>null,'thumb'=>null]; }
|
||||
if (stripos($u,'vimeo.com')!==false){ return ['provider'=>'vimeo','id'=>null,'thumb'=>null]; }
|
||||
return ['provider'=>'other','id'=>null,'thumb'=>null];
|
||||
}
|
||||
$u = trim($url);
|
||||
if (preg_match('~(?:youtube\.com/(?:watch\?v=|embed/|shorts/)|youtu\.be/)([A-Za-z0-9_-]{6,})~i', $u, $m)) {
|
||||
$id = $m[1];return ['provider' => 'youtube', 'id' => $id, 'thumb' => "https://i.ytimg.com/vi/$id/hqdefault.jpg"];
|
||||
}
|
||||
if (preg_match('~dailymotion\.com/video/([A-Za-z0-9]+)|dai\.ly/([A-Za-z0-9]+)~i', $u, $m)) {
|
||||
$id = $m[1] ?: $m[2];return ['provider' => 'dailymotion', 'id' => $id, 'thumb' => "https://www.dailymotion.com/thumbnail/video/$id"];
|
||||
}
|
||||
$path = parse_url($u, PHP_URL_PATH) ?? '';if (preg_match('~\.(mp4|webm|ogg)$~i', $path)) {return ['provider' => 'file', 'id' => null, 'thumb' => null];}
|
||||
if (stripos($u, 'vimeo.com') !== false) {return ['provider' => 'vimeo', 'id' => null, 'thumb' => null];}
|
||||
return ['provider' => 'other', 'id' => null, 'thumb' => null];
|
||||
}
|
||||
|
||||
// 1) Praticien
|
||||
$st = $pdo->prepare("SELECT id, titre, nom, prenom, telephone, adresse, ville, code_postal, specialite, presentation, photo, video, disponibilite
|
||||
FROM praticien WHERE id = ?");
|
||||
$st->execute([$praticien_id]);
|
||||
$p = $st->fetch(PDO::FETCH_ASSOC);
|
||||
if (!$p) { echo "<div class='container py-5'>".$translations['praticien_introuvable']."</div>"; exit; }
|
||||
// 1) Praticien
|
||||
$st = $pdo->prepare("
|
||||
SELECT
|
||||
p.id,
|
||||
p.titre,
|
||||
p.nom,
|
||||
p.prenom,
|
||||
p.telephone,
|
||||
p.adresse,
|
||||
p.ville,
|
||||
p.code_postal,
|
||||
p.specialite,
|
||||
p.presentation,
|
||||
p.photo,
|
||||
p.video,
|
||||
p.disponibilite,
|
||||
COALESCE(pt.nom_affiche, p.nom) AS nom_affiche,
|
||||
COALESCE(pt.prenom_affiche, p.prenom) AS prenom_affiche,
|
||||
COALESCE(pt.ville_affichee, p.ville) AS ville_affichee,
|
||||
COALESCE(pt.titre, p.titre) AS titre_affiche,
|
||||
COALESCE(pt.specialite, p.specialite) AS specialite_affichee,
|
||||
COALESCE(pt.presentation, p.presentation) AS presentation_affichee,
|
||||
COALESCE(pt.disponibilite, p.disponibilite) AS disponibilite_affichee
|
||||
FROM praticien p
|
||||
LEFT JOIN praticien_traduction pt
|
||||
ON pt.praticien_id = p.id
|
||||
AND pt.langue = :lang
|
||||
WHERE p.id = :id
|
||||
LIMIT 1
|
||||
");
|
||||
$st->execute([
|
||||
':lang' => $currentLang,
|
||||
':id' => $praticien_id,
|
||||
]);
|
||||
$p = $st->fetch(PDO::FETCH_ASSOC);
|
||||
if (! $p) {echo "<div class='container py-5'>" . $translations['praticien_introuvable'] . "</div>";exit;}
|
||||
|
||||
$name = $name = trim($p['prenom'] . ' ' . $p['nom']);
|
||||
$photo = !empty($p['photo']) ? "/public/uploads/".ltrim($p['photo'],'/') : "/public/uploads/img/user_placeholder.png";
|
||||
$name = trim(($p['prenom_affiche'] ?? $p['prenom'] ?? '') . ' ' . ($p['nom_affiche'] ?? $p['nom'] ?? ''));
|
||||
$photo = ! empty($p['photo']) ? "/public/uploads/" . ltrim($p['photo'], '/') : "/public/uploads/img/user_placeholder.png";
|
||||
|
||||
// 2) Vidéos additionnelles
|
||||
$vs = $pdo->prepare("SELECT id, url, title, ordre FROM praticien_video WHERE praticien_id=? ORDER BY ordre ASC, id DESC");
|
||||
$vs->execute([$praticien_id]);
|
||||
$videos = $vs->fetchAll(PDO::FETCH_ASSOC);
|
||||
// 2) Vidéos additionnelles
|
||||
$vs = $pdo->prepare("SELECT id, url, title, ordre FROM praticien_video WHERE praticien_id=? ORDER BY ordre ASC, id DESC");
|
||||
$vs->execute([$praticien_id]);
|
||||
$videos = $vs->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// 3) Avis: moyenne + total + dernier
|
||||
$avg = $pdo->prepare("SELECT ROUND(AVG(note),1) AS m, COUNT(*) AS c FROM avis_praticien WHERE praticien_id=? AND statut='publie'");
|
||||
$avg->execute([$praticien_id]);
|
||||
$stats = $avg->fetch(PDO::FETCH_ASSOC) ?: ['m'=>null,'c'=>0];
|
||||
// 3) Avis: moyenne + total + dernier
|
||||
$avg = $pdo->prepare("SELECT ROUND(AVG(note),1) AS m, COUNT(*) AS c FROM avis_praticien WHERE praticien_id=? AND statut='publie'");
|
||||
$avg->execute([$praticien_id]);
|
||||
$stats = $avg->fetch(PDO::FETCH_ASSOC) ?: ['m' => null, 'c' => 0];
|
||||
|
||||
$last = $pdo->prepare("SELECT a.note,a.titre,a.commentaire,a.visit_date,a.created_at,c.prenom,c.nom
|
||||
$last = $pdo->prepare("SELECT a.note,a.titre,a.commentaire,a.visit_date,a.created_at,c.prenom,c.nom
|
||||
FROM avis_praticien a
|
||||
LEFT JOIN client c ON c.id=a.client_id
|
||||
WHERE a.praticien_id=? AND a.statut='publie'
|
||||
ORDER BY a.created_at DESC LIMIT 1");
|
||||
$last->execute([$praticien_id]);
|
||||
$lrow = $last->fetch(PDO::FETCH_ASSOC);
|
||||
require_once __DIR__.'/../includes/header.php'; // inclut lang + styles
|
||||
$last->execute([$praticien_id]);
|
||||
$lrow = $last->fetch(PDO::FETCH_ASSOC);
|
||||
require_once __DIR__ . '/../includes/header.php'; // inclut lang + styles
|
||||
?>
|
||||
<div class="main1">
|
||||
<div class="container py-4">
|
||||
@@ -63,75 +105,75 @@ require_once __DIR__.'/../includes/header.php'; // inclut lang + styles
|
||||
<!-- Cadre 1 : Infos -->
|
||||
<section class="panel">
|
||||
<div class="info-scroll">
|
||||
<h2 class="pro-name"><?= h($name) ?></h2>
|
||||
<?php if($p['specialite']): ?><div><strong><?= h($translations['dp_specialite']) ?>:</strong> <?= h($p['specialite']) ?></div><?php endif; ?>
|
||||
<!--<?php if($p['telephone']): ?><div><strong><?= h($translations['dp_telephone']) ?>:</strong> <?= h($p['telephone']) ?></div><?php endif; ?>-->
|
||||
<?php if($p['titre']): ?><div><strong><?= h($translations['tarif_consultation']) ?>:</strong> <?= h($p['titre']) ?></div><?php endif; ?>
|
||||
<?php if($p['disponibilite']): ?><div><strong><?= h($translations['moyens_payement']) ?>:</strong> <?= h($p['disponibilite']) ?></div><?php endif; ?>
|
||||
<?php if($p['adresse']): ?><div><strong><?= h($translations['dp_adresse']) ?>:</strong> <?= nl2br(h($p['adresse'])) ?></div><?php endif; ?>
|
||||
<?php if($p['ville'] || $p['code_postal']): ?><div><strong><?= h($translations['dp_ville_cp']) ?>:</strong> <?= h(trim(($p['ville']??'').' '.($p['code_postal']??''))) ?></div><?php endif; ?>
|
||||
<?php if($p['presentation']): ?><div class="mt-3"><strong><?= h($translations['dp_description']) ?>:</strong><br><?= nl2br(h($p['presentation'])) ?></div><?php endif; ?>
|
||||
<h2 class="pro-name"><?php echo h($name) ?></h2>
|
||||
<?php if ($p['specialite_affichee']): ?><div><strong><?php echo h($translations['dp_specialite']) ?>:</strong> <?php echo h($p['specialite_affichee']) ?></div><?php endif; ?>
|
||||
<!--<?php if ($p['telephone']): ?><div><strong><?php echo h($translations['dp_telephone']) ?>:</strong> <?php echo h($p['telephone']) ?></div><?php endif; ?>-->
|
||||
<?php if ($p['titre_affiche']): ?><div><strong><?php echo h($translations['tarif_consultation']) ?>:</strong> <?php echo h($p['titre_affiche']) ?></div><?php endif; ?>
|
||||
<?php if ($p['disponibilite_affichee']): ?><div><strong><?php echo h($translations['moyens_payement']) ?>:</strong> <?php echo h($p['disponibilite_affichee']) ?></div><?php endif; ?>
|
||||
<?php if ($p['adresse']): ?><div><strong><?php echo h($translations['dp_adresse']) ?>:</strong> <?php echo nl2br(h($p['adresse'])) ?></div><?php endif; ?>
|
||||
<?php if ($p['ville_affichee'] || $p['code_postal']): ?><div><strong><?php echo h($translations['dp_ville_cp']) ?>:</strong> <?php echo h(trim(($p['ville_affichee'] ?? '') . ' ' . ($p['code_postal'] ?? ''))) ?></div><?php endif; ?>
|
||||
<?php if ($p['presentation_affichee']): ?><div class="mt-3"><strong><?php echo h($translations['dp_description']) ?>:</strong><br><?php echo nl2br(h($p['presentation_affichee'])) ?></div><?php endif; ?>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Centre : grande photo -->
|
||||
<section class="big-photo">
|
||||
<img class="img-fluid w-100" src="<?= h($photo) ?>" alt="Photo">
|
||||
<img class="img-fluid w-100" src="<?php echo h($photo) ?>" alt="Photo">
|
||||
</section>
|
||||
|
||||
<!-- Cadre 2 : Vidéos -->
|
||||
<?php if($p['video'] != ''){ ?>
|
||||
<?php if ($p['video'] != '') {?>
|
||||
<aside class="panel">
|
||||
<h2 class="pro-name"><?= h($translations['dp_videos']) ?></h2>
|
||||
<h2 class="pro-name"><?php echo h($translations['dp_videos']) ?></h2>
|
||||
<div class="vlist">
|
||||
<?php
|
||||
$alt = h($translations['video_alt'] ?? 'Vidéo');
|
||||
$printed = false;
|
||||
$alt = h($translations['video_alt'] ?? 'Vidéo');
|
||||
$printed = false;
|
||||
|
||||
// Petite fonction d'affichage d’une vignette
|
||||
$printThumb = function($provider, $id, $thumb, $rawUrl) use ($alt){
|
||||
if (in_array($provider, ['youtube','dailymotion']) && $id){
|
||||
echo '<a href="#" class="vitem d-block pro-video"
|
||||
// Petite fonction d'affichage d’une vignette
|
||||
$printThumb = function ($provider, $id, $thumb, $rawUrl) use ($alt) {
|
||||
if (in_array($provider, ['youtube', 'dailymotion']) && $id) {
|
||||
echo '<a href="#" class="vitem d-block pro-video"
|
||||
data-bs-toggle="modal" data-bs-target="#videoModal"
|
||||
data-provider="'.h($provider).'" data-id="'.h($id).'">';
|
||||
echo $thumb
|
||||
? '<img src="'.h($thumb).'" alt="'.$alt.'" loading="lazy">'
|
||||
: '<img src="/public/uploads/img/video_placeholder.jpg" alt="'.$alt.'" loading="lazy">';
|
||||
echo '</a>';
|
||||
} else {
|
||||
$safe = h($rawUrl);
|
||||
echo '<a class="vitem d-block" href="'.$safe.'" target="_blank" rel="noopener">';
|
||||
echo $thumb
|
||||
? '<img src="'.h($thumb).'" alt="'.$alt.'" loading="lazy">'
|
||||
: '<img src="/public/uploads/img/video_placeholder.jpg" alt="'.$alt.'" loading="lazy">';
|
||||
echo '</a>';
|
||||
}
|
||||
};
|
||||
data-provider="' . h($provider) . '" data-id="' . h($id) . '">';
|
||||
echo $thumb
|
||||
? '<img src="' . h($thumb) . '" alt="' . $alt . '" loading="lazy">'
|
||||
: '<img src="/public/uploads/img/video_placeholder.jpg" alt="' . $alt . '" loading="lazy">';
|
||||
echo '</a>';
|
||||
} else {
|
||||
$safe = h($rawUrl);
|
||||
echo '<a class="vitem d-block" href="' . $safe . '" target="_blank" rel="noopener">';
|
||||
echo $thumb
|
||||
? '<img src="' . h($thumb) . '" alt="' . $alt . '" loading="lazy">'
|
||||
: '<img src="/public/uploads/img/video_placeholder.jpg" alt="' . $alt . '" loading="lazy">';
|
||||
echo '</a>';
|
||||
}
|
||||
};
|
||||
|
||||
if (!empty($p['video'])) {
|
||||
$vi = video_thumb_info($p['video']);
|
||||
$thumb = $vi['thumb'] ?? null;
|
||||
$prov = $vi['provider'] ?? null;
|
||||
$vid = $vi['id'] ?? null;
|
||||
$printThumb($prov, $vid, $thumb, $p['video']);
|
||||
$printed = true;
|
||||
}
|
||||
foreach ($videos as $v) {
|
||||
$vi = video_thumb_info($v['url']);
|
||||
$thumb = $vi['thumb'] ?? null;
|
||||
$prov = $vi['provider'] ?? null;
|
||||
$vid = $vi['id'] ?? null;
|
||||
$printThumb($prov, $vid, $thumb, $v['url']);
|
||||
$printed = true;
|
||||
}
|
||||
if (!$printed) {
|
||||
echo '<div class="vitem"><img src="/public/uploads/img/video_placeholder.jpg" alt="'.$alt.'"></div>';
|
||||
//echo '<div class="text-muted small mt-1">'.h($translations['dp_aucune_video']).'</div>';
|
||||
}
|
||||
?>
|
||||
if (! empty($p['video'])) {
|
||||
$vi = video_thumb_info($p['video']);
|
||||
$thumb = $vi['thumb'] ?? null;
|
||||
$prov = $vi['provider'] ?? null;
|
||||
$vid = $vi['id'] ?? null;
|
||||
$printThumb($prov, $vid, $thumb, $p['video']);
|
||||
$printed = true;
|
||||
}
|
||||
foreach ($videos as $v) {
|
||||
$vi = video_thumb_info($v['url']);
|
||||
$thumb = $vi['thumb'] ?? null;
|
||||
$prov = $vi['provider'] ?? null;
|
||||
$vid = $vi['id'] ?? null;
|
||||
$printThumb($prov, $vid, $thumb, $v['url']);
|
||||
$printed = true;
|
||||
}
|
||||
if (! $printed) {
|
||||
echo '<div class="vitem"><img src="/public/uploads/img/video_placeholder.jpg" alt="' . $alt . '"></div>';
|
||||
//echo '<div class="text-muted small mt-1">'.h($translations['dp_aucune_video']).'</div>';
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
</aside>
|
||||
<?php } ?>
|
||||
<?php }?>
|
||||
</div>
|
||||
|
||||
<!-- Ligne du bas : Note / Dernier avis / Bouton RDV -->
|
||||
@@ -139,14 +181,16 @@ require_once __DIR__.'/../includes/header.php'; // inclut lang + styles
|
||||
<!-- Cadre 3 : Note -->
|
||||
<div class="card shadow-sm p-3">
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<?php $m=(float)($stats['m'] ?? 0); $c=(int)($stats['c'] ?? 0); $w=percent_from_5($m); ?>
|
||||
<?php $m = (float) ($stats['m'] ?? 0);
|
||||
$c = (int) ($stats['c'] ?? 0);
|
||||
$w = percent_from_5($m); ?>
|
||||
<div class="rating" aria-label="Note moyenne">
|
||||
<div class="stars-base">★★★★★</div>
|
||||
<div class="stars-fill" style="width:<?= $w ?>%">★★★★★</div>
|
||||
<div class="stars-fill" style="width:<?php echo $w ?>%">★★★★★</div>
|
||||
</div>
|
||||
<div>
|
||||
<div><strong><?= h($translations['avis_note_moy']) ?>:</strong> <?= $m ? number_format($m,1,',',' ') : '—' ?>/5</div>
|
||||
<div class="text-muted">(<?= $c.' '.h($translations['avis_total']) ?>)</div>
|
||||
<div><strong><?php echo h($translations['avis_note_moy']) ?>:</strong> <?php echo $m ? number_format($m, 1, ',', ' ') : '—' ?>/5</div>
|
||||
<div class="text-muted">(<?php echo $c . ' ' . h($translations['avis_total']) ?>)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -154,31 +198,31 @@ require_once __DIR__.'/../includes/header.php'; // inclut lang + styles
|
||||
<!-- Cadre 4 : Dernier avis -->
|
||||
<div class="card shadow-sm p-3 avis-card">
|
||||
<div class="d-flex justify-content-between align-items-start mb-2">
|
||||
<strong><?= h($translations['avis_dernier']) ?></strong>
|
||||
<a class="btn btn-link p-0" href="/public/client/avis.php?praticien_id=<?= (int)$praticien_id ?>">
|
||||
<?= h($translations['avis_lire_tous']) ?>
|
||||
<strong><?php echo h($translations['avis_dernier']) ?></strong>
|
||||
<a class="btn btn-link p-0" href="/public/client/avis.php?praticien_id=<?php echo (int) $praticien_id ?>">
|
||||
<?php echo h($translations['avis_lire_tous']) ?>
|
||||
</a>
|
||||
</div>
|
||||
<?php if ($lrow): ?>
|
||||
<?php $lw=percent_from_5((float)$lrow['note']); ?>
|
||||
<?php $lw = percent_from_5((float) $lrow['note']); ?>
|
||||
<div class="rating mb-1">
|
||||
<div class="stars-base">★★★★★</div>
|
||||
<div class="stars-fill" style="width:<?= $lw ?>%">★★★★★</div>
|
||||
<div class="stars-fill" style="width:<?php echo $lw ?>%">★★★★★</div>
|
||||
</div>
|
||||
<div class="meta">
|
||||
<?= h(pseudo_from($lrow['prenom'] ?? '', $lrow['nom'] ?? '')) ?>
|
||||
<?php if(!empty($lrow['visit_date'])): ?> • <?= date('d/m/Y', strtotime($lrow['visit_date'])) ?><?php endif; ?>
|
||||
<?php echo h(pseudo_from($lrow['prenom'] ?? '', $lrow['nom'] ?? '')) ?>
|
||||
<?php if (! empty($lrow['visit_date'])): ?> • <?php echo date('d/m/Y', strtotime($lrow['visit_date'])) ?><?php endif; ?>
|
||||
</div>
|
||||
<div><?= nl2br(h(mb_strimwidth((string)$lrow['commentaire'],0,320,'…','UTF-8'))) ?></div>
|
||||
<div><?php echo nl2br(h(mb_strimwidth((string) $lrow['commentaire'], 0, 320, '…', 'UTF-8'))) ?></div>
|
||||
<?php else: ?>
|
||||
<div class="text-muted"><?= h($translations['avis_aucun']) ?></div>
|
||||
<div class="text-muted"><?php echo h($translations['avis_aucun']) ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- Bouton RDV -->
|
||||
<div class="text-end">
|
||||
<a class="btn btn-danger btn-lg" href="/public/client/reserver_rdv.php?praticien_id=<?= (int)$praticien_id ?>">
|
||||
<?= h($translations['dp_btn_rdv']) ?>
|
||||
<a class="btn btn-danger btn-lg" href="/public/client/reserver_rdv.php?praticien_id=<?php echo (int) $praticien_id ?>">
|
||||
<?php echo h($translations['dp_btn_rdv']) ?>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -191,8 +235,8 @@ require_once __DIR__.'/../includes/header.php'; // inclut lang + styles
|
||||
<div class="modal-dialog modal-dialog-centered modal-xl">
|
||||
<div class="modal-content border-0 shadow-lg rounded-4">
|
||||
<div class="modal-header border-0">
|
||||
<h5 class="modal-title" id="videoModalLabel"><?= h($translations['video_titre'] ?? 'Vidéo') ?></h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="<?= h($translations['fermer'] ?? 'Fermer') ?>"></button>
|
||||
<h5 class="modal-title" id="videoModalLabel"><?php echo h($translations['video_titre'] ?? 'Vidéo') ?></h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="<?php echo h($translations['fermer'] ?? 'Fermer') ?>"></button>
|
||||
</div>
|
||||
<div class="modal-body pt-0">
|
||||
<div class="ratio ratio-16x9 rounded-3 overflow-hidden bg-black">
|
||||
@@ -204,7 +248,7 @@ require_once __DIR__.'/../includes/header.php'; // inclut lang + styles
|
||||
</div>
|
||||
<div class="modal-footer border-0">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||
<?= h($translations['fermer'] ?? 'Fermer') ?>
|
||||
<?php echo h($translations['fermer'] ?? 'Fermer') ?>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -241,7 +285,7 @@ require_once __DIR__.'/../includes/header.php'; // inclut lang + styles
|
||||
|
||||
<footer class="site-footer">
|
||||
<div class="container">
|
||||
<p><?= $translations['copyr'] ?></p>
|
||||
<p><?php echo $translations['copyr'] ?></p>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
|
||||
+303
-218
@@ -1,218 +1,271 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../includes/init_client.php';
|
||||
require_once __DIR__ . '/../includes/init_client.php';
|
||||
|
||||
// Support recherche ville en russe + latin
|
||||
// Essaie d'abord /public/includes/, sinon /includes/ (selon ton projet)
|
||||
$__paths = [
|
||||
__DIR__ . '/../includes/search_helpers.php',
|
||||
dirname(__DIR__, 2) . '/includes/search_helpers.php',
|
||||
];
|
||||
foreach ($__paths as $__p) { if (is_file($__p)) { require_once $__p; break; } }
|
||||
unset($__paths, $__p);
|
||||
// Support recherche ville en russe + latin
|
||||
// Essaie d'abord /public/includes/, sinon /includes/ (selon ton projet)
|
||||
$__paths = [
|
||||
__DIR__ . '/../includes/search_helpers.php',
|
||||
dirname(__DIR__, 2) . '/includes/search_helpers.php',
|
||||
];
|
||||
foreach ($__paths as $__p) {if (is_file($__p)) {require_once $__p;
|
||||
break;}}
|
||||
unset($__paths, $__p);
|
||||
|
||||
/* =========================
|
||||
/* =========================
|
||||
Paramètres / Recherche
|
||||
========================= */
|
||||
$perPage = 12;
|
||||
$page = max(1, (int)($_GET['page'] ?? 1));
|
||||
$qRaw = trim((string)($_GET['q'] ?? ''));
|
||||
$wRaw = trim((string)($_GET['where'] ?? ''));
|
||||
$q = mb_strtolower($qRaw);
|
||||
$w = mb_strtolower($wRaw);
|
||||
$isSearch = ($q !== '' || $w !== '');
|
||||
$perPage = 12;
|
||||
$page = max(1, (int) ($_GET['page'] ?? 1));
|
||||
$qRaw = trim((string) ($_GET['q'] ?? ''));
|
||||
$wRaw = trim((string) ($_GET['where'] ?? ''));
|
||||
$q = mb_strtolower($qRaw);
|
||||
$w = mb_strtolower($wRaw);
|
||||
$isSearch = ($q !== '' || $w !== '');
|
||||
$currentLang = $lang ?? 'ru';
|
||||
if (! in_array($currentLang, ['ru', 'fr', 'en'], true)) {
|
||||
$currentLang = 'ru';
|
||||
}
|
||||
|
||||
/* =========================
|
||||
/* =========================
|
||||
Helpers
|
||||
========================= */
|
||||
function video_thumb_info(?string $url): ?array {
|
||||
if (!$url || !trim($url)) return null;
|
||||
$u = trim($url);
|
||||
function video_thumb_info(?string $url): ?array
|
||||
{
|
||||
if (! $url || ! trim($url)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// YouTube
|
||||
if (preg_match('~(?:youtube\\.com/(?:watch\\?v=|embed/|shorts/)|youtu\\.be/)([A-Za-z0-9_-]{6,})~i', $u, $m)) {
|
||||
$id = $m[1];
|
||||
return ['provider'=>'youtube','id'=>$id, 'thumb'=>"https://i.ytimg.com/vi/$id/hqdefault.jpg"];
|
||||
}
|
||||
// Dailymotion
|
||||
if (preg_match('~dailymotion\\.com/video/([A-Za-z0-9]+)|dai\\.ly/([A-Za-z0-9]+)~i', $u, $m)) {
|
||||
$id = $m[1] ?: $m[2];
|
||||
return ['provider'=>'dailymotion','id'=>$id,'thumb'=>"https://www.dailymotion.com/thumbnail/video/$id"];
|
||||
}
|
||||
// Fichiers directs
|
||||
$path = parse_url($u, PHP_URL_PATH) ?? '';
|
||||
if (preg_match('~\\.(mp4|webm|ogg)$~i', $path)) return ['provider'=>'file','id'=>null,'thumb'=>null];
|
||||
if (strpos(strtolower($u), 'vimeo.com') !== false) return ['provider'=>'vimeo','id'=>null,'thumb'=>null];
|
||||
return ['provider'=>'other','id'=>null,'thumb'=>null];
|
||||
}
|
||||
$u = trim($url);
|
||||
|
||||
function fullName(array $p): string {
|
||||
return trim(($p['prenom'] ?? '').' '.($p['nom'] ?? ''));
|
||||
}
|
||||
// YouTube
|
||||
if (preg_match('~(?:youtube\\.com/(?:watch\\?v=|embed/|shorts/)|youtu\\.be/)([A-Za-z0-9_-]{6,})~i', $u, $m)) {
|
||||
$id = $m[1];
|
||||
return ['provider' => 'youtube', 'id' => $id, 'thumb' => "https://i.ytimg.com/vi/$id/hqdefault.jpg"];
|
||||
}
|
||||
// Dailymotion
|
||||
if (preg_match('~dailymotion\\.com/video/([A-Za-z0-9]+)|dai\\.ly/([A-Za-z0-9]+)~i', $u, $m)) {
|
||||
$id = $m[1] ?: $m[2];
|
||||
return ['provider' => 'dailymotion', 'id' => $id, 'thumb' => "https://www.dailymotion.com/thumbnail/video/$id"];
|
||||
}
|
||||
// Fichiers directs
|
||||
$path = parse_url($u, PHP_URL_PATH) ?? '';
|
||||
if (preg_match('~\\.(mp4|webm|ogg)$~i', $path)) {
|
||||
return ['provider' => 'file', 'id' => null, 'thumb' => null];
|
||||
}
|
||||
|
||||
function excerpt_words(string $html, int $words = 60): string {
|
||||
$plain = trim(preg_replace('/\\s+/', ' ', strip_tags($html)));
|
||||
if ($plain === '') return '';
|
||||
$parts = preg_split('/\\s+/', $plain);
|
||||
if (count($parts) <= $words) return $plain;
|
||||
return implode(' ', array_slice($parts, 0, $words)).'…';
|
||||
}
|
||||
if (strpos(strtolower($u), 'vimeo.com') !== false) {
|
||||
return ['provider' => 'vimeo', 'id' => null, 'thumb' => null];
|
||||
}
|
||||
|
||||
/* =========================
|
||||
return ['provider' => 'other', 'id' => null, 'thumb' => null];
|
||||
}
|
||||
|
||||
function fullName(array $p): string
|
||||
{
|
||||
return trim(($p['prenom_affiche'] ?? $p['prenom'] ?? '') . ' ' . ($p['nom_affiche'] ?? $p['nom'] ?? ''));
|
||||
}
|
||||
|
||||
function excerpt_words(string $html, int $words = 60): string
|
||||
{
|
||||
$plain = trim(preg_replace('/\\s+/', ' ', strip_tags($html)));
|
||||
if ($plain === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$parts = preg_split('/\\s+/', $plain);
|
||||
if (count($parts) <= $words) {
|
||||
return $plain;
|
||||
}
|
||||
|
||||
return implode(' ', array_slice($parts, 0, $words)) . '…';
|
||||
}
|
||||
|
||||
/* =========================
|
||||
Requêtes (COUNT + LIST)
|
||||
========================= */
|
||||
$whereSql = [];
|
||||
$params = [];
|
||||
$whereSql = [];
|
||||
$params = [];
|
||||
|
||||
/*==========================BLOCK WHERE AVEC ABONNEMENT VALIDE ==========================*/
|
||||
$whereSql[] = "(date_expiration IS NOT NULL AND date_expiration >= DATE_SUB(NOW(), INTERVAL 14 DAY))";
|
||||
/*=======================================================================================*/
|
||||
/*==========================BLOCK WHERE AVEC ABONNEMENT VALIDE ==========================*/
|
||||
$whereSql[] = "(p.date_expiration IS NOT NULL AND p.date_expiration >= DATE_SUB(NOW(), INTERVAL 14 DAY))";
|
||||
/*=======================================================================================*/
|
||||
|
||||
if ($q !== '') {
|
||||
$words = preg_split('/\s+/', $q, -1, PREG_SPLIT_NO_EMPTY);
|
||||
if ($q !== '') {
|
||||
$words = preg_split('/\s+/', $q, -1, PREG_SPLIT_NO_EMPTY);
|
||||
|
||||
$and = [];
|
||||
foreach ($words as $i => $word) {
|
||||
$k1 = ":q_name_$i";
|
||||
$k2 = ":q_spec_$i";
|
||||
$and = [];
|
||||
foreach ($words as $i => $word) {
|
||||
$k1 = ":q_name_$i";
|
||||
$k2 = ":q_spec_$i";
|
||||
|
||||
$and[] = "(
|
||||
LOWER(CONCAT(COALESCE(prenom,''),' ',COALESCE(nom,''))) LIKE $k1
|
||||
OR LOWER(COALESCE(specialite,'')) LIKE $k2
|
||||
)";
|
||||
$and[] = "(
|
||||
LOWER(CONCAT(COALESCE(pt.prenom_affiche, p.prenom, ''),' ',COALESCE(pt.nom_affiche, p.nom, ''))) LIKE $k1
|
||||
OR LOWER(COALESCE(pt.specialite, p.specialite, '')) LIKE $k2
|
||||
)";
|
||||
|
||||
$params[$k1] = '%'.$word.'%';
|
||||
$params[$k2] = '%'.$word.'%';
|
||||
}
|
||||
$params[$k1] = '%' . $word . '%';
|
||||
$params[$k2] = '%' . $word . '%';
|
||||
}
|
||||
|
||||
$whereSql[] = '(' . implode(' AND ', $and) . ')';
|
||||
}
|
||||
$whereSql[] = '(' . implode(' AND ', $and) . ')';
|
||||
}
|
||||
|
||||
if ($w !== '') {
|
||||
// Candidats latins à partir d'une saisie RU (alias + translittération)
|
||||
$villeCands = city_query_candidates($w);
|
||||
// Ajoute aussi la saisie brute (utile si déjà en latin)
|
||||
$villeCands[] = mb_strtolower($w, 'UTF-8');
|
||||
$villeCands = array_values(array_unique($villeCands));
|
||||
|
||||
if ($w !== '') {
|
||||
// Candidats latins à partir d'une saisie RU (alias + translittération)
|
||||
$villeCands = city_query_candidates($w);
|
||||
// Ajoute aussi la saisie brute (utile si déjà en latin)
|
||||
$villeCands[] = mb_strtolower($w, 'UTF-8');
|
||||
$villeCands = array_values(array_unique($villeCands));
|
||||
$ors = [];
|
||||
|
||||
$ors = [];
|
||||
// Ville (LOWER(...)) : on cherche par préfixe
|
||||
foreach ($villeCands as $i => $cand) {
|
||||
$key = ":wv$i";
|
||||
$ors[] = "LOWER(COALESCE(pt.ville_affichee, p.ville, '')) LIKE $key";
|
||||
$params[$key] = $cand . '%'; // ← passe en '%'.$cand.'%' si tu veux "contient"
|
||||
}
|
||||
|
||||
// Ville (LOWER(...)) : on cherche par préfixe
|
||||
foreach ($villeCands as $i => $cand) {
|
||||
$key = ":wv$i";
|
||||
$ors[] = "LOWER(COALESCE(ville,'')) LIKE $key";
|
||||
$params[$key] = $cand . '%'; // ← passe en '%'.$cand.'%' si tu veux "contient"
|
||||
}
|
||||
// Code postal : on garde un LIKE simple sur la saisie brute
|
||||
$ors[] = "COALESCE(p.code_postal,'') LIKE :wcp";
|
||||
$params[':wcp'] = '%' . $w . '%';
|
||||
|
||||
// Code postal : on garde un LIKE simple sur la saisie brute
|
||||
$ors[] = "COALESCE(code_postal,'') LIKE :wcp";
|
||||
$params[':wcp'] = '%' . $w . '%';
|
||||
$whereSql[] = '(' . implode(' OR ', $ors) . ')';
|
||||
}
|
||||
$whereClause = $whereSql ? (' WHERE ' . implode(' AND ', $whereSql)) : '';
|
||||
|
||||
$whereSql[] = '(' . implode(' OR ', $ors) . ')';
|
||||
}
|
||||
$whereClause = $whereSql ? (' WHERE '.implode(' AND ', $whereSql)) : '';
|
||||
|
||||
/* Total pour pagination (utile seulement si !isSearch, mais on calcule une fois) */
|
||||
$sqlCount = "SELECT COUNT(*) FROM praticien".$whereClause;
|
||||
$stmt = $pdo->prepare($sqlCount);
|
||||
$stmt->execute($params);
|
||||
$total = (int)$stmt->fetchColumn();
|
||||
$totalPages = max(1, (int)ceil($total / $perPage));
|
||||
|
||||
/* Liste (paginée si pas de recherche, sinon tout) */
|
||||
$sqlList = "
|
||||
SELECT id, titre, nom, prenom, photo, specialite, ville, code_postal, presentation, video
|
||||
FROM praticien
|
||||
/* Total pour pagination (utile seulement si !isSearch, mais on calcule une fois) */
|
||||
$sqlCount = "
|
||||
SELECT COUNT(*)
|
||||
FROM praticien p
|
||||
LEFT JOIN praticien_traduction pt
|
||||
ON pt.praticien_id = p.id
|
||||
AND pt.langue = :lang
|
||||
$whereClause
|
||||
ORDER BY specialite ASC, nom ASC
|
||||
";
|
||||
if (!$isSearch) {
|
||||
$sqlList .= " LIMIT :limit OFFSET :offset";
|
||||
}
|
||||
$stmt = $pdo->prepare($sqlList);
|
||||
foreach ($params as $k=>$v) $stmt->bindValue($k, $v, PDO::PARAM_STR);
|
||||
if (!$isSearch) {
|
||||
$stmt->bindValue(':limit', $perPage, PDO::PARAM_INT);
|
||||
$stmt->bindValue(':offset', ($page-1)*$perPage, PDO::PARAM_INT);
|
||||
}
|
||||
$stmt->execute();
|
||||
$praticiens = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
/* =========================
|
||||
$stmt = $pdo->prepare($sqlCount);
|
||||
$paramsCount = $params;
|
||||
$paramsCount[':lang'] = $currentLang;
|
||||
$stmt->execute($paramsCount);
|
||||
$total = (int) $stmt->fetchColumn();
|
||||
$totalPages = max(1, (int) ceil($total / $perPage));
|
||||
|
||||
/* Liste (paginée si pas de recherche, sinon tout) */
|
||||
$sqlList = "
|
||||
SELECT
|
||||
p.id,
|
||||
p.titre,
|
||||
p.nom,
|
||||
p.prenom,
|
||||
p.photo,
|
||||
p.ville,
|
||||
p.code_postal,
|
||||
p.video,
|
||||
COALESCE(pt.nom_affiche, p.nom) AS nom_affiche,
|
||||
COALESCE(pt.prenom_affiche, p.prenom) AS prenom_affiche,
|
||||
COALESCE(pt.specialite, p.specialite) AS specialite_affichee,
|
||||
COALESCE(pt.ville_affichee, p.ville) AS ville_affichee,
|
||||
COALESCE(pt.presentation, p.presentation) AS presentation_affichee
|
||||
FROM praticien p
|
||||
LEFT JOIN praticien_traduction pt
|
||||
ON pt.praticien_id = p.id
|
||||
AND pt.langue = :lang
|
||||
$whereClause
|
||||
ORDER BY COALESCE(pt.specialite, p.specialite) ASC, COALESCE(pt.nom_affiche, p.nom) ASC
|
||||
";
|
||||
if (! $isSearch) {
|
||||
$sqlList .= " LIMIT :limit OFFSET :offset";
|
||||
}
|
||||
$stmt = $pdo->prepare($sqlList);
|
||||
$params[':lang'] = $currentLang;
|
||||
foreach ($params as $k => $v) {
|
||||
$stmt->bindValue($k, $v, PDO::PARAM_STR);
|
||||
}
|
||||
|
||||
if (! $isSearch) {
|
||||
$stmt->bindValue(':limit', $perPage, PDO::PARAM_INT);
|
||||
$stmt->bindValue(':offset', ($page - 1) * $perPage, PDO::PARAM_INT);
|
||||
}
|
||||
$stmt->execute();
|
||||
$praticiens = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
/* =========================
|
||||
Rendu partiel AJAX (#results)
|
||||
========================= */
|
||||
function render_results(array $praticiens, array $translations, bool $isSearch, int $page, int $totalPages): void {
|
||||
?>
|
||||
function render_results(array $praticiens, array $translations, bool $isSearch, int $page, int $totalPages): void
|
||||
{
|
||||
?>
|
||||
<?php if ($isSearch): ?>
|
||||
<h1 class="results-title"><?= htmlspecialchars($translations['res_poisk'] ?? 'Résultats', ENT_QUOTES, 'UTF-8') ?></h1>
|
||||
<h1 class="results-title"><?php echo htmlspecialchars($translations['res_poisk'] ?? 'Résultats', ENT_QUOTES, 'UTF-8') ?></h1>
|
||||
|
||||
<div id="resultCount" class="result-count">
|
||||
<?php
|
||||
$count = count($praticiens);
|
||||
echo $count === 0
|
||||
? htmlspecialchars($translations['ne_naideno'] ?? 'Aucun résultat', ENT_QUOTES, 'UTF-8')
|
||||
: htmlspecialchars(($translations['naideno'] ?? 'Résultats')." : ".$count, ENT_QUOTES, 'UTF-8');
|
||||
?>
|
||||
$count = count($praticiens);
|
||||
echo $count === 0
|
||||
? htmlspecialchars($translations['ne_naideno'] ?? 'Aucun résultat', ENT_QUOTES, 'UTF-8')
|
||||
: htmlspecialchars(($translations['naideno'] ?? 'Résultats') . " : " . $count, ENT_QUOTES, 'UTF-8');
|
||||
?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="pro-list" id="list">
|
||||
<?php if (empty($praticiens)): ?>
|
||||
|
||||
|
||||
<?php else: ?>
|
||||
<?php foreach ($praticiens as $p):
|
||||
$name = fullName($p);
|
||||
$spec = $p['specialite'] ?? '';
|
||||
$ville = $p['ville'] ?? '';
|
||||
$cp = $p['code_postal'] ?? '';
|
||||
$present= excerpt_words((string)($p['presentation'] ?? ''), 35);
|
||||
$vinfo = !empty($p['video']) ? video_thumb_info($p['video']) : null;
|
||||
$thumb = $vinfo['thumb'] ?? null;
|
||||
$altVid = htmlspecialchars($translations['video_alt'] ?? 'Vidéo', ENT_QUOTES, 'UTF-8');
|
||||
$href = !empty($p['video']) ? htmlspecialchars($p['video'], ENT_QUOTES, 'UTF-8') : '';
|
||||
?>
|
||||
$name = fullName($p);
|
||||
$spec = $p['specialite_affichee'] ?? $p['specialite'] ?? '';
|
||||
$ville = $p['ville_affichee'] ?? $p['ville'] ?? '';
|
||||
$cp = $p['code_postal'] ?? '';
|
||||
$present = excerpt_words((string) ($p['presentation_affichee'] ?? $p['presentation'] ?? ''), 35);
|
||||
$vinfo = ! empty($p['video']) ? video_thumb_info($p['video']) : null;
|
||||
$thumb = $vinfo['thumb'] ?? null;
|
||||
$altVid = htmlspecialchars($translations['video_alt'] ?? 'Vidéo', ENT_QUOTES, 'UTF-8');
|
||||
$href = ! empty($p['video']) ? htmlspecialchars($p['video'], ENT_QUOTES, 'UTF-8') : '';
|
||||
?>
|
||||
<article class="pro-card praticien"
|
||||
data-name="<?= htmlspecialchars(mb_strtolower($name), ENT_QUOTES, 'UTF-8') ?>"
|
||||
data-spec="<?= htmlspecialchars(mb_strtolower($spec), ENT_QUOTES, 'UTF-8') ?>"
|
||||
data-ville="<?= htmlspecialchars(mb_strtolower($ville), ENT_QUOTES, 'UTF-8') ?>"
|
||||
data-cp="<?= htmlspecialchars(mb_strtolower($cp), ENT_QUOTES, 'UTF-8') ?>">
|
||||
data-name="<?php echo htmlspecialchars(mb_strtolower($name), ENT_QUOTES, 'UTF-8') ?>"
|
||||
data-spec="<?php echo htmlspecialchars(mb_strtolower($spec), ENT_QUOTES, 'UTF-8') ?>"
|
||||
data-ville="<?php echo htmlspecialchars(mb_strtolower($ville), ENT_QUOTES, 'UTF-8') ?>"
|
||||
data-cp="<?php echo htmlspecialchars(mb_strtolower($cp), ENT_QUOTES, 'UTF-8') ?>">
|
||||
|
||||
<!-- Photo -->
|
||||
<div class="pro-photo">
|
||||
<a href="/public/client/detail_praticien.php?id=<?= (int)$p['id'] ?>"><img
|
||||
src="<?= !empty($p['photo']) ? '/public/uploads/'.htmlspecialchars($p['photo'], ENT_QUOTES, 'UTF-8') : '/public/uploads/img/user_placeholder.png' ?>"
|
||||
alt="<?= htmlspecialchars($translations['alt_photo'] ?? 'Photo du praticien', ENT_QUOTES, 'UTF-8') ?>"
|
||||
<a href="/public/client/detail_praticien.php?id=<?php echo (int) $p['id'] ?>"><img
|
||||
src="<?php echo ! empty($p['photo']) ? '/public/uploads/' . htmlspecialchars($p['photo'], ENT_QUOTES, 'UTF-8') : '/public/uploads/img/user_placeholder.png' ?>"
|
||||
alt="<?php echo htmlspecialchars($translations['alt_photo'] ?? 'Photo du praticien', ENT_QUOTES, 'UTF-8') ?>"
|
||||
loading="lazy"></a>
|
||||
</div>
|
||||
|
||||
<!-- Infos -->
|
||||
<div class="pro-info">
|
||||
<h2 class="pro-name"><a href="/public/client/detail_praticien.php?id=<?= (int)$p['id'] ?>"><?= htmlspecialchars($name, ENT_QUOTES, 'UTF-8') ?></a></h2>
|
||||
<?php if ($spec): ?><div class="pro-spec"><?= htmlspecialchars($spec, ENT_QUOTES, 'UTF-8') ?></div><?php endif; ?>
|
||||
<h2 class="pro-name"><a href="/public/client/detail_praticien.php?id=<?php echo (int) $p['id'] ?>"><?php echo htmlspecialchars($name, ENT_QUOTES, 'UTF-8') ?></a></h2>
|
||||
<?php if ($spec): ?><div class="pro-spec"><?php echo htmlspecialchars($spec, ENT_QUOTES, 'UTF-8') ?></div><?php endif; ?>
|
||||
<?php if ($present): ?>
|
||||
<p class="pro-desc"><?= nl2br(htmlspecialchars($present, ENT_QUOTES, 'UTF-8')) ?></p>
|
||||
<p class="pro-desc"><?php echo nl2br(htmlspecialchars($present, ENT_QUOTES, 'UTF-8')) ?></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<a href="#" class="favori_lien" data-id="<?= (int)$p['id'] ?>" data-loading="0">
|
||||
<?= htmlspecialchars($translations['plus_favorits'] ?? 'Ajouter aux favoris', ENT_QUOTES, 'UTF-8') ?>
|
||||
<a href="#" class="favori_lien" data-id="<?php echo (int) $p['id'] ?>" data-loading="0">
|
||||
<?php echo htmlspecialchars($translations['plus_favorits'] ?? 'Ajouter aux favoris', ENT_QUOTES, 'UTF-8') ?>
|
||||
</a>
|
||||
<a href="/public/client/detail_praticien.php?id=<?= (int)$p['id'] ?>">
|
||||
<?= htmlspecialchars($translations['plus_details'] ?? 'Détails', ENT_QUOTES, 'UTF-8') ?>
|
||||
<a href="/public/client/detail_praticien.php?id=<?php echo (int) $p['id'] ?>">
|
||||
<?php echo htmlspecialchars($translations['plus_details'] ?? 'Détails', ENT_QUOTES, 'UTF-8') ?>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Vidéo -->
|
||||
<?php if(!empty($p['video'])): ?>
|
||||
<?php if (! empty($p['video'])): ?>
|
||||
<div class="pro-video-col">
|
||||
<a class="pro-video"
|
||||
href="#"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#videoModal"
|
||||
data-yturl="<?= $href ?>"
|
||||
aria-label="<?= $altVid ?>">
|
||||
data-yturl="<?php echo $href ?>"
|
||||
aria-label="<?php echo $altVid ?>">
|
||||
<?php if ($thumb): ?>
|
||||
<img src="<?= htmlspecialchars($thumb, ENT_QUOTES, 'UTF-8') ?>" alt="<?= $altVid ?>" loading="lazy">
|
||||
<img src="<?php echo htmlspecialchars($thumb, ENT_QUOTES, 'UTF-8') ?>" alt="<?php echo $altVid ?>" loading="lazy">
|
||||
<?php else: ?>
|
||||
<img src="/public/uploads/img/video_placeholder.jpg" alt="<?= $altVid ?>" loading="lazy">
|
||||
<img src="/public/uploads/img/video_placeholder.jpg" alt="<?php echo $altVid ?>" loading="lazy">
|
||||
<?php endif; ?>
|
||||
</a>
|
||||
</div>
|
||||
@@ -220,8 +273,8 @@ function render_results(array $praticiens, array $translations, bool $isSearch,
|
||||
|
||||
<!-- CTA -->
|
||||
<div class="pro-cta-col">
|
||||
<a class="pro-cta" href="/public/client/reserver_rdv.php?praticien_id=<?= (int)$p['id'] ?>">
|
||||
<?= htmlspecialchars($translations['den'] ?? 'Prendre RDV', ENT_QUOTES, 'UTF-8') ?>
|
||||
<a class="pro-cta" href="/public/client/reserver_rdv.php?praticien_id=<?php echo (int) $p['id'] ?>">
|
||||
<?php echo htmlspecialchars($translations['den'] ?? 'Prendre RDV', ENT_QUOTES, 'UTF-8') ?>
|
||||
</a>
|
||||
</div>
|
||||
</article>
|
||||
@@ -229,40 +282,40 @@ function render_results(array $praticiens, array $translations, bool $isSearch,
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if (!$isSearch && $totalPages > 1): ?>
|
||||
<?php if (! $isSearch && $totalPages > 1): ?>
|
||||
<nav class="pager" aria-label="Pagination">
|
||||
<a class="pager-arrow <?= $page<=1?'is-disabled':'' ?>"
|
||||
href="?page=<?= max(1,$page-1) ?>" aria-label="Page précédente">««</a>
|
||||
<a class="pager-arrow <?php echo $page <= 1 ? 'is-disabled' : '' ?>"
|
||||
href="?page=<?php echo max(1, $page - 1) ?>" aria-label="Page précédente">««</a>
|
||||
|
||||
<?php
|
||||
$start = max(1, $page-2);
|
||||
$end = min($totalPages, $page+2);
|
||||
for ($p=$start; $p<=$end; $p++):
|
||||
?>
|
||||
<a class="pager-page <?= $p===$page?'is-active':'' ?>" href="?page=<?= $p ?>"><?= $p ?></a>
|
||||
$start = max(1, $page - 2);
|
||||
$end = min($totalPages, $page + 2);
|
||||
for ($p = $start; $p <= $end; $p++):
|
||||
?>
|
||||
<a class="pager-page <?php echo $p === $page ? 'is-active' : '' ?>" href="?page=<?php echo $p ?>"><?php echo $p ?></a>
|
||||
<?php endfor; ?>
|
||||
|
||||
<a class="pager-arrow <?= $page>=$totalPages?'is-disabled':'' ?>"
|
||||
href="?page=<?= min($totalPages,$page+1) ?>" aria-label="Page suivante">»»</a>
|
||||
<a class="pager-arrow <?php echo $page >= $totalPages ? 'is-disabled' : '' ?>"
|
||||
href="?page=<?php echo min($totalPages, $page + 1) ?>" aria-label="Page suivante">»»</a>
|
||||
</nav>
|
||||
<?php endif;
|
||||
}
|
||||
}
|
||||
|
||||
/* Si appel AJAX, ne renvoyer que le fragment #results */
|
||||
if (isset($_GET['ajax']) && $_GET['ajax'] === '1') {
|
||||
header('Content-Type: text/html; charset=utf-8');
|
||||
ob_start();
|
||||
render_results($praticiens, $translations, $isSearch, $page, $totalPages);
|
||||
echo ob_get_clean();
|
||||
exit;
|
||||
}
|
||||
require_once '../includes/header.php';
|
||||
?>
|
||||
/* Si appel AJAX, ne renvoyer que le fragment #results */
|
||||
if (isset($_GET['ajax']) && $_GET['ajax'] === '1') {
|
||||
header('Content-Type: text/html; charset=utf-8');
|
||||
ob_start();
|
||||
render_results($praticiens, $translations, $isSearch, $page, $totalPages);
|
||||
echo ob_get_clean();
|
||||
exit;
|
||||
}
|
||||
require_once '../includes/header.php';
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title><?= htmlspecialchars($translations['menu_liste_specialistes'] ?? 'Liste des praticiens', ENT_QUOTES, 'UTF-8') ?></title>
|
||||
<title><?php echo htmlspecialchars($translations['menu_liste_specialistes'] ?? 'Liste des praticiens', ENT_QUOTES, 'UTF-8') ?></title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<!-- Design -->
|
||||
<link rel="stylesheet" href="../../includes/custom_style.css">
|
||||
@@ -295,14 +348,14 @@ require_once '../includes/header.php';
|
||||
<span class="search-icon" aria-hidden="true">🔎</span>
|
||||
<div class="input-wrap">
|
||||
<input id="q" type="text"
|
||||
placeholder="<?= htmlspecialchars($translations['n_special'] ?? 'spécialité, nom…', ENT_QUOTES, 'UTF-8') ?>"
|
||||
aria-label="spécialité, nom" value="<?= htmlspecialchars($qRaw, ENT_QUOTES, 'UTF-8') ?>">
|
||||
placeholder="<?php echo htmlspecialchars($translations['n_special'] ?? 'spécialité, nom…', ENT_QUOTES, 'UTF-8') ?>"
|
||||
aria-label="spécialité, nom" value="<?php echo htmlspecialchars($qRaw, ENT_QUOTES, 'UTF-8') ?>">
|
||||
</div>
|
||||
<div class="divider" aria-hidden="true"></div>
|
||||
<div class="input-wrap">
|
||||
<input id="where" type="text"
|
||||
placeholder="<?= htmlspecialchars($translations['gde'] ?? 'ville ou code postal', ENT_QUOTES, 'UTF-8') ?>"
|
||||
aria-label="ville ou code postal" value="<?= htmlspecialchars($wRaw, ENT_QUOTES, 'UTF-8') ?>">
|
||||
placeholder="<?php echo htmlspecialchars($translations['gde'] ?? 'ville ou code postal', ENT_QUOTES, 'UTF-8') ?>"
|
||||
aria-label="ville ou code postal" value="<?php echo htmlspecialchars($wRaw, ENT_QUOTES, 'UTF-8') ?>">
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -316,7 +369,7 @@ require_once '../includes/header.php';
|
||||
|
||||
<footer class="site-footer">
|
||||
<div class="container">
|
||||
<p><?= $translations['copyr'] ?></p>
|
||||
<p><?php echo $translations['copyr'] ?></p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@@ -325,8 +378,8 @@ require_once '../includes/header.php';
|
||||
<div class="modal-dialog modal-dialog-centered modal-xl">
|
||||
<div class="modal-content border-0 shadow-lg rounded-4">
|
||||
<div class="modal-header border-0">
|
||||
<h5 class="modal-title" id="videoModalLabel"><?= htmlspecialchars($translations['video_titre'] ?? 'Vidéo') ?></h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="<?= htmlspecialchars($translations['fermer'] ?? 'Fermer') ?>"></button>
|
||||
<h5 class="modal-title" id="videoModalLabel"><?php echo htmlspecialchars($translations['video_titre'] ?? 'Vidéo') ?></h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="<?php echo htmlspecialchars($translations['fermer'] ?? 'Fermer') ?>"></button>
|
||||
</div>
|
||||
<div class="modal-body pt-0">
|
||||
<div class="ratio ratio-16x9 rounded-3 overflow-hidden">
|
||||
@@ -340,7 +393,7 @@ require_once '../includes/header.php';
|
||||
</div>
|
||||
<div class="modal-footer border-0">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||
<?= htmlspecialchars($translations['fermer'] ?? 'Fermer') ?>
|
||||
<?php echo htmlspecialchars($translations['fermer'] ?? 'Fermer') ?>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -348,6 +401,9 @@ require_once '../includes/header.php';
|
||||
</div>
|
||||
|
||||
<!-- JS -->
|
||||
<script>
|
||||
const csrfToken = <?php echo json_encode(ensure_csrf_token(), JSON_UNESCAPED_UNICODE) ?>;
|
||||
</script>
|
||||
<script>
|
||||
(function(){
|
||||
// Convertit n'importe quelle URL YouTube en URL d'embed nocookie
|
||||
@@ -421,40 +477,69 @@ require_once '../includes/header.php';
|
||||
}, false);
|
||||
|
||||
// Favoris (SweetAlert2)
|
||||
function ajouterFavori(el, praticienId) {
|
||||
if (el.dataset.loading === '1') return;
|
||||
el.dataset.loading = '1';
|
||||
fetch('/public/client/ajouter_favori.php', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
},
|
||||
body: new URLSearchParams({ praticien_id: praticienId }).toString()
|
||||
})
|
||||
.then(async (r) => {
|
||||
const text = await r.text();
|
||||
if (!r.ok) throw new Error(text || 'Erreur réseau');
|
||||
const deja = /déjà|already/i.test(text);
|
||||
el.classList.add('ajoute'); el.setAttribute('aria-pressed', 'true'); el.title = text;
|
||||
if (typeof Swal !== 'undefined') {
|
||||
Swal.fire({
|
||||
icon: deja ? 'info' : 'success',
|
||||
title: <?= json_encode($translations['sw_favori_ajoute_title'] ?? 'Favori ajouté !', JSON_UNESCAPED_UNICODE) ?>,
|
||||
text: text,
|
||||
timer: 1800,
|
||||
showConfirmButton: false
|
||||
});
|
||||
} else { alert(text); }
|
||||
})
|
||||
.catch((err) => {
|
||||
if (typeof Swal !== 'undefined') {
|
||||
Swal.fire({ icon:'error', title:'Oups', text: err.message || 'Erreur.' });
|
||||
} else { console.error(err); alert('Erreur.'); }
|
||||
})
|
||||
.finally(() => { el.dataset.loading = '0'; });
|
||||
function ajouterFavori(el, praticienId) {
|
||||
if (el.dataset.loading === '1') return;
|
||||
el.dataset.loading = '1';
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('praticien_id', String(praticienId));
|
||||
formData.append('csrf_token', csrfToken);
|
||||
|
||||
fetch('/public/client/ajouter_favori.php', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
},
|
||||
body: formData
|
||||
})
|
||||
.then(async (r) => {
|
||||
const text = await r.text();
|
||||
let data;
|
||||
|
||||
try {
|
||||
data = JSON.parse(text);
|
||||
} catch (e) {
|
||||
throw new Error(text || 'Erreur réseau');
|
||||
}
|
||||
|
||||
if (!r.ok || !data.ok) {
|
||||
throw new Error(data.message || 'Erreur.');
|
||||
}
|
||||
|
||||
el.classList.add('ajoute');
|
||||
el.setAttribute('aria-pressed', 'true');
|
||||
el.title = data.message || '';
|
||||
|
||||
if (typeof Swal !== 'undefined') {
|
||||
Swal.fire({
|
||||
icon: data.already_exists ? 'info' : 'success',
|
||||
title: <?php echo json_encode($translations['sw_favori_ajoute_title'] ?? 'Favori ajouté !', JSON_UNESCAPED_UNICODE) ?>,
|
||||
text: data.message || '',
|
||||
timer: 1800,
|
||||
showConfirmButton: false
|
||||
});
|
||||
} else {
|
||||
alert(data.message || 'OK');
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
if (typeof Swal !== 'undefined') {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Oups',
|
||||
text: err.message || 'Erreur.'
|
||||
});
|
||||
} else {
|
||||
console.error(err);
|
||||
alert('Erreur.');
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
el.dataset.loading = '0';
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('click', (e)=>{
|
||||
const a = e.target.closest('.favori_lien');
|
||||
if (!a) return;
|
||||
@@ -462,10 +547,10 @@ require_once '../includes/header.php';
|
||||
const id = a.dataset.id;
|
||||
|
||||
const T = {
|
||||
title: <?= json_encode($translations['confirmation'] ?? 'Confirmation') ?>,
|
||||
text: <?= json_encode($translations['voulez_vous_ajouter_favori'] ?? 'Ajouter ce praticien à vos favoris ?') ?>,
|
||||
ok: <?= json_encode($translations['btn_oui'] ?? 'Oui') ?>,
|
||||
cancel:<?= json_encode($translations['btn_annuler'] ?? 'Annuler') ?>
|
||||
title: <?php echo json_encode($translations['confirmation'] ?? 'Confirmation') ?>,
|
||||
text: <?php echo json_encode($translations['voulez_vous_ajouter_favori'] ?? 'Ajouter ce praticien à vos favoris ?') ?>,
|
||||
ok: <?php echo json_encode($translations['btn_oui'] ?? 'Oui') ?>,
|
||||
cancel:<?php echo json_encode($translations['btn_annuler'] ?? 'Annuler') ?>
|
||||
};
|
||||
|
||||
if (typeof Swal !== 'undefined') {
|
||||
|
||||
@@ -1,67 +1,68 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../includes/init_client.php';
|
||||
require_once __DIR__ . '/../includes/init_client.php';
|
||||
|
||||
$client_id = $_SESSION['reference_id'];
|
||||
|
||||
|
||||
$client_id = $_SESSION['reference_id'];
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT p.*
|
||||
FROM praticien_client pc
|
||||
JOIN praticien p ON pc.praticien_id = p.id
|
||||
WHERE pc.client_id = ?
|
||||
ORDER BY p.nom ASC
|
||||
");
|
||||
$stmt->execute([$client_id]);
|
||||
$favoris = $stmt->fetchAll();
|
||||
$stmt->execute([$client_id]);
|
||||
$favoris = $stmt->fetchAll();
|
||||
|
||||
// util pour couper les mots de la description texte
|
||||
function h($s)
|
||||
{return htmlspecialchars((string) $s, ENT_QUOTES, 'UTF-8');}
|
||||
function excerpt($text, $len = 160)
|
||||
{
|
||||
$t = trim(strip_tags((string) $text));
|
||||
if (mb_strlen($t, 'UTF-8') <= $len) {
|
||||
return $t;
|
||||
}
|
||||
|
||||
// util pour couper les mots de la description texte
|
||||
function h($s){ return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); }
|
||||
function excerpt($text, $len=160){
|
||||
$t = trim(strip_tags((string)$text));
|
||||
if (mb_strlen($t,'UTF-8') <= $len) return $t;
|
||||
return mb_substr($t, 0, $len, 'UTF-8') . '…';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
$PAGE_TITLE = $translations['mes_praticiens_favoris_title'] ?? 'Mes praticiens favoris';
|
||||
$HEAD_EXTRA = <<<HTML
|
||||
$PAGE_TITLE = $translations['mes_praticiens_favoris_title'] ?? 'Mes praticiens favoris';
|
||||
$HEAD_EXTRA = <<<HTML
|
||||
<script src="/js/sweetalert2@11.js"></script>
|
||||
HTML;
|
||||
|
||||
require_once '../includes/header.php';
|
||||
require_once '../includes/header.php';
|
||||
?>
|
||||
<div class="main1">
|
||||
<div class="container py-5">
|
||||
<div class="shadow p-4 rounded bg-light">
|
||||
<h2 class="mb-4 text-center"> <?= $translations['mes_praticiens_favoris_heading'] ?></h2>
|
||||
<h2 class="mb-4 text-center"> <?php echo $translations['mes_praticiens_favoris_heading'] ?></h2>
|
||||
|
||||
<?php if (empty($favoris)): ?>
|
||||
<div class="alert alert-info"><?= $translations['aucun_favori_pour_le_moment'] ?></div>
|
||||
<div class="alert alert-info"><?php echo $translations['aucun_favori_pour_le_moment'] ?></div>
|
||||
<?php else: ?>
|
||||
<?php foreach ($favoris as $p): ?>
|
||||
<div class="card mb-4 shadow-sm" id="praticien_<?= $p['id'] ?>">
|
||||
<div class="card mb-4 shadow-sm" id="praticien_<?php echo $p['id'] ?>">
|
||||
<div class="card-body praticien-card">
|
||||
<div>
|
||||
<?php if ($p['photo']): ?>
|
||||
<img src="/public/uploads/<?= htmlspecialchars($p['photo']) ?>" alt="<?= $translations['alt_photo'] ?>" class="card-img-top">
|
||||
<img src="/public/uploads/<?php echo htmlspecialchars($p['photo']) ?>" alt="<?php echo $translations['alt_photo'] ?>" class="card-img-top">
|
||||
<?php else: ?>
|
||||
<img src="/public/uploads/img/user_placeholder.png" alt="<?= $translations['alt_photo'] ?>" class="card-img-top">
|
||||
<img src="/public/uploads/img/user_placeholder.png" alt="<?php echo $translations['alt_photo'] ?>" class="card-img-top">
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<h2 class="pro-name"><?= htmlspecialchars($p['prenom']?? '') . ' ' . htmlspecialchars($p['nom']?? '') ?></h5>
|
||||
<p class="mb-1"><strong><?= $translations['label_ville'] ?> :</strong> <?= htmlspecialchars($p['ville'] ?? '') ?></p>
|
||||
<h2 class="pro-name"><?php echo htmlspecialchars($p['prenom'] ?? '') . ' ' . htmlspecialchars($p['nom'] ?? '') ?></h5>
|
||||
<p class="mb-1"><strong><?php echo $translations['label_ville'] ?> :</strong> <?php echo htmlspecialchars($p['ville'] ?? '') ?></p>
|
||||
<p class="mb-2">
|
||||
<?php if (!empty($p['presentation'])): ?>
|
||||
<?= h(excerpt($p['presentation'], 300)) ?>
|
||||
<?php if (! empty($p['presentation'])): ?>
|
||||
<?php echo h(excerpt($p['presentation'], 300)) ?>
|
||||
<?php endif; ?>
|
||||
</p>
|
||||
|
||||
<div class="d-flex gap-3">
|
||||
<button class="btn btn-danger btn-sm" onclick="supprimerFavori(<?= $p['id'] ?>)"> <?= $translations['btn_retirer'] ?></button>
|
||||
<a class="btn btn-outline-secondary btn-sm btn-table" href="/public/client/reserver_rdv.php?praticien_id=<?= $p['id'] ?>"> <?= $translations['btn_rendez_vous'] ?></a>
|
||||
<button class="btn btn-danger btn-sm" onclick="supprimerFavori(<?php echo $p['id'] ?>)"> <?php echo $translations['btn_retirer'] ?></button>
|
||||
<a class="btn btn-outline-secondary btn-sm btn-table" href="/public/client/reserver_rdv.php?praticien_id=<?php echo $p['id'] ?>"> <?php echo $translations['btn_rendez_vous'] ?></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -70,44 +71,76 @@ require_once '../includes/header.php';
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
const csrfToken = <?php echo json_encode(ensure_csrf_token(), JSON_UNESCAPED_UNICODE) ?>;
|
||||
</script>
|
||||
<script>
|
||||
function supprimerFavori(praticienId) {
|
||||
Swal.fire({
|
||||
title: <?= json_encode($translations['confirm_supprimer_favori_title']) ?>,
|
||||
text: <?= json_encode($translations['confirm_supprimer_favori_text']) ?>,
|
||||
title: <?php echo json_encode($translations['confirm_supprimer_favori_title']) ?>,
|
||||
text: <?php echo json_encode($translations['confirm_supprimer_favori_text']) ?>,
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#dc3545',
|
||||
cancelButtonColor: '#6c757d',
|
||||
confirmButtonText: <?= json_encode($translations['btn_oui_supprimer']) ?>,
|
||||
cancelButtonText: <?= json_encode($translations['btn_annuler']) ?>
|
||||
confirmButtonText: <?php echo json_encode($translations['btn_oui_supprimer']) ?>,
|
||||
cancelButtonText: <?php echo json_encode($translations['btn_annuler']) ?>
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
fetch('/public/client/supprimer_favori.php', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: 'praticien_id=' + praticienId
|
||||
})
|
||||
.then(res => res.text())
|
||||
.then(msg => {
|
||||
Swal.fire({
|
||||
icon: 'success',
|
||||
title: <?= json_encode($translations['favori_supprime_title']) ?>,
|
||||
text: msg,
|
||||
timer: 1500,
|
||||
showConfirmButton: false
|
||||
});
|
||||
const element = document.getElementById('praticien_' + praticienId);
|
||||
if (element) element.remove();
|
||||
if (!result.isConfirmed) return;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('praticien_id', String(praticienId));
|
||||
formData.append('csrf_token', csrfToken);
|
||||
|
||||
fetch('/public/client/supprimer_favori.php', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
},
|
||||
body: formData
|
||||
})
|
||||
.then(async (res) => {
|
||||
const text = await res.text();
|
||||
let data;
|
||||
|
||||
try {
|
||||
data = JSON.parse(text);
|
||||
} catch (e) {
|
||||
throw new Error(text || 'Erreur réseau');
|
||||
}
|
||||
|
||||
if (!res.ok || !data.ok) {
|
||||
throw new Error(data.message || 'Erreur.');
|
||||
}
|
||||
|
||||
Swal.fire({
|
||||
icon: 'success',
|
||||
title: <?php echo json_encode($translations['favori_supprime_title']) ?>,
|
||||
text: data.message || '',
|
||||
timer: 1500,
|
||||
showConfirmButton: false
|
||||
});
|
||||
}
|
||||
|
||||
const element = document.getElementById('praticien_' + praticienId);
|
||||
if (element) {
|
||||
element.remove();
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Oups',
|
||||
text: err.message || 'Erreur.'
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
<footer class="site-footer">
|
||||
<div class="container">
|
||||
<p><?= $translations['copyr'] ?></p>
|
||||
<p><?php echo $translations['copyr'] ?></p>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
|
||||
@@ -1,21 +1,17 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../includes/init_client.php';
|
||||
require_once __DIR__ . '/../includes/init_client.php';
|
||||
|
||||
|
||||
|
||||
$pdo->prepare("
|
||||
$pdo->prepare("
|
||||
INSERT INTO push_badge_counters (compte_id, msg_unread, rdv_unread, updated_at)
|
||||
VALUES (:cid, 0, 0, NOW())
|
||||
ON DUPLICATE KEY UPDATE
|
||||
rdv_unread = 0,
|
||||
updated_at = NOW()
|
||||
")->execute([':cid' => (int)$_SESSION['compte_id']]);
|
||||
")->execute([':cid' => (int) $_SESSION['compte_id']]);
|
||||
|
||||
$client_id = $_SESSION['reference_id'];
|
||||
|
||||
|
||||
$client_id = $_SESSION['reference_id'];
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT
|
||||
rv.*,
|
||||
p.nom AS nom_praticien,
|
||||
@@ -29,143 +25,148 @@ $stmt = $pdo->prepare("
|
||||
WHERE rv.client_id = ? AND rv.date_rdv >= NOW()
|
||||
ORDER BY rv.date_rdv ASC
|
||||
");
|
||||
$stmt->execute([$client_id]);
|
||||
$rdvs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
$stmt->execute([$client_id]);
|
||||
$rdvs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$PAGE_TITLE = $translations['mes_rdv_title'] ?? 'Mes rendez-vous';
|
||||
$HEAD_EXTRA = <<<HTML
|
||||
$PAGE_TITLE = $translations['mes_rdv_title'] ?? 'Mes rendez-vous';
|
||||
$HEAD_EXTRA = <<<HTML
|
||||
<script src="/js/sweetalert2@11.js"></script>
|
||||
HTML;
|
||||
require_once '../includes/header.php';
|
||||
require_once '../includes/header.php';
|
||||
?>
|
||||
<div class="main1">
|
||||
<div class="container py-5">
|
||||
<div class="shadow p-4 rounded bg-light">
|
||||
<h2 class="mb-4 text-center">📅 <?= $translations['mes_rdv_heading'] ?></h2>
|
||||
<h2 class="mb-4 text-center">📅 <?php echo $translations['mes_rdv_heading'] ?></h2>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover align-middle mobile-cards" id="table_rdv">
|
||||
<thead class="table-light">
|
||||
<tr align="center">
|
||||
<th><?= $translations['col_date'] ?></th>
|
||||
<th><?= $translations['col_heure'] ?></th>
|
||||
<th><?= $translations['col_praticien'] ?></th>
|
||||
<th><?= $translations['col_duree'] ?></th>
|
||||
<th><?= $translations['col_statut'] ?></th>
|
||||
<th><?= $translations['col_action'] ?></th>
|
||||
<th><?php echo $translations['col_date'] ?></th>
|
||||
<th><?php echo $translations['col_heure'] ?></th>
|
||||
<th><?php echo $translations['col_praticien'] ?></th>
|
||||
<th><?php echo $translations['col_duree'] ?></th>
|
||||
<th><?php echo $translations['col_statut'] ?></th>
|
||||
<th><?php echo $translations['col_action'] ?></th>
|
||||
<!-- Chat -->
|
||||
<th><?= $translations['btn_chat'] ?></th>
|
||||
<th><?php echo $translations['btn_chat'] ?></th>
|
||||
<!-- Visio / Adresse -->
|
||||
<th><?= htmlspecialchars($translations['status_online']) ?></th>
|
||||
<th><?php echo htmlspecialchars($translations['status_online']) ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($rdvs)): ?>
|
||||
<tr><td colspan="8" class="text-center"><?= $translations['aucun_rdv_a_venir'] ?></td></tr>
|
||||
<tr><td colspan="8" class="text-center"><?php echo $translations['aucun_rdv_a_venir'] ?></td></tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($rdvs as $r):
|
||||
$dt = new DateTime($r['date_rdv']);
|
||||
$id = (int)$r['id'];
|
||||
$ficheUrl = '/specialiste?id='.(int)$r['praticien_id'];
|
||||
$dt = new DateTime($r['date_rdv']);
|
||||
$id = (int) $r['id'];
|
||||
$ficheUrl = '/specialiste?id=' . (int) $r['praticien_id'];
|
||||
?>
|
||||
<tr>
|
||||
<td><?= $dt->format('d/m/Y') ?></td>
|
||||
<td><?= $dt->format('H:i') ?></td>
|
||||
<td><?php echo $dt->format('d/m/Y') ?></td>
|
||||
<td><?php echo $dt->format('H:i') ?></td>
|
||||
<td>
|
||||
<a href="/public/client/detail_praticien.php?id=<?= (int)$r['praticien_id'] ?>">
|
||||
<?= htmlspecialchars($r['prenom_praticien'] . ' ' . $r['nom_praticien']) ?>
|
||||
<a href="/public/client/detail_praticien.php?id=<?php echo (int) $r['praticien_id'] ?>">
|
||||
<?php echo htmlspecialchars($r['prenom_praticien'] . ' ' . $r['nom_praticien']) ?>
|
||||
</a>
|
||||
</td>
|
||||
<td><?= (int)$r['duree_minutes'] ?> <?= $translations['min_abbr'] ?></td>
|
||||
<td><?php echo (int) $r['duree_minutes'] ?> <?php echo $translations['min_abbr'] ?></td>
|
||||
|
||||
<td id="statut_<?= $id ?>">
|
||||
<span class="badge rounded-pill <?= $r['statut'] === 'confirmé' ? 'bg-success' : ($r['statut'] === 'prévu' ? 'bg-warning text-dark' : 'bg-secondary') ?>">
|
||||
<?= $r['statut'] === 'confirmé' ? $translations['statut_confirme'] : ($r['statut'] === 'prévu' ? $translations['statut_prevu'] : $translations['statut_annule']) ?>
|
||||
<td id="statut_<?php echo $id ?>">
|
||||
<span class="badge rounded-pill <?php echo $r['statut'] === 'confirmé' ? 'bg-success' : ($r['statut'] === 'prévu' ? 'bg-warning text-dark' : 'bg-secondary') ?>">
|
||||
<?php echo $r['statut'] === 'confirmé' ? $translations['statut_confirme'] : ($r['statut'] === 'prévu' ? $translations['statut_prevu'] : $translations['statut_annule']) ?>
|
||||
</span>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<?php if ($r['statut'] !== 'annulé'): ?>
|
||||
<button class="btn btn-outline-danger btn-sm" onclick="annulerRdv(<?= $id ?>)"> <?= $translations['btn_annuler'] ?></button>
|
||||
<button class="btn btn-outline-danger btn-sm" onclick="annulerRdv(<?php echo $id ?>)"> <?php echo $translations['btn_annuler'] ?></button>
|
||||
<?php else: ?>
|
||||
<em class="text-muted"><?= $translations['statut_annule'] ?></em>
|
||||
<em class="text-muted"><?php echo $translations['statut_annule'] ?></em>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
|
||||
<!-- Chat -->
|
||||
<td>
|
||||
<a class="btn btn-outline-secondary btn-sm btn-table" href="/messages/start.php?praticien_id=<?= (int)$r['praticien_id'] ?>">
|
||||
<?= $translations['chat_praticien'] ?>
|
||||
<a class="btn btn-outline-secondary btn-sm btn-table" href="/messages/start.php?praticien_id=<?php echo (int) $r['praticien_id'] ?>">
|
||||
<?php echo $translations['chat_praticien'] ?>
|
||||
</a>
|
||||
</td>
|
||||
|
||||
<!-- Visio (online) OU Adresse/Lien (offline) -->
|
||||
<td>
|
||||
<?php
|
||||
$lieu = strtolower(trim((string)($r['lieu'] ?? '')));
|
||||
if ($lieu === 'online') {
|
||||
// --- Affichage visio comme avant ---
|
||||
$statut = $r['statut']; // 'confirmé' | 'prévu' | 'annulé'
|
||||
$lieu = strtolower(trim((string) ($r['lieu'] ?? '')));
|
||||
if ($lieu === 'online') {
|
||||
// --- Affichage visio comme avant ---
|
||||
$statut = $r['statut']; // 'confirmé' | 'prévu' | 'annulé'
|
||||
|
||||
$tzParis = new DateTimeZone('Europe/Paris');
|
||||
$now = new DateTime('now', $tzParis);
|
||||
$start = new DateTime($r['date_rdv'], $tzParis);
|
||||
$open = (clone $start)->modify('-15 minutes');
|
||||
$close = (clone $start)->modify('+'.(int)($r['duree_minutes'] ?? 120).' minutes');
|
||||
$tzParis = new DateTimeZone('Europe/Paris');
|
||||
$now = new DateTime('now', $tzParis);
|
||||
$start = new DateTime($r['date_rdv'], $tzParis);
|
||||
$open = (clone $start)->modify('-15 minutes');
|
||||
$close = (clone $start)->modify('+' . (int) ($r['duree_minutes'] ?? 120) . ' minutes');
|
||||
|
||||
if ($statut === 'confirmé') {
|
||||
if ($now >= $open && $now <= $close) {
|
||||
// Bouton actif
|
||||
?>
|
||||
<a href="/visio/join.php?rdv=<?= (int)$r['id'] ?>&start=1" class="btn btn-outline-secondary btn-sm btn-table">
|
||||
<?= htmlspecialchars($translations['visio_nachalo']) ?>
|
||||
if ($statut === 'confirmé') {
|
||||
if ($now >= $open && $now <= $close) {
|
||||
// Bouton actif
|
||||
?>
|
||||
<a href="/visio/join.php?rdv=<?php echo (int) $r['id'] ?>&start=1" class="btn btn-outline-secondary btn-sm btn-table">
|
||||
<?php echo htmlspecialchars($translations['visio_nachalo']) ?>
|
||||
</a>
|
||||
<?php
|
||||
} else {
|
||||
// Messages hors fenêtre
|
||||
?>
|
||||
} else {
|
||||
// Messages hors fenêtre
|
||||
?>
|
||||
<small class="text-muted d-block mt-1">
|
||||
<?php
|
||||
if ($now < $open) echo htmlspecialchars(($translations['ouvert_a'] ?? 'Откроется в')).' '.$open->format('H:i');
|
||||
elseif ($now > $close) echo htmlspecialchars(($translations['ferme_a'] ?? 'Закрыто в')).' '.$close->format('H:i');
|
||||
else echo htmlspecialchars(($translations['prev_a'] ?? 'Запланировано на')).' '.$start->format('d/m/Y \à H:i').'.';
|
||||
?>
|
||||
if ($now < $open) {
|
||||
echo htmlspecialchars(($translations['ouvert_a'] ?? 'Откроется в')) . ' ' . $open->format('H:i');
|
||||
} elseif ($now > $close) {
|
||||
echo htmlspecialchars(($translations['ferme_a'] ?? 'Закрыто в')) . ' ' . $close->format('H:i');
|
||||
} else {
|
||||
echo htmlspecialchars(($translations['prev_a'] ?? 'Запланировано на')) . ' ' . $start->format('d/m/Y \à H:i') . '.';
|
||||
}
|
||||
|
||||
?>
|
||||
</small>
|
||||
<?php
|
||||
}
|
||||
} elseif ($statut === 'prévu') {
|
||||
// Pas encore confirmé
|
||||
?>
|
||||
}
|
||||
} elseif ($statut === 'prévu') {
|
||||
// Pas encore confirmé
|
||||
?>
|
||||
<small class="text-danger d-block mt-1">
|
||||
<?= htmlspecialchars($translations['votre_rdv_jdet'] ?? 'En attente de confirmation') ?>
|
||||
<?php echo htmlspecialchars($translations['votre_rdv_jdet'] ?? 'En attente de confirmation') ?>
|
||||
</small>
|
||||
<?php
|
||||
} else {
|
||||
?>
|
||||
} else {
|
||||
?>
|
||||
<small class="text-muted d-block mt-1">
|
||||
<?= htmlspecialchars($translations['statut_annule'] ?? 'Annulé') ?>
|
||||
<?php echo htmlspecialchars($translations['statut_annule'] ?? 'Annulé') ?>
|
||||
</small>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// --- OFFLINE : afficher adresse (si dispo) + lien fiche praticien ---
|
||||
$addrParts = array_filter([
|
||||
trim((string)($r['p_adresse'] ?? '')),
|
||||
trim((string)($r['p_cp'] ?? '')) . ' ' . trim((string)($r['p_ville'] ?? ''))
|
||||
], fn($x) => $x !== '' && $x !== ' ');
|
||||
$addr = trim(implode(', ', $addrParts));
|
||||
} else {
|
||||
// --- OFFLINE : afficher adresse (si dispo) + lien fiche praticien ---
|
||||
$addrParts = array_filter([
|
||||
trim((string) ($r['p_adresse'] ?? '')),
|
||||
trim((string) ($r['p_cp'] ?? '')) . ' ' . trim((string) ($r['p_ville'] ?? '')),
|
||||
], fn($x) => $x !== '' && $x !== ' ');
|
||||
$addr = trim(implode(', ', $addrParts));
|
||||
|
||||
if ($addr !== '') {
|
||||
//echo '<div class="mb-1"> '.htmlspecialchars($addr).'</div>';
|
||||
}
|
||||
?>
|
||||
<a href="<?= htmlspecialchars($ficheUrl) ?>" class="btn btn-outline-secondary btn-sm btn-table">
|
||||
<?= htmlspecialchars($translations['voir_fiche_praticien'] ?? 'Voir la fiche') ?>
|
||||
if ($addr !== '') {
|
||||
//echo '<div class="mb-1"> '.htmlspecialchars($addr).'</div>';
|
||||
}
|
||||
?>
|
||||
<a href="<?php echo htmlspecialchars($ficheUrl) ?>" class="btn btn-outline-secondary btn-sm btn-table">
|
||||
<?php echo htmlspecialchars($translations['voir_fiche_praticien'] ?? 'Voir la fiche') ?>
|
||||
</a>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
}
|
||||
?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
@@ -175,32 +176,35 @@ require_once '../includes/header.php';
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const csrfToken = <?php echo json_encode(ensure_csrf_token(), JSON_UNESCAPED_UNICODE) ?>;
|
||||
function annulerRdv(id) {
|
||||
Swal.fire({
|
||||
title: <?= json_encode($translations['confirm_annulation_title']) ?>,
|
||||
title: <?php echo json_encode($translations['confirm_annulation_title']) ?>,
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonText: <?= json_encode($translations['btn_oui_annuler']) ?>,
|
||||
cancelButtonText: <?= json_encode($translations['btn_annuler']) ?>,
|
||||
confirmButtonText: <?php echo json_encode($translations['btn_oui_annuler']) ?>,
|
||||
cancelButtonText: <?php echo json_encode($translations['btn_annuler']) ?>,
|
||||
confirmButtonColor: '#dc3545',
|
||||
cancelButtonColor: '#6c757d'
|
||||
}).then((result) => {
|
||||
if (!result.isConfirmed) return;
|
||||
|
||||
fetch('/public/client/annuler_rdv.php', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'},
|
||||
body: new URLSearchParams({ id: String(id) })
|
||||
})
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'},
|
||||
body: new URLSearchParams({
|
||||
id: String(id),
|
||||
csrf_token: csrfToken
|
||||
})
|
||||
})
|
||||
.then(r => r.json().catch(() => ({ok:false, message:'Bad JSON'})))
|
||||
.then(data => {
|
||||
if (data.ok) {
|
||||
Swal.fire({ icon:'success', title: <?= json_encode($translations['rdv_annule_ok']) ?>, text: data.message, timer: 1600, showConfirmButton:false });
|
||||
Swal.fire({ icon:'success', title: <?php echo json_encode($translations['rdv_annule_ok']) ?>, text: data.message, timer: 1600, showConfirmButton:false });
|
||||
const statutEl = document.getElementById("statut_" + id);
|
||||
if (statutEl) {
|
||||
statutEl.innerHTML = '<span class="badge bg-secondary"><?= htmlspecialchars($translations['statut_annule']) ?></span>';
|
||||
statutEl.innerHTML = '<span class="badge bg-secondary"><?php echo htmlspecialchars($translations['statut_annule']) ?></span>';
|
||||
}
|
||||
} else {
|
||||
Swal.fire({ icon:'error', title: 'Oops', text: data.message || 'Erreur' });
|
||||
@@ -241,7 +245,7 @@ function annulerRdv(id) {
|
||||
</div>
|
||||
<footer class="site-footer">
|
||||
<div class="container">
|
||||
<p><?= $translations['copyr'] ?></p>
|
||||
<p><?php echo $translations['copyr'] ?></p>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
|
||||
+48
-46
@@ -1,19 +1,20 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../includes/init_client.php';
|
||||
require_once __DIR__ . '/../includes/init_client.php';
|
||||
|
||||
$id = (int)($_SESSION['reference_id'] ?? 0);
|
||||
if (!$id) { http_response_code(403); exit($translations['client_introuvable'] ?? 'Client introuvable'); }
|
||||
$id = (int) ($_SESSION['reference_id'] ?? 0);
|
||||
if (! $id) {http_response_code(403);exit($translations['client_introuvable'] ?? 'Client introuvable');}
|
||||
|
||||
// Récupérer les données du client
|
||||
$stmt = $pdo->prepare("SELECT * FROM client WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$client = $stmt->fetch();
|
||||
if (!$client) {
|
||||
// Récupérer les données du client
|
||||
$stmt = $pdo->prepare("SELECT * FROM client WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$client = $stmt->fetch();
|
||||
if (! $client) {
|
||||
exit($translations['client_introuvable'] ?? 'Client introuvable');
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Traitement du formulaire AVANT tout output ----
|
||||
if (($_SERVER['REQUEST_METHOD'] ?? '') === 'POST') {
|
||||
// ---- Traitement du formulaire AVANT tout output ----
|
||||
if (($_SERVER['REQUEST_METHOD'] ?? '') === 'POST') {
|
||||
require_post_csrf();
|
||||
$nom = trim($_POST['nom'] ?? '');
|
||||
$prenom = trim($_POST['prenom'] ?? '');
|
||||
$telephone = trim($_POST['telephone'] ?? '');
|
||||
@@ -30,84 +31,85 @@ if (($_SERVER['REQUEST_METHOD'] ?? '') === 'POST') {
|
||||
WHERE id=:id
|
||||
");
|
||||
$stmt->execute([
|
||||
'nom' => $nom,
|
||||
'prenom' => $prenom,
|
||||
'telephone' => $telephone,
|
||||
'genre' => $genre,
|
||||
'nom' => $nom,
|
||||
'prenom' => $prenom,
|
||||
'telephone' => $telephone,
|
||||
'genre' => $genre,
|
||||
'date_naissance' => $date_naiss ?: null,
|
||||
'adresse' => $adresse,
|
||||
'ville' => $ville,
|
||||
'code_postal' => $cp,
|
||||
'id' => $id
|
||||
'adresse' => $adresse,
|
||||
'ville' => $ville,
|
||||
'code_postal' => $cp,
|
||||
'id' => $id,
|
||||
]);
|
||||
|
||||
// redirection (évite le repost + actualise la page infos)
|
||||
header('Location: /public/client/informations.php?updated=1');
|
||||
exit;
|
||||
}
|
||||
require_once __DIR__ . '/../includes/header.php';
|
||||
}
|
||||
require_once __DIR__ . '/../includes/header.php';
|
||||
|
||||
?>
|
||||
|
||||
<div class="main1">
|
||||
<div class="container py-5">
|
||||
|
||||
|
||||
|
||||
<form method="POST" class="shadow p-4 rounded bg-light">
|
||||
<h2 class="mb-4 text-center"><?= $translations['modifier_infos_heading'] ?></h2>
|
||||
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars(ensure_csrf_token(), ENT_QUOTES, 'UTF-8') ?>">
|
||||
<h2 class="mb-4 text-center"><?php echo $translations['modifier_infos_heading'] ?></h2>
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><?= $translations['label_nom'] ?> *</label>
|
||||
<input type="text" name="nom" value="<?= htmlspecialchars($client['nom'] ?? '') ?>" required class="form-control">
|
||||
<label class="form-label"><?php echo $translations['label_nom'] ?> *</label>
|
||||
<input type="text" name="nom" value="<?php echo htmlspecialchars($client['nom'] ?? '') ?>" required class="form-control">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><?= $translations['label_prenom'] ?> *</label>
|
||||
<input type="text" name="prenom" value="<?= htmlspecialchars($client['prenom'] ?? '') ?>" required class="form-control">
|
||||
<label class="form-label"><?php echo $translations['label_prenom'] ?> *</label>
|
||||
<input type="text" name="prenom" value="<?php echo htmlspecialchars($client['prenom'] ?? '') ?>" required class="form-control">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><?= $translations['label_telephone'] ?> *</label>
|
||||
<input type="tel" name="telephone" id="telephone" value="<?= htmlspecialchars($client['telephone'] ?? '') ?>" class="form-control" placeholder="+33600000000"
|
||||
<label class="form-label"><?php echo $translations['label_telephone'] ?> *</label>
|
||||
<input type="tel" name="telephone" id="telephone" value="<?php echo htmlspecialchars($client['telephone'] ?? '') ?>" class="form-control" placeholder="+33600000000"
|
||||
required
|
||||
pattern="^\+?[1-9]\d{6,14}$"
|
||||
title="<?=htmlspecialchars($translations['telephone_format_message']?? '') ?>">
|
||||
title="<?php echo htmlspecialchars($translations['telephone_format_message'] ?? '') ?>">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><?= $translations['label_genre'] ?></label>
|
||||
<label class="form-label"><?php echo $translations['label_genre'] ?></label>
|
||||
<select name="genre" class="form-select">
|
||||
<option value=""><?= $translations['select_selectionnez'] ?></option>
|
||||
<option value="<?= $translations['genre_homme'] ?>" <?= $client['genre'] === $translations['genre_homme'] ? 'selected' : '' ?>><?= $translations['genre_homme'] ?></option>
|
||||
<option value="<?= $translations['genre_femme'] ?>" <?= $client['genre'] === $translations['genre_femme'] ? 'selected' : '' ?>><?= $translations['genre_femme'] ?></option>
|
||||
<option value="<?= $translations['genre_autre'] ?>" <?= $client['genre'] === $translations['genre_autre'] ? 'selected' : '' ?>><?= $translations['genre_autre'] ?></option>
|
||||
<option value=""><?php echo $translations['select_selectionnez'] ?></option>
|
||||
<option value="<?php echo $translations['genre_homme'] ?>" <?php echo $client['genre'] === $translations['genre_homme'] ? 'selected' : '' ?>><?php echo $translations['genre_homme'] ?></option>
|
||||
<option value="<?php echo $translations['genre_femme'] ?>" <?php echo $client['genre'] === $translations['genre_femme'] ? 'selected' : '' ?>><?php echo $translations['genre_femme'] ?></option>
|
||||
<option value="<?php echo $translations['genre_autre'] ?>" <?php echo $client['genre'] === $translations['genre_autre'] ? 'selected' : '' ?>><?php echo $translations['genre_autre'] ?></option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><?= $translations['label_date_naissance'] ?></label>
|
||||
<input type="date" name="date_naissance" value="<?= htmlspecialchars($client['date_naissance'] ?? '') ?>" class="form-control">
|
||||
<label class="form-label"><?php echo $translations['label_date_naissance'] ?></label>
|
||||
<input type="date" name="date_naissance" value="<?php echo htmlspecialchars($client['date_naissance'] ?? '') ?>" class="form-control">
|
||||
</div>
|
||||
|
||||
<div class="mb-3 input-wrapper">
|
||||
<label class="form-label"><?= $translations['label_adresse'] ?></label>
|
||||
<textarea name="adresse" id="adresse" class="form-control"><?= htmlspecialchars($client['adresse'] ?? '') ?></textarea>
|
||||
<label class="form-label"><?php echo $translations['label_adresse'] ?></label>
|
||||
<textarea name="adresse" id="adresse" class="form-control"><?php echo htmlspecialchars($client['adresse'] ?? '') ?></textarea>
|
||||
<ul id="suggestions" class="list-group position-absolute"></ul>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><?= $translations['label_ville'] ?></label>
|
||||
<input type="text" name="ville" id="ville" value="<?= htmlspecialchars($client['ville'] ?? '') ?>" class="form-control">
|
||||
<label class="form-label"><?php echo $translations['label_ville'] ?></label>
|
||||
<input type="text" name="ville" id="ville" value="<?php echo htmlspecialchars($client['ville'] ?? '') ?>" class="form-control">
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="form-label"><?= $translations['label_code_postal'] ?></label>
|
||||
<input type="text" name="code_postal" id="code_postal" value="<?= htmlspecialchars($client['code_postal'] ?? '') ?>" class="form-control">
|
||||
<label class="form-label"><?php echo $translations['label_code_postal'] ?></label>
|
||||
<input type="text" name="code_postal" id="code_postal" value="<?php echo htmlspecialchars($client['code_postal'] ?? '') ?>" class="form-control">
|
||||
</div>
|
||||
|
||||
<div class="text-center">
|
||||
<button type="submit" class="btn btn-primary"><?= $translations['btn_mettre_a_jour'] ?></button>
|
||||
<button type="submit" class="btn btn-primary"><?php echo $translations['btn_mettre_a_jour'] ?></button>
|
||||
</div>
|
||||
<?= $translations['champs_obligatoire'] ?>
|
||||
<?php echo $translations['champs_obligatoire'] ?>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -217,7 +219,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
</div>
|
||||
<footer class="site-footer">
|
||||
<div class="container">
|
||||
<p><?= $translations['copyr'] ?></p>
|
||||
<p><?php echo $translations['copyr'] ?></p>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
|
||||
@@ -1,15 +1,59 @@
|
||||
<?php
|
||||
declare (strict_types = 1);
|
||||
|
||||
require_once __DIR__ . '/../includes/init_client.php';
|
||||
|
||||
$client_id = $_SESSION['reference_id'];
|
||||
$praticien_id = intval($_POST['praticien_id'] ?? 0);
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
if ($praticien_id > 0) {
|
||||
|
||||
$stmt = $pdo->prepare("DELETE FROM praticien_client WHERE client_id = ? AND praticien_id = ?");
|
||||
if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
|
||||
echo json_encode([
|
||||
'ok' => false,
|
||||
'message' => 'Méthode non autorisée.',
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
require_post_csrf();
|
||||
|
||||
$client_id = (int) ($_SESSION['reference_id'] ?? 0);
|
||||
$praticien_id = (int) ($_POST['praticien_id'] ?? 0);
|
||||
|
||||
if ($client_id <= 0) {
|
||||
echo json_encode([
|
||||
'ok' => false,
|
||||
'message' => $translations['erreur_generique'] ?? 'Erreur.',
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($praticien_id <= 0) {
|
||||
echo json_encode([
|
||||
'ok' => false,
|
||||
'message' => $translations['praticien_id_invalide'] ?? 'ID praticien invalide.',
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$stmt = $pdo->prepare("
|
||||
DELETE FROM praticien_client
|
||||
WHERE client_id = ? AND praticien_id = ?
|
||||
");
|
||||
$stmt->execute([$client_id, $praticien_id]);
|
||||
echo $translations['favori_supprime_title'];
|
||||
} else {
|
||||
echo $translations['praticien_id_invalide'];
|
||||
|
||||
echo json_encode([
|
||||
'ok' => true,
|
||||
'deleted' => ($stmt->rowCount() > 0),
|
||||
'message' => $translations['favori_supprime_title'] ?? 'Favori supprimé.',
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
|
||||
} catch (Throwable $e) {
|
||||
error_log('supprimer_favori.php: ' . $e->getMessage());
|
||||
|
||||
echo json_encode([
|
||||
'ok' => false,
|
||||
'message' => $translations['erreur_generique'] ?? 'Erreur serveur.',
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
Reference in new issue
Block a user