diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b98386..9ba9bf2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ Format : [Keep a Changelog](https://keepachangelog.com/fr/1.0.0/) — versionnag --- +## [1.6.16] - 2026-05-16 + +### Ajouté +- `SearchLogParser` : paramètre `$days` (7 ou 14) — cache distinct par période, filtre logFiles par date (#46) +- `admin/searches` : boutons 7 j / 14 j pour choisir la fenêtre d'analyse (#46) + +### Modifié +- `SearchLogParser` : tri par visiteurs uniques (IPs distinctes) au lieu de hits bruts — colonne renommée « Visiteurs » (#41) +- URL inconnue / article introuvable : redirection 302 vers `/search?q=…` au lieu de page 404 (#57) +- `edit_tags` : sections « Abréviations » et « Noms composés » masquées si des valeurs connues existent pour le type (#48) + +--- + ## [1.6.15] - 2026-05-16 ### Ajouté diff --git a/public/index.php b/public/index.php index 37a305d..0cc6c77 100644 --- a/public/index.php +++ b/public/index.php @@ -50,12 +50,17 @@ $metaRobots = in_array($action, $_noindexActions, true) ? 'noindex, nofollow' : unset($_noindexActions); // ─── Recherche de l'article le plus proche et redirection 301 ──────────────── +function slugToSearchQuery(string $rawPath): string +{ + return trim((string)preg_replace('/\s{2,}/', ' ', (string)preg_replace( + '/[^a-zA-ZÀ-ÿ0-9\s]/u', ' ', str_replace(['-', '_', '/'], ' ', $rawPath) + ))); +} + function searchAndRedirect(string $rawPath, ArticleManager $articles): void { require_once BASE_PATH . '/src/SearchEngine.php'; - $query = (string)preg_replace('/\s{2,}/', ' ', trim( - (string)preg_replace('/[^a-zA-ZÀ-ÿ0-9\s]/u', ' ', str_replace(['-', '_', '/'], ' ', $rawPath)) - )); + $query = slugToSearchQuery($rawPath); if ($query === '') { return; } @@ -666,9 +671,8 @@ switch ($action) { case 'view': $article = $slug !== '' ? $articles->getBySlug($slug) : null; if (!$article) { - searchAndRedirect($slug, $articles); - http_response_code(404); - echo 'Article introuvable.'; + $q = slugToSearchQuery($slug); + header('Location: /search' . ($q !== '' ? '?q=' . urlencode($q) : ''), true, 302); exit; } @@ -2577,9 +2581,11 @@ switch ($action) { exit; } require_once BASE_PATH . '/src/SearchLogParser.php'; - $parser = new SearchLogParser('/var/log/apache2', apacheAccessLog()); - $adminData['search_terms'] = $parser->topTerms(100); + $days = in_array((int)($_GET['days'] ?? 14), [7, 14], true) ? (int)$_GET['days'] : 14; + $parser = new SearchLogParser('/var/log/apache2', apacheAccessLog(), '', 600, $days); + $adminData['search_terms'] = $parser->topTerms(100); $adminData['search_log_readable'] = $parser->isReadable(); + $adminData['search_days'] = $days; } if ($tab === 'stats') { @@ -3383,23 +3389,9 @@ switch ($action) { (string)(parse_url($_SERVER['REDIRECT_URL'] ?? $_SERVER['REQUEST_URI'] ?? '', PHP_URL_PATH) ?? ''), '/' ); - if ($notFoundPath !== '') { - searchAndRedirect(basename($notFoundPath), $articles); - } - http_response_code(404); - ob_start(); - ?> -
-

Page introuvable

-

Cette adresse ne correspond à aucun article.
Vous avez peut-être suivi un ancien lien.

- ← Retour à l'accueil -
- logDir = rtrim($logDir, '/'); $this->vhostBase = $vhostBase; + $this->days = max(1, min(30, $days)); $this->cacheFile = $cacheFile !== '' ? $cacheFile - : dirname(__DIR__) . '/_cache/search_terms.json'; + : dirname(__DIR__) . '/_cache/search_terms_' . $this->days . 'd.json'; $this->cacheTtl = $cacheTtl; } - /** @return array terme => nombre d'occurrences, trié desc */ + /** @return array terme => visiteurs uniques, trié desc */ public function topTerms(int $limit = 100): array { if ($this->cacheValid()) { @@ -33,9 +36,14 @@ class SearchLogParser } } - $counts = []; + $visitors = []; // terme => [ip => true] foreach ($this->logFiles() as $file) { - $this->parseFile($file, $counts); + $this->parseFile($file, $visitors); + } + + $counts = []; + foreach ($visitors as $term => $ips) { + $counts[$term] = count($ips); } arsort($counts); @@ -61,6 +69,7 @@ class SearchLogParser { $pattern = $this->logDir . '/' . $this->vhostBase; $files = []; + $cutoff = time() - $this->days * 86400; // Fichiers correspondant au pattern de base (courants + rotations incluses si glob) $bases = glob($pattern) ?: []; @@ -75,6 +84,9 @@ class SearchLogParser if (!is_readable($path)) { continue; } + if (@filemtime($path) < $cutoff) { + continue; + } if (str_ends_with($path, '.tar.gz')) { $files[] = ['path' => $path, 'type' => 'tgz']; } elseif (str_ends_with($path, '.gz')) { @@ -88,7 +100,7 @@ class SearchLogParser return $files; } - private function parseFile(array $file, array &$counts): void + private function parseFile(array $file, array &$visitors): void { if ($file['type'] === 'tgz') { try { @@ -99,7 +111,7 @@ class SearchLogParser continue; } foreach (explode("\n", $content) as $line) { - $this->parseLine($line, $counts); + $this->parseLine($line, $visitors); } } } catch (\Exception $e) { @@ -113,7 +125,7 @@ class SearchLogParser while (!gzeof($h)) { $line = gzgets($h, 8192); if ($line !== false) { - $this->parseLine($line, $counts); + $this->parseLine($line, $visitors); } } gzclose($h); @@ -123,28 +135,29 @@ class SearchLogParser return; } while (($line = fgets($h)) !== false) { - $this->parseLine($line, $counts); + $this->parseLine($line, $visitors); } fclose($h); } } - private function parseLine(string $line, array &$counts): void + private function parseLine(string $line, array &$visitors): void { if (!str_contains($line, 'GET /search?')) { return; } - if (!preg_match('/"GET \/search\?([^"]*) HTTP\//', $line, $m)) { + if (!preg_match('/^(\S+) \S+ \S+ \[[^\]]+\] "GET \/search\?([^"]*) HTTP\//', $line, $m)) { return; } - parse_str($m[1], $params); + $ip = $m[1]; + parse_str($m[2], $params); $q = trim(urldecode($params['q'] ?? '')); if ($q === '' || mb_strlen($q) > 200) { return; } $q = mb_strtolower($q); - $counts[$q] = ($counts[$q] ?? 0) + 1; + $visitors[$q][$ip] = true; } } diff --git a/templates/admin.php b/templates/admin.php index d0dedaf..6acda9b 100644 --- a/templates/admin.php +++ b/templates/admin.php @@ -1207,7 +1207,15 @@ foreach (COLOR_PALETTE_16 as $_i => $_rgb):
Termes recherchés
- Derniers 14 jours de logs · cache 10 min +
+ Derniers jours · cache 10 min +
+ 7 j + 14 j +
+
@@ -1228,7 +1236,7 @@ foreach (COLOR_PALETTE_16 as $_i => $_rgb): # Terme recherché - Fois + Visiteurs diff --git a/templates/edit_tags.php b/templates/edit_tags.php index 7621331..99007b2 100644 --- a/templates/edit_tags.php +++ b/templates/edit_tags.php @@ -97,8 +97,10 @@ $_typeLabel = $isCatField ? 'Catégorie' : ($tagTypes[$tagType] ?? ucfirst($tag + +

Aucun terme détecté dans cet article.