- Add password-protected links with session-based unlock flow - Add redirect warning (interstitial) pages before external URLs - Add smart targeting rules: device-based, country/geo, A/B weighted rotation - Add configurable per-IP rate limiting with 429 + Retry-After headers - Add daily stats aggregation & log pruning (short-url:aggregate-and-prune) - Add IncrementVisitJob as queue-based counter buffering fallback - Add ShortUrlDailyStats model with JSON stat columns per day - Add two new migrations: targeting/security fields, daily_stats table - Add password-prompt.blade.php and warning.blade.php views - Extend Settings GUI with Performance & Security tab (aggregation + rate limiting) - Extend ShortUrlForm with Targeting & Security section - Add POST route for password form submission (was GET-only → 405) - Replace enum(device_type) with string(20) for cross-DB compatibility - Remove ->after() MySQL-only hints from ALTER TABLE migrations - Fix aggregation test: use whereDate() instead of assertDatabaseHas for date column - Extend en/pl translations for all new features - Expand README.md with full v1.2.0 documentation (476 lines)
59 lines
1.7 KiB
PHP
59 lines
1.7 KiB
PHP
<?php
|
|
|
|
namespace Bjanczak\FilamentShortUrl\Filament\Resources\ShortUrlResource\Widgets;
|
|
|
|
use Bjanczak\FilamentShortUrl\Models\ShortUrl;
|
|
use Filament\Widgets\Widget;
|
|
|
|
class ShortUrlVisitsBottomBreakdown extends Widget
|
|
{
|
|
public ?ShortUrl $record = null;
|
|
|
|
public ?string $dateFrom = null;
|
|
|
|
public ?string $dateTo = null;
|
|
|
|
protected string $view = 'filament-short-url::widgets.visits-bottom-breakdown';
|
|
|
|
protected int|string|array $columnSpan = 'full';
|
|
|
|
public function mount(?ShortUrl $record = null): void
|
|
{
|
|
$this->record = $record;
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>
|
|
*/
|
|
protected function getViewData(): array
|
|
{
|
|
if (! $this->record) {
|
|
return [
|
|
'visitsByDevice' => [],
|
|
'visitsByBrowser' => [],
|
|
'visitsByOs' => [],
|
|
'visitsByReferer' => [],
|
|
'visitsByCity' => [],
|
|
'utmSources' => [],
|
|
'utmMediums' => [],
|
|
'utmCampaigns' => [],
|
|
'totalVisits' => 0,
|
|
];
|
|
}
|
|
|
|
$stats = $this->record->getCachedStats($this->dateFrom, $this->dateTo);
|
|
|
|
return [
|
|
'visitsByDevice' => $stats['visitsByDevice'] ?? [],
|
|
'visitsByBrowser' => $stats['visitsByBrowser'] ?? [],
|
|
'visitsByOs' => $stats['visitsByOs'] ?? [],
|
|
'visitsByReferer' => $stats['visitsByReferer'] ?? [],
|
|
'visitsByCity' => $stats['visitsByCity'] ?? [],
|
|
'utmSources' => $stats['utmSources'] ?? [],
|
|
'utmMediums' => $stats['utmMediums'] ?? [],
|
|
'utmCampaigns' => $stats['utmCampaigns'] ?? [],
|
|
'totalVisits' => $stats['totalVisits'] ?? 0,
|
|
];
|
|
}
|
|
}
|