This commit is contained in:
mathiaspalmqvist committed 2025-09-30 10:20:29 +02:00
commit 50d66414ea
13 files changed
+10287

No files matched your search

+170
View File
@@ -0,0 +1,170 @@
<?php
namespace Mattitja\Analytics\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Log;
use Mattitja\Analytics\Services\AnalyticsClient;
class AnalyticsMiddleware
{
/**
* Common bot user agent patterns for detection.
*/
private const array BOT_PATTERNS = [
'bot',
'crawler',
'spider',
'scraper',
'curl',
'wget',
'googlebot',
'bingbot',
'facebookexternalhit',
'twitterbot',
];
/**
* Handle an incoming request and capture analytics data.
*/
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);
if ($this->shouldCaptureAnalytics($request)) {
$data = $this->buildAnalyticsData($request);
$this->sendAnalytics($data);
}
return $response;
}
/**
* Determine if analytics should be captured for this request.
*/
protected function shouldCaptureAnalytics(Request $request): bool
{
return $this->isAnalyticsEnabled()
&& $this->hasSiteKey()
&& ! $this->isIgnoredPath($request)
&& ! $this->isIgnoredIp($request)
&& ! $this->shouldIgnoreBot($request)
&& ! $this->respectsDoNotTrack($request);
}
/**
* Check if analytics tracking is enabled.
*/
private function isAnalyticsEnabled(): bool
{
return config('mattitja-analytics.enabled', true);
}
/**
* Check if site key is configured.
*/
private function hasSiteKey(): bool
{
return ! empty(config('mattitja-analytics.site_key'));
}
/**
* Check if bot traffic should be ignored.
*/
private function shouldIgnoreBot(Request $request): bool
{
return $this->isBot($request) && ! config('mattitja-analytics.capture_bots', false);
}
/**
* Check if the request path should be ignored.
*/
protected function isIgnoredPath(Request $request): bool
{
$ignoredPaths = config('mattitja-analytics.ignore_paths', []);
$path = $request->path();
foreach ($ignoredPaths as $pattern) {
if (fnmatch($pattern, $path)) {
return true;
}
}
return false;
}
/**
* Check if the request IP should be ignored.
*/
protected function isIgnoredIp(Request $request): bool
{
$ignoredIps = config('mattitja-analytics.ignore_ips', []);
return in_array($request->ip(), $ignoredIps, true);
}
/**
* Detect if the request is from a bot.
*/
protected function isBot(Request $request): bool
{
$userAgent = $request->userAgent() ?? '';
foreach (self::BOT_PATTERNS as $pattern) {
if (stripos($userAgent, $pattern) !== false) {
return true;
}
}
return false;
}
/**
* Check if the request respects Do Not Track header.
*/
protected function respectsDoNotTrack(Request $request): bool
{
if (! config('mattitja-analytics.respect_dnt', true)) {
return false;
}
return $request->header('DNT') === '1';
}
/**
* Build analytics data payload from request.
*/
protected function buildAnalyticsData(Request $request): array
{
$data = [
'k' => config('mattitja-analytics.site_key'),
'u' => $request->fullUrl(),
'ip' => $request->ip(),
'ua' => $request->userAgent() ?? '',
];
$referrer = $request->header('referer');
if (! empty($referrer)) {
$data['r'] = $referrer;
}
return $data;
}
/**
* Send analytics data to the API.
*/
protected function sendAnalytics(array $data): void
{
try {
app(AnalyticsClient::class)->send($data);
} catch (\Exception $e) {
Log::error('Failed to send analytics data', [
'error' => $e->getMessage(),
'data' => $data,
]);
}
}
}