logFile = $logFile; } public function log(string $page, string $ip, string $userAgent, string $referrer, string $host, string $acceptLanguage): void { $dir = dirname($this->logFile); if (!is_dir($dir)) { @mkdir($dir, 0755, true); } $timestamp = date('Y-m-d H:i:s'); $ua = substr(preg_replace('/[[:cntrl:]]/', '', $userAgent), 0, 500); $ref = substr(preg_replace('/[[:cntrl:]]/', '', $referrer), 0, 500); $line = "[{$timestamp}] [{$ip}] [{$host}] [{$acceptLanguage}] [{$page}] [{$ua}] [{$ref}]\n"; @file_put_contents($this->logFile, $line, FILE_APPEND | LOCK_EX); } public static function getClientIp(): string { $headers = [ 'HTTP_CF_CONNECTING_IP', 'HTTP_X_REAL_IP', 'HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'REMOTE_ADDR', ]; foreach ($headers as $header) { if (!empty($_SERVER[$header])) { $rawIp = $_SERVER[$header]; if (str_contains($rawIp, ',')) { $ips = explode(',', $rawIp); $rawIp = trim($ips[0]); } $rawIp = trim($rawIp); if (filter_var($rawIp, FILTER_VALIDATE_IP) !== false) { return $rawIp; } } } return $_SERVER['REMOTE_ADDR'] ?? 'cli'; } public static function detectVisitorInfo(string $ua, string $user = ''): array { if (empty($ua)) { $userLabel = ($user && $user !== 'Gast') ? $user : 'Onbekend'; return ['type' => 'unknown', 'label' => $userLabel, 'badge' => 'secondary', 'icon' => 'bi-question-circle']; } $bots = [ 'AI' => [ 'GPTBot' => 'AI (GPTBot)', 'ChatGPT-User' => 'AI (ChatGPT)', 'Claude-Web' => 'AI (Claude)', 'ClaudeBot' => 'AI (ClaudeBot)', 'anthropic-ai' => 'AI (Anthropic)', 'Google-Extended' => 'AI (Gemini/Google)', 'CCBot' => 'AI (CommonCrawl)', 'PerplexityBot' => 'AI (Perplexity)', 'Amazonbot' => 'AI (Amazon)', 'cohere-ai' => 'AI (Cohere)', 'OAI-SearchBot' => 'AI (OpenAI)', 'Bytespider' => 'AI (ByteDance)', 'FacebookBot' => 'AI (Meta/FB)', 'Applebot-Extended' => 'AI (Apple)', ], 'Search' => [ 'Googlebot' => 'Zoekmachine (Google)', 'Bingbot' => 'Zoekmachine (Bing)', 'BingPreview' => 'Zoekmachine (Bing)', 'Slurp' => 'Zoekmachine (Yahoo)', 'DuckDuckBot' => 'Zoekmachine (DuckDuckGo)', 'Baiduspider' => 'Zoekmachine (Baidu)', 'YandexBot' => 'Zoekmachine (Yandex)', 'Sogou' => 'Zoekmachine (Sogou)', 'Exabot' => 'Zoekmachine (Exabot)', 'facebot' => 'Zoekmachine (Facebook)', ], 'Scraper' => [ 'HTTrack' => 'Scraper (HTTrack)', 'Scrapy' => 'Scraper (Scrapy)', 'PhantomJS' => 'Scraper (PhantomJS)', 'HeadlessChrome' => 'Scraper (Headless)', 'curl' => 'Scraper (cURL)', 'wget' => 'Scraper (Wget)', 'python' => 'Scraper (Python)', 'Postman' => 'Scraper (Postman)', ], ]; foreach ($bots['AI'] as $pattern => $label) { if (stripos($ua, $pattern) !== false) { return ['type' => 'ai', 'label' => $label, 'badge' => 'danger', 'icon' => 'bi-robot']; } } foreach ($bots['Search'] as $pattern => $label) { if (stripos($ua, $pattern) !== false) { return ['type' => 'search', 'label' => $label, 'badge' => 'primary', 'icon' => 'bi-search']; } } foreach ($bots['Scraper'] as $pattern => $label) { if (stripos($ua, $pattern) !== false) { return ['type' => 'scraper', 'label' => $label, 'badge' => 'warning text-dark', 'icon' => 'bi-bug']; } } if (preg_match('/(bot|crawler|spider|slurp)/i', $ua)) { return ['type' => 'bot', 'label' => 'Bot', 'badge' => 'secondary', 'icon' => 'bi-robot']; } $userPrefix = ($user && $user !== 'Gast') ? htmlspecialchars($user) . ' (' : ''; $userSuffix = ($user && $user !== 'Gast') ? ')' : ''; return [ 'type' => 'human', 'label' => $userPrefix . 'Mens' . $userSuffix, 'badge' => 'success', 'icon' => 'bi-person-check', ]; } public static function detectBot(): ?string { $ua = $_SERVER['HTTP_USER_AGENT'] ?? ''; if (empty($ua)) return null; $bots = [ 'AI' => [ 'GPTBot', 'ChatGPT-User', 'Claude-Web', 'ClaudeBot', 'anthropic-ai', 'Google-Extended', 'CCBot', 'PerplexityBot', 'Amazonbot', 'cohere-ai', 'OAI-SearchBot', 'Bytespider', 'FacebookBot', 'Applebot-Extended', ], 'Search' => [ 'Googlebot', 'Bingbot', 'BingPreview', 'Slurp', 'DuckDuckBot', 'Baiduspider', 'YandexBot', 'Sogou', 'Exabot', 'facebot', ], 'Scraper' => [ 'HTTrack', 'Scrapy', 'PhantomJS', 'HeadlessChrome', ], ]; foreach ($bots as $category => $patterns) { foreach ($patterns as $pattern) { if (stripos($ua, $pattern) !== false) { return $category; } } } return null; } public function getLogs(int $lines = 100): array { if (!file_exists($this->logFile)) { return []; } $content = file($this->logFile); $content = array_slice($content, -$lines); $logs = []; foreach ($content as $line) { if (preg_match('/^\[([^\]]+)\] \[([^\]]+)\] \[([^\]]+)\] \[([^\]]*)\] \[([^\]]+)\] \[([^\]]*)\] \[([^\]]*)\]$/', trim($line), $m)) { $user = $m[3]; // Handle backwards compatibility where host (e.g. localhost:8080 or domain.com) was logged if (str_contains($user, '.') || str_contains($user, ':') || $user === 'cli') { $user = 'Gast'; } $visitorInfo = self::detectVisitorInfo($m[6], $user); $logs[] = [ 'time' => $m[1], 'ip' => $m[2], 'user' => $user, 'visitor_info' => $visitorInfo, 'lang' => $m[4], 'page' => $m[5], 'ua' => $m[6], 'referrer' => $m[7], ]; } } return array_reverse($logs); } }