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 */ 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); } }