[ 'avocats' => 'Адвокаты', 'consultants' => 'Консультанты', 'vrachi' => 'Врачи', 'psychologues' => 'Психологи', 'repetitori' => 'Преподаватели', 'finance' => 'Бухгалтеры', 'perevod' => 'Переводчики', 'notarius' => 'Нотариусы', 'gid' => 'Гиды', 'rieltori' => 'Риелторы', 'drugie' => 'Специалисты', ], 'fr' => [ 'avocats' => 'Avocats', 'consultants' => 'Consultants', 'vrachi' => 'Médecins', 'psychologues' => 'Psychologues', 'repetitori' => 'Professeurs', 'finance' => 'Comptables', 'perevod' => 'Traducteurs', 'notarius' => 'Notaires', 'gid' => 'Guides', 'rieltori' => 'Agents immobiliers', 'drugie' => 'Spécialistes', ], ]; // Helper detection video 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 (strpos(strtolower($u), 'vimeo.com') !== false) { return ['provider' => 'vimeo', 'id' => null, 'thumb' => null]; } return ['provider' => 'other', 'id' => null, 'thumb' => null]; } // inverse map: canon_value -> key $canonToKey = []; foreach (($SPEC_CANON[$lang] ?? []) as $k => $v) { $canonToKey[mb_strtolower($v, 'UTF-8')] = $k; } $inFrance = ($lang === 'fr') ? ' en France' : ' во Франции'; $h1Default = $translations['res_poisk'] ?? 'Результаты поиска'; $h1 = $h1Default; // Normalisation affichage terme $sp = trim(str_replace(["\xC2\xA0"], ' ', $specialiteParam)); $sp = preg_replace('/\s+/u', ' ', $sp); $sp_l = mb_strtolower($sp, 'UTF-8'); if ($specialiteParam === '__OTHERS__') { $label = $H1_LABELS[$lang]['drugie'] ?? $h1Default; $h1 = $label . $inFrance; } elseif (! empty($resolvedSeoSpecSlug)) { $slugToKey = [ 'avocat' => 'avocats', 'consultant' => 'consultants', 'medecin' => 'vrachi', 'psychologue' => 'psychologues', 'professeur' => 'repetitori', 'comptable' => 'finance', 'traducteur' => 'perevod', 'notaire' => 'notarius', 'guide' => 'gid', 'agent-immobilier' => 'rieltori', 'autres-specialistes' => 'drugie', ]; $h1Key = $slugToKey[$resolvedSeoSpecSlug] ?? null; if ($h1Key !== null) { $label = $H1_LABELS[$lang][$h1Key] ?? $h1Default; $h1 = $label . $inFrance; } } elseif ($sp !== '' && isset($canonToKey[$sp_l])) { $key = $canonToKey[$sp_l]; $label = $H1_LABELS[$lang][$key] ?? ($translations[$key] ?? $sp); $h1 = $label . $inFrance; } // Cas spécial : autres spécialistes $onlyOthers = false; $specialite = $specialiteParam; if ($specialiteParam === '__OTHERS__') { $onlyOthers = true; $specialite = ''; } // Pagination $perPage = 12; $page = max(1, (int) ($_GET['page'] ?? 1)); $offset = ($page - 1) * $perPage; // Construction SQL $where = []; $params = []; /*========================== BLOCK WHERE AVEC ABONNEMENT VALIDE ==========================*/ $where[] = "(p.date_expiration IS NOT NULL AND p.date_expiration >= DATE_SUB(NOW(), INTERVAL 14 DAY))"; /*========================================================================================*/ // Le champ "specialite" peut contenir une spécialité OU un nom/prénom $term = trim((string) $specialite); $term_l = mb_strtolower($term, 'UTF-8'); // 1) Cas "Другие" if ($onlyOthers) { $mainSpecs = array_values($canon); if ($mainSpecs) { $ors = []; foreach ($mainSpecs as $i => $spc) { $key = ":cat{$i}"; $ors[] = "LOWER(p.specialite) LIKE $key"; $params[$key] = mb_strtolower($spc, 'UTF-8') . '%'; } $where[] = '(p.specialite IS NULL OR p.specialite = "" OR NOT (' . implode(' OR ', $ors) . '))'; } } // 2) Cas normal elseif ($term !== '') { $where[] = "(" . "LOWER(COALESCE(pt.specialite, p.specialite)) LIKE :sp_like " . "OR LOWER(COALESCE(pt.nom_affiche, p.nom)) LIKE :nm_prefix1 " . "OR LOWER(COALESCE(pt.prenom_affiche, p.prenom)) LIKE :nm_prefix2 " . "OR LOWER(CONCAT(COALESCE(pt.prenom_affiche, p.prenom),' ',COALESCE(pt.nom_affiche, p.nom))) LIKE :nm_like_full1 " . "OR LOWER(CONCAT(COALESCE(pt.nom_affiche, p.nom),' ',COALESCE(pt.prenom_affiche, p.prenom))) LIKE :nm_like_full2 " . ")"; $params[':sp_like'] = '%' . $term_l . '%'; $params[':nm_prefix1'] = $term_l . '%'; $params[':nm_prefix2'] = $term_l . '%'; $params[':nm_like_full1'] = '%' . $term_l . '%'; $params[':nm_like_full2'] = '%' . $term_l . '%'; } // Filtre ville — support latin + cyrillique if ($ville !== '') { $villeCands = city_query_candidates($ville); $villeCands[] = mb_strtolower($ville, 'UTF-8'); $villeCands = array_values(array_unique($villeCands)); $villeOrs = []; foreach ($villeCands as $i => $cand) { $key = ":ville{$i}"; $params[$key] = $cand . '%'; $villeOrs[] = "LOWER(COALESCE(pt.ville_affichee, p.ville)) LIKE {$key}"; } if ($villeOrs) { $where[] = '(' . implode(' OR ', $villeOrs) . ')'; } } $whereSql = $where ? ('WHERE ' . implode(' AND ', $where)) : ''; // total $sqlCount = " SELECT COUNT(*) FROM praticien p LEFT JOIN praticien_traduction pt ON pt.praticien_id = p.id AND pt.langue = :lang $whereSql "; $stmt = $pdo->prepare($sqlCount); $paramsCount = $params; $paramsCount[':lang'] = $currentLang; $stmt->execute($paramsCount); $total = (int) $stmt->fetchColumn(); $pages = max(1, (int) ceil($total / $perPage)); // data $sql = " SELECT p.id, p.nom, p.prenom, p.ville, p.photo, 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 $whereSql "; if ($term !== '') { $sql .= " ORDER BY (LOWER(CONCAT(COALESCE(pt.prenom_affiche, p.prenom),' ',COALESCE(pt.nom_affiche, p.nom))) = :nm_exact1 OR LOWER(CONCAT(COALESCE(pt.nom_affiche, p.nom),' ',COALESCE(pt.prenom_affiche, p.prenom))) = :nm_exact2) DESC, (LOWER(COALESCE(pt.nom_affiche, p.nom)) = :nm_exact3 OR LOWER(COALESCE(pt.prenom_affiche, p.prenom)) = :nm_exact4) DESC, (LOWER(COALESCE(pt.nom_affiche, p.nom)) LIKE :nm_prefix3 OR LOWER(COALESCE(pt.prenom_affiche, p.prenom)) LIKE :nm_prefix4) DESC, (LOWER(COALESCE(pt.specialite, p.specialite)) LIKE :sp_prefix1) DESC, COALESCE(pt.nom_affiche, p.nom) ASC, COALESCE(pt.prenom_affiche, p.prenom) ASC"; $params[':nm_exact1'] = $term_l; $params[':nm_exact2'] = $term_l; $params[':nm_exact3'] = $term_l; $params[':nm_exact4'] = $term_l; $params[':nm_prefix3'] = $term_l . '%'; $params[':nm_prefix4'] = $term_l . '%'; $params[':sp_prefix1'] = $term_l . '%'; } else { $sql .= " ORDER BY p.updated_at DESC, p.id DESC"; } $sql .= " LIMIT :offset, :limit"; $stmt = $pdo->prepare($sql); $params[':lang'] = $currentLang; foreach ($params as $k => $v) { $stmt->bindValue($k, $v, PDO::PARAM_STR); } $stmt->bindValue(':offset', (int) $offset, PDO::PARAM_INT); $stmt->bindValue(':limit', (int) $perPage, PDO::PARAM_INT); $stmt->execute(); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); // Si ce n'est pas une catégorie canonique et qu'on a des résultats if ( $h1 === $h1Default && $specialiteParam !== '__OTHERS__' && $sp !== '' && empty($resolvedSeoSpecSlug) && ! isset($canonToKey[$sp_l]) ) { if (! empty($rows)) { $h1 = ''; } } // util function h($s): string { return htmlspecialchars((string) $s, ENT_QUOTES, 'UTF-8'); } function excerpt($text, $len = 160): string { $t = trim(strip_tags((string) $text)); if (mb_strlen($t, 'UTF-8') <= $len) { return $t; } return mb_substr($t, 0, $len, 'UTF-8') . '…'; } // pour liens de pagination $qParams = []; if ($onlyOthers) { $qParams['specialite'] = '__OTHERS__'; } elseif ($specialite !== '') { $qParams['specialite'] = $specialite; } if ($ville !== '') { $qParams['ville'] = $ville; } $baseQuery = http_build_query($qParams); // ========================= // SEO dynamique // ========================= $seoSpecialite = trim((string) $specialiteParam); $seoVille = trim((string) $ville); $isCanonCategory = ($seoSpecialite !== '' && isset($canonToKey[mb_strtolower($seoSpecialite, 'UTF-8')])); $isOthersCategory = ($seoSpecialite === '__OTHERS__'); $seoDisplaySpecialite = $seoSpecialite; if ($isOthersCategory) { $seoDisplaySpecialite = $translations['drugie'] ?? 'Spécialistes'; } if ($seoDisplaySpecialite === '') { $seoTitle = $translations['boostseo_search_title_default'] ?? ''; $seoDesc = $translations['boostseo_search_desc_default'] ?? ''; $seoIntro = $translations['boostseo_search_intro_default'] ?? ''; } elseif ($seoVille !== '') { $seoTitle = strtr(($translations['boostseo_search_title_spec_city'] ?? ''), [ '%specialite%' => $seoDisplaySpecialite, '%ville%' => $seoVille, ]); $seoDesc = strtr(($translations['boostseo_search_desc_spec_city'] ?? ''), [ '%specialite%' => $seoDisplaySpecialite, '%ville%' => $seoVille, ]); $seoIntro = strtr(($translations['boostseo_search_intro_spec_city'] ?? ''), [ '%specialite%' => $seoDisplaySpecialite, '%ville%' => $seoVille, ]); } else { $seoTitle = strtr(($translations['boostseo_search_title_spec'] ?? ''), [ '%specialite%' => $seoDisplaySpecialite, ]); $seoDesc = strtr(($translations['boostseo_search_desc_spec'] ?? ''), [ '%specialite%' => $seoDisplaySpecialite, ]); $seoIntro = strtr(($translations['boostseo_search_intro_spec'] ?? ''), [ '%specialite%' => $seoDisplaySpecialite, ]); } $canonicalUrl = 'https://uslugi.fr/recherche'; $canonicalSpecSlug = ''; if ($specialiteParam === '__OTHERS__') { $canonicalSpecSlug = 'autres-specialistes'; } elseif ($specialiteParam !== '') { foreach (seo_spec_map() as $slug => $cfg) { if (($cfg['db'] ?? '') === $specialiteParam) { $canonicalSpecSlug = $slug; break; } } } $cityCanonicalSlug = ''; if ($ville !== '') { $cityCanonicalSlug = seo_city_slug_from_input($ville); } if ($canonicalSpecSlug !== '') { $canonicalUrl .= '/' . rawurlencode($canonicalSpecSlug); if ($cityCanonicalSlug !== '') { $canonicalUrl .= '/' . rawurlencode($cityCanonicalSlug); } } elseif ($cityCanonicalSlug !== '') { $canonicalUrl .= '/ville/' . rawurlencode($cityCanonicalSlug); } $canonicalUrl .= '/'; if ($page > 1) { $canonicalUrl .= '?page=' . $page; } ?>