103 lines
2.5 KiB
PHP
103 lines
2.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Http\Middleware;
|
|
|
|
use App\Services\Traffic\OnlineVisitorRepository;
|
|
use Closure;
|
|
use Illuminate\Http\Request;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
|
|
final class TrackOnlineVisitor
|
|
{
|
|
public function __construct(private readonly OnlineVisitorRepository $visitors)
|
|
{
|
|
}
|
|
|
|
public function handle(Request $request, Closure $next): Response
|
|
{
|
|
$shouldTrack = $this->shouldTrack($request);
|
|
$response = $next($request);
|
|
|
|
if ($shouldTrack) {
|
|
try {
|
|
$this->visitors->track($request);
|
|
} catch (\Throwable) {
|
|
// Presence tracking is best-effort only.
|
|
}
|
|
}
|
|
|
|
return $response;
|
|
}
|
|
|
|
private function shouldTrack(Request $request): bool
|
|
{
|
|
if (! in_array($request->getMethod(), ['GET', 'HEAD'], true)) {
|
|
return false;
|
|
}
|
|
|
|
if ($this->matchesAny($request, [
|
|
'build/*',
|
|
'storage/*',
|
|
'favicon.ico',
|
|
'livewire/*',
|
|
'_debugbar/*',
|
|
'telescope/*',
|
|
'horizon/*',
|
|
'moderation/*',
|
|
])) {
|
|
return false;
|
|
}
|
|
|
|
if ($request->path() === 'moderation/traffic/online/data') {
|
|
return false;
|
|
}
|
|
|
|
if ($this->matchesAny($request, [
|
|
'api/*',
|
|
'admin/*',
|
|
'dashboard*',
|
|
'studio*',
|
|
'settings*',
|
|
'messages*',
|
|
'creator*',
|
|
'login',
|
|
'register',
|
|
'forgot-password',
|
|
'reset-password/*',
|
|
'email/*',
|
|
'logout',
|
|
'up',
|
|
])) {
|
|
return false;
|
|
}
|
|
|
|
return ! $this->isStaticAssetPath($request->path());
|
|
}
|
|
|
|
/**
|
|
* @param array<int, string> $patterns
|
|
*/
|
|
private function matchesAny(Request $request, array $patterns): bool
|
|
{
|
|
foreach ($patterns as $pattern) {
|
|
if ($request->is($pattern)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private function isStaticAssetPath(string $path): bool
|
|
{
|
|
$normalizedPath = '/' . ltrim($path, '/');
|
|
|
|
if (in_array($normalizedPath, ['/robots.txt', '/sitemap.xml'], true) || str_starts_with($normalizedPath, '/sitemaps/')) {
|
|
return false;
|
|
}
|
|
|
|
return (bool) preg_match('/\.(?:css|js|mjs|map|png|jpe?g|gif|svg|webp|avif|ico|woff2?|ttf|eot|otf|mp4|webm|mp3|wav|pdf|zip)$/i', $normalizedPath);
|
|
}
|
|
} |