feat: plugin analytics avec custom field kirbyup + Chart.js
Refactoring complet du plugin analytics : remplacement de la section avec template Vue inline par un custom field compilé avec kirbyup. Dashboard avec KPIs, line chart Chart.js et filtres par date. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
7371e66ec1
commit
8a73da920f
15 changed files with 873 additions and 0 deletions
37
public/site/plugins/analytics/classes/AnalyticsPage.php
Normal file
37
public/site/plugins/analytics/classes/AnalyticsPage.php
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace adrienpayet\analytics;
|
||||
|
||||
use Kirby\Cms\Page;
|
||||
|
||||
class AnalyticsPage extends Page
|
||||
{
|
||||
public function getAnalyticsData(array $filters = []): array
|
||||
{
|
||||
$user = kirby()->user();
|
||||
|
||||
if (!$user) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if ($user->isAdmin()) {
|
||||
return AnalyticsStore::getAggregatedData($filters);
|
||||
}
|
||||
|
||||
$allowedProjects = $user->currentProjects();
|
||||
$allowedEmails = [];
|
||||
foreach ($allowedProjects as $project) {
|
||||
$users = kirby()->users();
|
||||
foreach ($users as $u) {
|
||||
if ($u->currentProjects()->has($project)) {
|
||||
$allowedEmails[] = $u->email()->value();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$filters['emails'] = array_unique($allowedEmails);
|
||||
|
||||
return AnalyticsStore::getAggregatedData($filters);
|
||||
}
|
||||
|
||||
}
|
||||
148
public/site/plugins/analytics/classes/AnalyticsStore.php
Normal file
148
public/site/plugins/analytics/classes/AnalyticsStore.php
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
<?php
|
||||
|
||||
namespace adrienpayet\analytics;
|
||||
|
||||
use Kirby\Data\Yaml;
|
||||
use Kirby\Toolkit\F;
|
||||
|
||||
class AnalyticsStore
|
||||
{
|
||||
private static function getFilePath(): string
|
||||
{
|
||||
return kirby()->root('content') . '/analytics/visits.yml';
|
||||
}
|
||||
|
||||
private static function ensureFileExists(): void
|
||||
{
|
||||
$filePath = self::getFilePath();
|
||||
$dirPath = dirname($filePath);
|
||||
|
||||
if (!is_dir($dirPath)) {
|
||||
F::mkdir($dirPath, true);
|
||||
}
|
||||
|
||||
if (!F::exists($filePath)) {
|
||||
F::write($filePath, Yaml::encode(['visits' => []]));
|
||||
}
|
||||
}
|
||||
|
||||
public static function addVisit(Visit $visit): void
|
||||
{
|
||||
self::ensureFileExists();
|
||||
|
||||
$filePath = self::getFilePath();
|
||||
$data = Yaml::decode(F::read($filePath));
|
||||
|
||||
$visits = $data['visits'] ?? [];
|
||||
$visits[] = $visit->toArray();
|
||||
|
||||
// Limiter à 10000 visites max
|
||||
if (count($visits) > 10000) {
|
||||
$visits = array_slice($visits, -10000);
|
||||
}
|
||||
|
||||
$data['visits'] = $visits;
|
||||
F::write($filePath, Yaml::encode($data));
|
||||
}
|
||||
|
||||
public static function getVisits(array $filters = []): array
|
||||
{
|
||||
self::ensureFileExists();
|
||||
|
||||
$filePath = self::getFilePath();
|
||||
$data = Yaml::decode(F::read($filePath));
|
||||
$visits = $data['visits'] ?? [];
|
||||
|
||||
// Convertir en objets Visit
|
||||
$visits = array_map(fn($v) => Visit::fromArray($v), $visits);
|
||||
|
||||
// Filtrer par daterange
|
||||
if (!empty($filters['startDate']) || !empty($filters['endDate'])) {
|
||||
$visits = array_filter($visits, function($visit) use ($filters) {
|
||||
$timestamp = strtotime($visit->timestamp);
|
||||
|
||||
if (!empty($filters['startDate'])) {
|
||||
$startDate = strtotime($filters['startDate'] . ' 00:00:00');
|
||||
if ($timestamp < $startDate) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($filters['endDate'])) {
|
||||
$endDate = strtotime($filters['endDate'] . ' 23:59:59');
|
||||
if ($timestamp > $endDate) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
// Filtrer par projet
|
||||
if (!empty($filters['project'])) {
|
||||
$projectId = $filters['project'];
|
||||
$visits = array_filter($visits, function($visit) use ($projectId) {
|
||||
return str_contains($visit->pageUrl, "/projects/{$projectId}");
|
||||
});
|
||||
}
|
||||
|
||||
// Filtrer par email (permissions)
|
||||
if (!empty($filters['emails'])) {
|
||||
$allowedEmails = $filters['emails'];
|
||||
$visits = array_filter($visits, function($visit) use ($allowedEmails) {
|
||||
return in_array($visit->email, $allowedEmails);
|
||||
});
|
||||
}
|
||||
|
||||
return array_values($visits);
|
||||
}
|
||||
|
||||
public static function getAggregatedData(array $filters = []): array
|
||||
{
|
||||
self::ensureFileExists();
|
||||
$visits = self::getVisits($filters);
|
||||
|
||||
// Visites par jour
|
||||
$visitsByDay = [];
|
||||
foreach ($visits as $visit) {
|
||||
$day = date('Y-m-d', strtotime($visit->timestamp));
|
||||
$visitsByDay[$day] = ($visitsByDay[$day] ?? 0) + 1;
|
||||
}
|
||||
ksort($visitsByDay);
|
||||
|
||||
// Visites par page
|
||||
$visitsByPage = [];
|
||||
foreach ($visits as $visit) {
|
||||
$page = $visit->pageName ?: $visit->pageUrl;
|
||||
$visitsByPage[$page] = ($visitsByPage[$page] ?? 0) + 1;
|
||||
}
|
||||
arsort($visitsByPage);
|
||||
|
||||
// Visites par utilisateur
|
||||
$visitsByUser = [];
|
||||
foreach ($visits as $visit) {
|
||||
$visitsByUser[$visit->email] = ($visitsByUser[$visit->email] ?? 0) + 1;
|
||||
}
|
||||
arsort($visitsByUser);
|
||||
|
||||
// Nombre de sessions uniques
|
||||
$uniqueSessions = count(array_unique(array_map(fn($v) => $v->sessionId, $visits)));
|
||||
|
||||
// Visites par type de page
|
||||
$visitsByType = [];
|
||||
foreach ($visits as $visit) {
|
||||
$visitsByType[$visit->pageType] = ($visitsByType[$visit->pageType] ?? 0) + 1;
|
||||
}
|
||||
arsort($visitsByType);
|
||||
|
||||
return [
|
||||
'totalVisits' => count($visits),
|
||||
'uniqueSessions' => $uniqueSessions,
|
||||
'visitsByDay' => $visitsByDay,
|
||||
'visitsByPage' => array_slice($visitsByPage, 0, 10),
|
||||
'visitsByUser' => $visitsByUser,
|
||||
'visitsByType' => $visitsByType,
|
||||
];
|
||||
}
|
||||
}
|
||||
46
public/site/plugins/analytics/classes/Visit.php
Normal file
46
public/site/plugins/analytics/classes/Visit.php
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
namespace adrienpayet\analytics;
|
||||
|
||||
class Visit
|
||||
{
|
||||
public string $id;
|
||||
public string $email;
|
||||
public ?string $country;
|
||||
public string $timestamp;
|
||||
public string $sessionId;
|
||||
public string $pageUrl;
|
||||
public string $pageType;
|
||||
public ?string $pageName;
|
||||
|
||||
public function __construct(array $data)
|
||||
{
|
||||
$this->id = $data['id'] ?? uniqid('visit_', true);
|
||||
$this->email = $data['email'];
|
||||
$this->country = $data['country'] ?? null;
|
||||
$this->timestamp = $data['timestamp'] ?? date('Y-m-d H:i:s');
|
||||
$this->sessionId = $data['sessionId'];
|
||||
$this->pageUrl = $data['pageUrl'];
|
||||
$this->pageType = $data['pageType'];
|
||||
$this->pageName = $data['pageName'] ?? null;
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'email' => $this->email,
|
||||
'country' => $this->country,
|
||||
'timestamp' => $this->timestamp,
|
||||
'sessionId' => $this->sessionId,
|
||||
'pageUrl' => $this->pageUrl,
|
||||
'pageType' => $this->pageType,
|
||||
'pageName' => $this->pageName,
|
||||
];
|
||||
}
|
||||
|
||||
public static function fromArray(array $data): self
|
||||
{
|
||||
return new self($data);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue