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

+42 -230
View File
@@ -3,6 +3,8 @@
require_once __DIR__ . '/../includes/bootstrap_global.php';
require_once APP_HTDOCS . '/includes/avatar_initiales.php';
require_once __DIR__ . '/includes/search_helpers.php';
require_once __DIR__ . '/traitement_form_index.php';
$SPEC_CANON = require __DIR__ . '/includes/spec_map.php';
$canon = $SPEC_CANON[$lang] ?? [];
@@ -238,35 +240,48 @@ else: ?>
</div>
<br>
<h1 class="hero-title"><?php echo($translations['titre_home'] ?? '') ?></h1>
<h1 class="hero-title">
<?php echo($translations['titre_home'] ?? '') ?>
<span class="visually-hidden">
<?php echo h($translations['boostseo_home_h1_suffix'] ?? '') ?>
</span>
</h1>
<p class="visually-hidden">
<?php echo h($translations['boostseo_home_intro'] ?? '') ?>
</p>
<center><h5><?php echo($translations['sous_titre_home'] ?? '') ?></h5></center>
<br>
<?php
// Chips -> liens (SEO + comme image)
$chipKeysRow1 = ['avocats', 'notarius', 'rieltori'];
$chipKeysRow2 = ['vrachi', 'psychologues', 'consultants'];
$chipKeysRow3 = ['perevod', 'repetitori', 'drugie'];
$renderChipLinks = function (array $keys) use ($translations, $canon) {
$renderChipLinks = function (array $keys) use ($translations) {
foreach ($keys as $k) {
$label = $translations[$k] ?? $k;
$query = ($k === 'drugie') ? '__OTHERS__' : ($canon[$k] ?? $label);
$slug = seo_spec_slug_from_key($k);
$href = '/result_search.php?' . http_build_query([
'specialite' => $query,
'ville' => '',
]);
$href = '/recherche/';
if ($slug !== null && $slug !== '') {
$href .= rawurlencode($slug) . '/';
}
echo '<a class="chip chip-link" href="' . h($href) . '">' . h($label) . '</a>';
}
};
?>
<!--<h2 class="home-h2"><?php echo h($translations['home_popular_specialists'] ?? '') ?></h2>-->
<h2 class="visually-hidden">
<?php echo h($translations['boostseo_home_h2_categories'] ?? '') ?>
</h2>
<div class="row"><?php $renderChipLinks($chipKeysRow1); ?></div>
<div class="row"><?php $renderChipLinks($chipKeysRow2); ?></div>
<div class="row"><?php $renderChipLinks($chipKeysRow3); ?></div>
<br>
<h2 class="visually-hidden">
<?php echo h($translations['boostseo_home_h2_recent'] ?? '') ?>
</h2>
<div class="hp-list">
<?php
try {
@@ -420,24 +435,31 @@ else: ?>
?>
</div>
<br><br>
<form class="searchbar" action="result_search.php" method="get" id="searchForm">
<form class="searchbar" action="/recherche" method="get" id="searchForm">
<div class="search-icon">🔍</div>
<div class="input-wrap">
<input type="text" id="specialite" name="specialite" autocomplete="off"
placeholder="<?php echo h($translations['n_special'] ?? '') ?>" aria-label="Специальность">
<input type="text"
id="specialite"
name="specialite"
autocomplete="off"
placeholder="<?php echo h($translations['n_special'] ?? '') ?>"
aria-label="Специальность">
<ul class="suggest" id="sg-specialite" hidden></ul>
</div>
<div class="divider"></div>
<div class="input-wrap">
<input type="text" id="ville" name="ville" autocomplete="off"
placeholder="<?php echo h($translations['gde'] ?? '') ?>" aria-label="Город">
<input type="text"
id="ville"
name="ville"
autocomplete="off"
placeholder="<?php echo h($translations['gde'] ?? '') ?>"
aria-label="Город">
<ul class="suggest" id="sg-ville" hidden></ul>
</div>
<!-- ✅ bouton inside -->
<button class="hp-btn btn-find--inbar" type="submit">
<?php echo h($translations['naiti'] ?? '') ?>
</button>
@@ -445,7 +467,7 @@ else: ?>
<div class="spec-cta-wrap">
<h3><?php echo h($translations['choix_pro_titre_home'] ?? '') ?></h3>
<p><!--<?php echo h($translations['choix_pro_texte_court'] ?? '') ?>--></p>
<a class="spec-cta" href="/inscription_praticien.php">
<a class="spec-cta" href="/registration.php">
<span class="spec-cta__label"><?php echo h($translations['titre_spec'] ?? '') ?></span>
</a>
</div>
@@ -457,222 +479,9 @@ else: ?>
</div>-->
<br>
<!-- Articles -->
<!--<h2 class="home-h2"><?php echo h($translations['home_useful_articles'] ?? '') ?></h2>-->
<?php
function fetch_url(string $url, int $timeout = 4): array
{
// Retourne [ok(bool), body(string), err(string), http(int)]
if (! function_exists('curl_init')) {
return [false, '', 'cURL non disponible', 0];
}
<?php include __DIR__ . '/form_index.php'; ?>
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_CONNECTTIMEOUT => $timeout,
CURLOPT_TIMEOUT => $timeout,
CURLOPT_USERAGENT => 'uslugi.fr (+https://staging.uslugi.fr)',
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
]);
$body = (string) curl_exec($ch);
$err = curl_error($ch);
$http = (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
$ok = ($err === '' && $http >= 200 && $http < 300 && $body !== '');
return [$ok, $body, $err, $http];
}
function decode_entities(string $s): string
{
// ENT_HTML5 gère correctement les entités modernes
return html_entity_decode($s, ENT_QUOTES | ENT_HTML5, 'UTF-8');
}
function fetch_sovetnik_articles(int $limit = 4, bool $debug = false): array
{
$cacheFile = APP_HTDOCS . '/cache/sovetnik_feed.json';
$lockFile = APP_HTDOCS . '/cache/sovetnik_feed.lock';
// Soft TTL: on essaye de rafraîchir après 15 min
// Hard TTL: on accepte d'afficher un cache jusqu'à 24h (pour ne jamais bloquer la page)
$ttlSoft = 15 * 60;
$ttlHard = 24 * 60 * 60;
$now = time();
$hasCache = is_file($cacheFile);
$age = $hasCache ? ($now - filemtime($cacheFile)) : PHP_INT_MAX;
// Helper pour lire cache
$readCache = function () use ($cacheFile, $limit): array {
$data = json_decode((string) @file_get_contents($cacheFile), true);
return (is_array($data) && $data) ? array_slice($data, 0, $limit) : [];
};
// 1) Cache frais => retour immédiat (rapide)
if ($hasCache && $age < $ttlSoft) {
return $readCache();
}
// 2) Cache expiré mais pas trop vieux => on le garde en "stale" (fallback instantané)
$stale = [];
if ($hasCache && $age < $ttlHard) {
$stale = $readCache();
}
// 3) On tente un refresh, MAIS sans bloquer longtemps
// + lock pour éviter que plusieurs requêtes refreshent en même temps
$canRefresh = true;
if (is_file($lockFile)) {
// lock encore récent => on ne refresh pas
if (($now - filemtime($lockFile)) < 60) {
$canRefresh = false;
}
}
if (! $canRefresh) {
// On sert le stale cache (ou vide)
return $stale;
}
// On pose le lock (best-effort)
@is_dir(dirname($lockFile)) || @mkdir(dirname($lockFile), 0755, true);
@file_put_contents($lockFile, (string) $now);
// Timeout court = clé de la perf
$timeout = 1;
// 1) RSS
$feedCandidates = [
'https://www.sovetnik.fr/feed/',
'https://www.sovetnik.fr/?feed=rss2',
'https://www.sovetnik.fr/feed/rss2/',
];
foreach ($feedCandidates as $feedUrl) {
[$ok, $xmlStr, $err, $http] = fetch_url($feedUrl, $timeout);
if ($debug) {
echo "\n<!-- Sovetnik RSS try: $feedUrl | ok=" . ($ok ? '1' : '0') . " | http=$http | err=" . htmlspecialchars($err) . " -->\n";
}
if (! $ok) {
continue;
}
libxml_use_internal_errors(true);
$xml = simplexml_load_string($xmlStr);
if (! $xml || empty($xml->channel->item)) {
continue;
}
$out = [];
foreach ($xml->channel->item as $item) {
$titleRaw = (string) $item->title;
$descRaw = (string) $item->description;
$out[] = [
'url' => (string) $item->link,
'title' => trim(decode_entities(strip_tags($titleRaw))),
'desc' => trim(decode_entities(strip_tags($descRaw))),
'img' => '',
];
if (count($out) >= $limit) {
break;
}
}
if ($out) {
@is_dir(dirname($cacheFile)) || @mkdir(dirname($cacheFile), 0755, true);
@file_put_contents($cacheFile, json_encode($out, JSON_UNESCAPED_UNICODE));
@unlink($lockFile);
return $out;
}
}
// 2) Fallback API
$apiUrl = 'https://www.sovetnik.fr/wp-json/wp/v2/posts?per_page=' . (int) $limit . '&_embed=1';
[$ok, $jsonStr, $err, $http] = fetch_url($apiUrl, $timeout);
if ($debug) {
echo "\n<!-- Sovetnik API try: $apiUrl | ok=" . ($ok ? '1' : '0') . " | http=$http | err=" . htmlspecialchars($err) . " -->\n";
}
if ($ok) {
$arr = json_decode($jsonStr, true);
if (is_array($arr)) {
$out = [];
foreach ($arr as $p) {
$title = trim(decode_entities(strip_tags($p['title']['rendered'] ?? '')));
$desc = trim(decode_entities(strip_tags($p['excerpt']['rendered'] ?? '')));
$img = '';
if (! empty($p['_embedded']['wp:featuredmedia'][0]['source_url'])) {
$img = (string) $p['_embedded']['wp:featuredmedia'][0]['source_url'];
}
$out[] = [
'url' => (string) ($p['link'] ?? ''),
'title' => $title,
'desc' => $desc,
'img' => $img,
];
if (count($out) >= $limit) {
break;
}
}
if ($out) {
@is_dir(dirname($cacheFile)) || @mkdir(dirname($cacheFile), 0755, true);
@file_put_contents($cacheFile, json_encode($out, JSON_UNESCAPED_UNICODE));
@unlink($lockFile);
return $out;
}
}
}
// Échec refresh => on enlève le lock et on sert le stale
@unlink($lockFile);
return $stale;
}
// Debug optionnel
$debugArticles = isset($_GET['debug']) && $_GET['debug'] === '1';
$articles = fetch_sovetnik_articles(4, $debugArticles);
?>
<div class="hp-articles">
<?php if (! empty($articles)): ?>
<?php foreach ($articles as $a): ?>
<a class="ha" href="<?php echo h($a['url']) ?>" target="_blank" rel="noopener">
<div class="ha-img" style="<?php echo ! empty($a['img']) ? 'background:url(' . h($a['img']) . ') top center/cover no-repeat;' : '' ?>"></div>
<div class="ha-body">
<p class="ha-title"><?php echo h($a['title']) ?></p>
<p class="ha-sub"><?php echo h(mb_strimwidth($a['desc'], 0, 120, '…', 'UTF-8')) ?></p>
</div>
</a>
<?php endforeach; ?>
<?php else: ?>
<div class="text-muted"><?php echo h($translations['home_articles_unavailable'] ?? 'Articles indisponibles pour le moment.') ?></div>
<?php endif; ?>
</div>
<!--<div class="hp-all">
<a href="https://www.sovetnik.fr/" target="_blank" rel="noopener">
<?php echo h($translations['home_all_articles'] ?? '') ?>
</a>
</div>-->
<br>
</div>
@@ -680,7 +489,10 @@ else: ?>
</div>
</section>
<div class="visually-hidden">
<h2><?php echo h($translations['boostseo_home_h2_cities'] ?? '') ?></h2>
<p><?php echo h($translations['boostseo_home_cities_text'] ?? '') ?></p>
</div>
<footer class="site-footer">
<div class="container">
<p><?php echo $translations['copyr'] ?? '' ?><a href="/terms_fr.php" target="_blank"></a></p>