129 lines
4.0 KiB
PHP
129 lines
4.0 KiB
PHP
<?php
|
|
|
|
namespace Bjanczak\FilamentShortUrl\Services;
|
|
|
|
use Bjanczak\FilamentShortUrl\Models\ShortUrl;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Carbon;
|
|
|
|
class ShortUrlService
|
|
{
|
|
/**
|
|
* Start building a ShortUrl programmatically using a fluent interface.
|
|
*/
|
|
public function destination(string $url): ShortUrlBuilder
|
|
{
|
|
return new ShortUrlBuilder($this, $url);
|
|
}
|
|
|
|
/**
|
|
* Generate a unique, collision-free URL key.
|
|
*
|
|
* Uses base62 (a-z A-Z 0-9) for maximum URL friendliness.
|
|
* Retries automatically on the rare collision chance.
|
|
*/
|
|
public function generateKey(?int $length = null): string
|
|
{
|
|
$length ??= (int) config('filament-short-url.key_length', 6);
|
|
$characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
|
$maxIndex = strlen($characters) - 1;
|
|
|
|
do {
|
|
$key = '';
|
|
for ($i = 0; $i < $length; $i++) {
|
|
$key .= $characters[random_int(0, $maxIndex)];
|
|
}
|
|
} while ($this->keyExists($key));
|
|
|
|
return $key;
|
|
}
|
|
|
|
public function keyExists(string $key): bool
|
|
{
|
|
return ShortUrl::where('url_key', $key)->exists();
|
|
}
|
|
|
|
/**
|
|
* Create and persist a new ShortUrl.
|
|
*
|
|
* @param array{
|
|
* destination_url: string,
|
|
* url_key?: string,
|
|
* notes?: string,
|
|
* is_enabled?: bool,
|
|
* redirect_status_code?: int,
|
|
* single_use?: bool,
|
|
* forward_query_params?: bool,
|
|
* expires_at?: Carbon|null,
|
|
* track_visits?: bool,
|
|
* track_ip_address?: bool,
|
|
* track_browser?: bool,
|
|
* track_browser_version?: bool,
|
|
* track_operating_system?: bool,
|
|
* track_operating_system_version?: bool,
|
|
* track_device_type?: bool,
|
|
* track_referer_url?: bool,
|
|
* qr_options?: array,
|
|
* ga_tracking_id?: string|null,
|
|
* } $data
|
|
*/
|
|
public function create(array $data): ShortUrl
|
|
{
|
|
$tracking = config('filament-short-url.tracking', []);
|
|
$fields = $tracking['fields'] ?? [];
|
|
|
|
$data = array_merge([
|
|
'is_enabled' => true,
|
|
'redirect_status_code' => config('filament-short-url.redirect_status_code', 302),
|
|
'single_use' => false,
|
|
'forward_query_params' => false,
|
|
'track_visits' => $tracking['enabled'] ?? true,
|
|
'track_ip_address' => $fields['ip_address'] ?? true,
|
|
'track_browser' => $fields['browser'] ?? true,
|
|
'track_browser_version' => $fields['browser_version'] ?? true,
|
|
'track_operating_system' => $fields['operating_system'] ?? true,
|
|
'track_operating_system_version' => $fields['operating_system_version'] ?? true,
|
|
'track_device_type' => $fields['device_type'] ?? true,
|
|
'track_referer_url' => $fields['referer_url'] ?? true,
|
|
], $data);
|
|
|
|
if (empty($data['url_key'])) {
|
|
$data['url_key'] = $this->generateKey();
|
|
}
|
|
|
|
return ShortUrl::create($data);
|
|
}
|
|
|
|
/**
|
|
* Build the full publicly accessible short URL string.
|
|
*/
|
|
public function buildShortUrl(ShortUrl $shortUrl): string
|
|
{
|
|
return $shortUrl->getShortUrl();
|
|
}
|
|
|
|
/**
|
|
* Resolve the final redirect target URL, optionally forwarding query params.
|
|
*/
|
|
public function resolveRedirectUrl(ShortUrl $shortUrl, Request $request): string
|
|
{
|
|
$destination = $shortUrl->resolveDestinationUrl($request);
|
|
|
|
if (! $shortUrl->forward_query_params) {
|
|
return $destination;
|
|
}
|
|
|
|
$queryParams = $request->query();
|
|
// Remove routing/auth parameters so they don't leak to destination
|
|
unset($queryParams['confirmed'], $queryParams['password']);
|
|
|
|
if (empty($queryParams)) {
|
|
return $destination;
|
|
}
|
|
|
|
$separator = str_contains($destination, '?') ? '&' : '?';
|
|
|
|
return $destination.$separator.http_build_query($queryParams);
|
|
}
|
|
}
|