Version stable, aprelic, connexion CFRS et autres corrections

This commit is contained in:
Oleg Barykin committed 2026-04-17 20:18:11 +02:00
1 parent 865de8a976
commit e9d0010451
62 files changed
+17483 -7256

No files matched your search

+336 -140
View File
@@ -1,87 +1,272 @@
<?php
require_once __DIR__ . '/../includes/bootstrap_global.php';
require_once APP_HTDOCS . '/includes/avatar_initiales.php';
$SPEC_CANON = require __DIR__ . '/includes/spec_map.php';
declare (strict_types = 1);
$canon = $SPEC_CANON[$lang] ?? [];
require_once __DIR__ . '/../includes/bootstrap_global.php';
require_once APP_HTDOCS . '/includes/avatar_initiales.php';
require_once __DIR__ . '/includes/search_helpers.php';
$SPEC_CANON = require __DIR__ . '/includes/spec_map.php';
$canon = $SPEC_CANON[$lang] ?? [];
$currentLang = $lang ?? 'ru';
if (! in_array($currentLang, ['ru', 'fr', 'en'], true)) {
$currentLang = 'ru';
}
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;
}
// ------- (ton code existant) -------
$praticien_id = (int)($_GET['id'] ?? 0);
if ($praticien_id <= 0) { echo "<div class='container py-5'>ID invalide.</div>"; exit; }
$u = trim($url);
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];
}
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"];
}
// 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; }
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"];
}
$name = $name = trim($p['prenom'] . ' ' . $p['nom']);
$photo = !empty($p['photo']) ? "/public/uploads/".ltrim($p['photo'],'/') : "/public/uploads/img/user_placeholder.png";
$path = parse_url($u, PHP_URL_PATH) ?? '';
if (preg_match('~\.(mp4|webm|ogg)$~i', $path)) {
return ['provider' => 'file', 'id' => null, 'thumb' => null];
}
// 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);
if (stripos($u, 'vimeo.com') !== false) {
return ['provider' => 'vimeo', 'id' => null, 'thumb' => null];
}
// 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];
return ['provider' => 'other', 'id' => null, 'thumb' => null];
}
$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);
// ID praticien
$praticien_id = (int) ($_GET['id'] ?? 0);
if ($praticien_id <= 0) {
echo "<div class='container py-5'>ID invalide.</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'] ?? 'Praticien introuvable') . "</div>";
exit;
}
$name = trim(($p['prenom_affiche'] ?? $p['prenom'] ?? '') . ' ' . ($p['nom_affiche'] ?? $p['nom'] ?? ''));
$photo = ! empty($p['photo'])
? "/public/uploads/" . ltrim((string) $p['photo'], '/')
: "/public/uploads/img/user_placeholder.png";
// Slug spécialité SEO depuis la valeur BDD
$specSlug = '';
if (! empty($p['specialite'])) {
$specDb = mb_strtolower(trim((string) $p['specialite']), 'UTF-8');
// 1) correspondance exacte
foreach (seo_spec_map() as $slug => $cfg) {
$dbVal = mb_strtolower(trim((string) ($cfg['db'] ?? '')), 'UTF-8');
if ($dbVal !== '' && $dbVal === $specDb) {
$specSlug = $slug;
break;
}
}
// 2) commence par
if ($specSlug === '') {
foreach (seo_spec_map() as $slug => $cfg) {
$dbVal = mb_strtolower(trim((string) ($cfg['db'] ?? '')), 'UTF-8');
if ($dbVal !== '' && mb_stripos($specDb, $dbVal, 0, 'UTF-8') === 0) {
$specSlug = $slug;
break;
}
}
}
// 3) contient
if ($specSlug === '') {
foreach (seo_spec_map() as $slug => $cfg) {
$dbVal = mb_strtolower(trim((string) ($cfg['db'] ?? '')), 'UTF-8');
if ($dbVal !== '' && mb_stripos($specDb, $dbVal, 0, 'UTF-8') !== false) {
$specSlug = $slug;
break;
}
}
}
// 4) fallback propre : slug du texte libre
if ($specSlug === '') {
$specSlug = seo_slug((string) $p['specialite']);
}
}
if ($specSlug === '') {
$specSlug = 'specialiste';
}
// Slug ville SEO
$citySlug = '';
if (! empty($p['ville'])) {
$citySlug = seo_city_slug_from_input((string) $p['ville']);
}
if ($citySlug === '') {
$citySlug = 'france';
}
// Slug nom
$nameSlug = seo_slug($name);
if ($nameSlug === '') {
$nameSlug = 'profil';
}
// URL SEO finale
$prettyPath = '/specialistes/' . rawurlencode($specSlug) . '/' . rawurlencode($citySlug) . '/' . rawurlencode($nameSlug . '-' . (int) $p['id']) . '/';
$canonicalUrl = 'https://uslugi.fr' . $prettyPath;
// Redirection 301 depuis ancienne URL
$currentPath = parse_url($_SERVER['REQUEST_URI'] ?? '', PHP_URL_PATH) ?? '';
if (
($currentPath === '/detail_public.php' || $currentPath === '/specialistes')
&& (int) ($_GET['id'] ?? 0) > 0
) {
$target = $prettyPath;
$extra = $_GET;
unset($extra['id'], $extra['slug']);
if (! empty($extra)) {
$target .= '?' . http_build_query($extra);
}
if (($_SERVER['REQUEST_URI'] ?? '') !== $target) {
header('Location: ' . $target, true, 301);
exit;
}
}
// Variables SEO
$seoTitle = $name;
if (! empty($p['specialite_affichee'])) {
$seoTitle .= ' — ' . $p['specialite_affichee'];
}
if (! empty($p['ville_affichee'])) {
$seoTitle .= ' à ' . $p['ville_affichee'];
}
$seoTitle .= ' | Uslugi.fr';
$presentationShort = trim(strip_tags((string) ($p['presentation_affichee'] ?? $p['presentation'] ?? '')));
if ($presentationShort !== '') {
$presentationShort = mb_substr($presentationShort, 0, 155, 'UTF-8');
}
$seoDesc = $presentationShort !== ''
? $presentationShort
: (
$name
. (! empty($p['specialite_affichee']) ? ' — ' . $p['specialite_affichee'] : '')
. (! empty($p['ville_affichee']) ? ' à ' . $p['ville_affichee'] : '')
. '.'
);
// 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];
$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);
?>
<!doctype html>
<html lang="ru">
<html lang="<?php echo h($currentLang) ?>">
<head>
<meta charset="utf-8">
<title><?= $name . " - " . $p['specialite'] . " - " . htmlspecialchars($translations['seo_detail_public_title'], ENT_QUOTES, 'UTF-8')?></title>
<meta name="description" content="<?= $name . " - " . $p['specialite'] . " - " . $p['presentation'] ?>">
<meta name="keywords" content="<?= htmlspecialchars($translations['seo_detail_public_keywords'], ENT_QUOTES, 'UTF-8') ?>">
<meta name="author" content="Uslugi.fr">
<title><?php echo h($seoTitle) ?></title>
<meta name="description" content="<?php echo h($seoDesc) ?>">
<link rel="canonical" href="<?php echo h($canonicalUrl) ?>">
<meta name="robots" content="index, follow">
<meta property="og:type" content="profile">
<meta property="og:url" content="<?php echo h($canonicalUrl) ?>">
<meta property="og:title" content="<?php echo h($seoTitle) ?>">
<meta property="og:description" content="<?php echo h($seoDesc) ?>">
<meta property="og:image" content="<?php echo h('https://uslugi.fr' . $photo) ?>">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link href="/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="<?= asset('/includes/site_style.css') ?>">
<link rel="stylesheet" href="<?= asset('/includes/custom_style.css') ?>">
<link rel="stylesheet" href="<?= asset('/includes/mobil_style.css') ?>">
<link rel="stylesheet" href="<?php echo asset('/includes/site_style.css') ?>">
<link rel="stylesheet" href="<?php echo asset('/includes/custom_style.css') ?>">
<link rel="stylesheet" href="<?php echo asset('/includes/mobil_style.css') ?>">
<?php include_once __DIR__ . '/includes/tracking.php'; ?>
</head>
<body class="layout">
<?php if ($logged):
include_header_clean(APP_HTDOCS . '/public/includes/header.php');
include_header_clean(APP_HTDOCS . '/public/includes/header.php');
else: ?>
<!-- Header public (Connexion + “?”) -->
<header class="site-header">
@@ -102,20 +287,20 @@ else: ?>
</div>
<nav class="header-actions">
<a class="btn-login" href="/connexion.php">
<span class="d-sm-none"><?php echo $translations['btn_se_connecter']?></span>
<span class="d-none d-sm-inline"><?php echo $translations['ja_spec']?></span>
<span class="d-sm-none"><?php echo $translations['btn_se_connecter'] ?></span>
<span class="d-none d-sm-inline"><?php echo $translations['ja_spec'] ?></span>
</a>
<!--<a class="btn-login" href="/connexion.php"><?php echo $translations['ja_spec']?></a>-->
<!--<a class="btn-login" href="/connexion.php"><?php echo $translations['ja_spec'] ?></a>-->
<div class="dropdown">
<button class="btn btn-logo rounded-circle d-inline-flex align-items-center justify-content-center question-btn"
type="button" data-bs-toggle="dropdown" data-bs-auto-close="true"
aria-expanded="false" aria-label="Помощь">?</button>
<ul class="dropdown-menu dropdown-menu-end shadow help-menu menu-panel">
<li><a href="/how-it-work.php"><?= $translations['kak_rabotaet'] ?></a></a></li>
<li><a href="/about.php"><?= $translations['pro_nas'] ?></a></li>
<li><a href="/faq.php"><?= $translations['faq'] ?></a></li>
<li><a href="/contact.php"><?= $translations['nam_pisat'] ?></a></li>
<li><a href="/how-it-work.php"><?php echo $translations['kak_rabotaet'] ?></a></a></li>
<li><a href="/about.php"><?php echo $translations['pro_nas'] ?></a></li>
<li><a href="/faq.php"><?php echo $translations['faq'] ?></a></li>
<li><a href="/contact.php"><?php echo $translations['nam_pisat'] ?></a></li>
</ul>
</div>
</nav>
@@ -130,75 +315,84 @@ else: ?>
<!-- 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">
</section>
<img class="img-fluid w-100"
src="<?php echo h($photo) ?>"
alt="<?php
echo h(
trim($name)
. (! empty($p['specialite_affichee']) ? ' — ' . $p['specialite_affichee'] : '')
. (! empty($p['ville_affichee']) ? ' à ' . $p['ville_affichee'] : '')
. ' — Uslugi.fr'
);
?>">
</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>
@@ -207,14 +401,16 @@ else: ?>
<!-- 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>
@@ -222,31 +418,31 @@ else: ?>
<!-- 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="/avis_public.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="/avis_public.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>
@@ -259,8 +455,8 @@ else: ?>
<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">
@@ -272,7 +468,7 @@ else: ?>
</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>
@@ -309,7 +505,7 @@ else: ?>
<footer class="site-footer">
<div class="container">
<p><?= $translations['copyr'] ?></p>
<p><?php echo $translations['copyr'] ?></p>
</div>
</footer>
</body>