feat: visiteurs uniques par article (7/14/30 j) stockés dans visitors.json
- AccessLogParser : suivi des IPs non-bot uniques par /post/ sur 3 fenêtres (7/14/30 j) - index.php : écriture de data/UUID/visitors.json à chaque recalcul des stats admin - post_view.php : affichage du compteur de lecteurs dans la zone hero Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+31
-7
@@ -896,6 +896,13 @@ switch ($action) {
|
||||
$bookContext['next_article'] = $bookContext['next'] !== null ? $articles->getBySlug($bookContext['next']) : null;
|
||||
}
|
||||
|
||||
$articleVisitors = [];
|
||||
$_visFile = DATA_PATH . '/' . ($article['uuid'] ?? '') . '/visitors.json';
|
||||
if (($article['uuid'] ?? '') !== '' && is_file($_visFile)) {
|
||||
$articleVisitors = json_decode((string) file_get_contents($_visFile), true) ?: [];
|
||||
}
|
||||
unset($_visFile);
|
||||
|
||||
include BASE_PATH . '/templates/post_view.php';
|
||||
break;
|
||||
|
||||
@@ -2785,13 +2792,14 @@ switch ($action) {
|
||||
}
|
||||
|
||||
$statsRaw = [
|
||||
'readable' => $accessParser->isReadable(),
|
||||
'books' => $tParser->top($cutoff14, 20, ['/book/']),
|
||||
'as' => AsnLookup::aggregateByAs($topIps, $asnMap),
|
||||
'pages_by_day' => $accessStats['pages_by_day'] ?? [],
|
||||
'ip_data' => $ipData,
|
||||
'all_uas' => $accessStats['all_uas'] ?? [],
|
||||
'unique_visitors' => $accessStats['unique_visitors'] ?? [7 => 0, 14 => 0, 30 => 0],
|
||||
'readable' => $accessParser->isReadable(),
|
||||
'books' => $tParser->top($cutoff14, 20, ['/book/']),
|
||||
'as' => AsnLookup::aggregateByAs($topIps, $asnMap),
|
||||
'pages_by_day' => $accessStats['pages_by_day'] ?? [],
|
||||
'ip_data' => $ipData,
|
||||
'all_uas' => $accessStats['all_uas'] ?? [],
|
||||
'unique_visitors' => $accessStats['unique_visitors'] ?? [7 => 0, 14 => 0, 30 => 0],
|
||||
'article_unique_visitors' => $accessStats['article_unique_visitors'] ?? [],
|
||||
];
|
||||
@file_put_contents($statsCacheFile, json_encode($statsRaw));
|
||||
}
|
||||
@@ -2805,6 +2813,22 @@ switch ($action) {
|
||||
$adminData['stats_all_uas'] = $statsRaw['all_uas'] ?? [];
|
||||
$adminData['stats_unique_visitors'] = $statsRaw['unique_visitors'] ?? [7 => 0, 14 => 0, 30 => 0];
|
||||
|
||||
// Écriture des visitors.json par article (slug → UUID via l'index)
|
||||
$_slugIndex = is_file(DATA_PATH . '/_cache/slug_index.json')
|
||||
? (json_decode((string) file_get_contents(DATA_PATH . '/_cache/slug_index.json'), true) ?: [])
|
||||
: [];
|
||||
foreach (($statsRaw['article_unique_visitors'] ?? []) as $_artPath => $_artCounts) {
|
||||
$_artSlug = rawurldecode(substr((string) $_artPath, 6));
|
||||
$_artUuid = $_slugIndex[$_artSlug] ?? null;
|
||||
if ($_artUuid !== null && preg_match('/^[0-9a-f\-]{36}$/i', $_artUuid)) {
|
||||
@file_put_contents(
|
||||
DATA_PATH . '/' . $_artUuid . '/visitors.json',
|
||||
json_encode(array_merge($_artCounts, ['updated' => time()]), JSON_UNESCAPED_UNICODE)
|
||||
);
|
||||
}
|
||||
}
|
||||
unset($_slugIndex, $_artPath, $_artCounts, $_artSlug, $_artUuid);
|
||||
|
||||
// AS exclus (chargé en direct, pas mis en cache)
|
||||
$excludedAsFile = DATA_PATH . '/excluded_as.json';
|
||||
$adminData['excluded_as'] = is_file($excludedAsFile)
|
||||
|
||||
Reference in New Issue
Block a user