- new blog cms
This commit is contained in:
+125
@@ -0,0 +1,125 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user