'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]; } function fullName(array $p): string { return trim(($p['prenom'] ?? '').' '.($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 = []; /*==========================BLOCK WHERE AVEC ABONNEMENT VALIDE ==========================*/ $whereSql[] = "(date_expiration IS NOT NULL AND date_expiration >= DATE_SUB(NOW(), INTERVAL 14 DAY))"; /*=======================================================================================*/ 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[] = "( LOWER(CONCAT(COALESCE(prenom,''),' ',COALESCE(nom,''))) LIKE $k1 OR LOWER(COALESCE(specialite,'')) LIKE $k2 )"; $params[$k1] = '%'.$word.'%'; $params[$k2] = '%'.$word.'%'; } $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)); $ors = []; // 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(code_postal,'') LIKE :wcp"; $params[':wcp'] = '%' . $w . '%'; $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 $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); /* ========================= Rendu partiel AJAX (#results) ========================= */ function render_results(array $praticiens, array $translations, bool $isSearch, int $page, int $totalPages): void { ?>

<?= htmlspecialchars($translations['alt_photo'] ?? 'Photo du praticien', ENT_QUOTES, 'UTF-8') ?>
1): ?> <?= htmlspecialchars($translations['menu_liste_specialistes'] ?? 'Liste des praticiens', ENT_QUOTES, 'UTF-8') ?>