126 lines
3.4 KiB
PHP
126 lines
3.4 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
namespace NeonBlog;
|
|
|
|
final class Stats
|
|
{
|
|
private string $path;
|
|
private Config $config;
|
|
|
|
public function __construct(string $path, Config $config)
|
|
{
|
|
$this->path = $path;
|
|
$this->config = $config;
|
|
}
|
|
|
|
public function record(string $routeType, string $slug, int $statusCode): void
|
|
{
|
|
if (!$this->config->get('stats.enabled', true) || PHP_SAPI === 'cli') {
|
|
return;
|
|
}
|
|
if (($_SERVER['REQUEST_METHOD'] ?? 'GET') !== 'GET') {
|
|
return;
|
|
}
|
|
|
|
$dir = dirname($this->path);
|
|
if (!is_dir($dir)) {
|
|
mkdir($dir, 0775, true);
|
|
}
|
|
|
|
$handle = fopen($this->path, 'ab');
|
|
if (!$handle) {
|
|
return;
|
|
}
|
|
|
|
flock($handle, LOCK_EX);
|
|
clearstatcache(true, $this->path);
|
|
if ((filesize($this->path) ?: 0) === 0) {
|
|
fputcsv($handle, [
|
|
'date',
|
|
'time',
|
|
'request_id',
|
|
'ip_hash',
|
|
'method',
|
|
'path',
|
|
'route_type',
|
|
'slug',
|
|
'query',
|
|
'referrer',
|
|
'user_agent',
|
|
'status_code',
|
|
], ',', '"', '', "\n");
|
|
}
|
|
|
|
$query = parse_url((string) ($_SERVER['REQUEST_URI'] ?? ''), PHP_URL_QUERY) ?: '';
|
|
fputcsv($handle, [
|
|
date('Y-m-d'),
|
|
date('H:i:s'),
|
|
bin2hex(random_bytes(6)),
|
|
$this->ipHash(),
|
|
$_SERVER['REQUEST_METHOD'] ?? 'GET',
|
|
parse_url((string) ($_SERVER['REQUEST_URI'] ?? '/'), PHP_URL_PATH) ?: '/',
|
|
$routeType,
|
|
$slug,
|
|
$query,
|
|
$_SERVER['HTTP_REFERER'] ?? '',
|
|
substr((string) ($_SERVER['HTTP_USER_AGENT'] ?? ''), 0, 300),
|
|
$statusCode,
|
|
], ',', '"', '', "\n");
|
|
|
|
flock($handle, LOCK_UN);
|
|
fclose($handle);
|
|
}
|
|
|
|
/** @return array<string, mixed> */
|
|
public function summary(): array
|
|
{
|
|
if (!is_file($this->path)) {
|
|
return [
|
|
'visits' => 0,
|
|
'routes' => [],
|
|
'top_paths' => [],
|
|
];
|
|
}
|
|
|
|
$handle = fopen($this->path, 'rb');
|
|
if (!$handle) {
|
|
return ['visits' => 0, 'routes' => [], 'top_paths' => []];
|
|
}
|
|
|
|
$header = fgetcsv($handle, null, ',', '"', '') ?: [];
|
|
$visits = 0;
|
|
$routes = [];
|
|
$paths = [];
|
|
while (($row = fgetcsv($handle, null, ',', '"', '')) !== false) {
|
|
if ($row === $header) {
|
|
continue;
|
|
}
|
|
$record = array_combine($header, $row);
|
|
if (!is_array($record)) {
|
|
continue;
|
|
}
|
|
$visits++;
|
|
$routes[$record['route_type']] = ($routes[$record['route_type']] ?? 0) + 1;
|
|
$paths[$record['path']] = ($paths[$record['path']] ?? 0) + 1;
|
|
}
|
|
fclose($handle);
|
|
arsort($routes);
|
|
arsort($paths);
|
|
|
|
return [
|
|
'visits' => $visits,
|
|
'routes' => $routes,
|
|
'top_paths' => array_slice($paths, 0, 10, true),
|
|
];
|
|
}
|
|
|
|
private function ipHash(): string
|
|
{
|
|
$ip = (string) ($_SERVER['REMOTE_ADDR'] ?? 'cli');
|
|
$salt = (string) $this->config->get('stats.hash_salt', 'change-me');
|
|
|
|
return hash_hmac('sha256', $ip, $salt);
|
|
}
|
|
}
|