mirror of
https://github.com/shlinkio/shlink.git
synced 2026-03-10 17:23:12 +08:00
Refactor Visitor model and allow a Location object to be passed to it
This commit is contained in:
@@ -59,4 +59,12 @@ final readonly class TrackingOptions
|
||||
{
|
||||
return $this->disableTrackParam !== null && array_key_exists($this->disableTrackParam, $query);
|
||||
}
|
||||
|
||||
/**
|
||||
* If IP address tracking is disabled, or tracking is disabled all together, then geolocation is not relevant
|
||||
*/
|
||||
public function isGeolocationRelevant(): bool
|
||||
{
|
||||
return ! $this->disableTracking && ! $this->disableIpTracking;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ class Visit extends AbstractEntity implements JsonSerializable
|
||||
type: $type,
|
||||
userAgent: $visitor->userAgent,
|
||||
referer: $visitor->referer,
|
||||
potentialBot: $visitor->isPotentialBot(),
|
||||
potentialBot: $visitor->potentialBot,
|
||||
remoteAddr: self::processAddress($visitor->remoteAddress, $anonymize),
|
||||
visitedUrl: $visitor->visitedUrl,
|
||||
);
|
||||
|
||||
@@ -6,78 +6,85 @@ namespace Shlinkio\Shlink\Core\Visit\Model;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Shlinkio\Shlink\Core\Config\Options\TrackingOptions;
|
||||
use Shlinkio\Shlink\IpGeolocation\Model\Location;
|
||||
|
||||
use function Shlinkio\Shlink\Core\geolocationFromRequest;
|
||||
use function Shlinkio\Shlink\Core\ipAddressFromRequest;
|
||||
use function Shlinkio\Shlink\Core\isCrawler;
|
||||
use function substr;
|
||||
|
||||
final class Visitor
|
||||
final readonly class Visitor
|
||||
{
|
||||
public const USER_AGENT_MAX_LENGTH = 512;
|
||||
public const REFERER_MAX_LENGTH = 1024;
|
||||
public const REMOTE_ADDRESS_MAX_LENGTH = 256;
|
||||
public const VISITED_URL_MAX_LENGTH = 2048;
|
||||
|
||||
public readonly string $userAgent;
|
||||
public readonly string $referer;
|
||||
public readonly string $visitedUrl;
|
||||
public readonly string|null $remoteAddress;
|
||||
private bool $potentialBot;
|
||||
|
||||
public function __construct(string $userAgent, string $referer, string|null $remoteAddress, string $visitedUrl)
|
||||
{
|
||||
$this->userAgent = $this->cropToLength($userAgent, self::USER_AGENT_MAX_LENGTH);
|
||||
$this->referer = $this->cropToLength($referer, self::REFERER_MAX_LENGTH);
|
||||
$this->visitedUrl = $this->cropToLength($visitedUrl, self::VISITED_URL_MAX_LENGTH);
|
||||
$this->remoteAddress = $remoteAddress === null ? null : $this->cropToLength(
|
||||
$remoteAddress,
|
||||
self::REMOTE_ADDRESS_MAX_LENGTH,
|
||||
);
|
||||
$this->potentialBot = isCrawler($userAgent);
|
||||
private function __construct(
|
||||
public string $userAgent,
|
||||
public string $referer,
|
||||
public string|null $remoteAddress,
|
||||
public string $visitedUrl,
|
||||
public bool $potentialBot,
|
||||
public Location|null $geolocation,
|
||||
) {
|
||||
}
|
||||
|
||||
private function cropToLength(string $value, int $length): string
|
||||
public static function fromParams(
|
||||
string $userAgent = '',
|
||||
string $referer = '',
|
||||
string|null $remoteAddress = null,
|
||||
string $visitedUrl = '',
|
||||
Location|null $geolocation = null,
|
||||
): self {
|
||||
return new self(
|
||||
userAgent: self::cropToLength($userAgent, self::USER_AGENT_MAX_LENGTH),
|
||||
referer: self::cropToLength($referer, self::REFERER_MAX_LENGTH),
|
||||
remoteAddress: $remoteAddress === null
|
||||
? null
|
||||
: self::cropToLength($remoteAddress, self::REMOTE_ADDRESS_MAX_LENGTH),
|
||||
visitedUrl: self::cropToLength($visitedUrl, self::VISITED_URL_MAX_LENGTH),
|
||||
potentialBot: isCrawler($userAgent),
|
||||
geolocation: $geolocation,
|
||||
);
|
||||
}
|
||||
|
||||
private static function cropToLength(string $value, int $length): string
|
||||
{
|
||||
return substr($value, 0, $length);
|
||||
}
|
||||
|
||||
public static function fromRequest(ServerRequestInterface $request): self
|
||||
{
|
||||
return new self(
|
||||
$request->getHeaderLine('User-Agent'),
|
||||
$request->getHeaderLine('Referer'),
|
||||
ipAddressFromRequest($request),
|
||||
$request->getUri()->__toString(),
|
||||
return self::fromParams(
|
||||
userAgent: $request->getHeaderLine('User-Agent'),
|
||||
referer: $request->getHeaderLine('Referer'),
|
||||
remoteAddress: ipAddressFromRequest($request),
|
||||
visitedUrl: $request->getUri()->__toString(),
|
||||
geolocation: geolocationFromRequest($request),
|
||||
);
|
||||
}
|
||||
|
||||
public static function empty(): self
|
||||
{
|
||||
return new self('', '', null, '');
|
||||
return self::fromParams();
|
||||
}
|
||||
|
||||
public static function botInstance(): self
|
||||
{
|
||||
return new self('cf-facebook', '', null, '');
|
||||
}
|
||||
|
||||
public function isPotentialBot(): bool
|
||||
{
|
||||
return $this->potentialBot;
|
||||
return self::fromParams(userAgent: 'cf-facebook');
|
||||
}
|
||||
|
||||
public function normalizeForTrackingOptions(TrackingOptions $options): self
|
||||
{
|
||||
$instance = new self(
|
||||
$options->disableUaTracking ? '' : $this->userAgent,
|
||||
$options->disableReferrerTracking ? '' : $this->referer,
|
||||
$options->disableIpTracking ? null : $this->remoteAddress,
|
||||
$this->visitedUrl,
|
||||
return new self(
|
||||
userAgent: $options->disableUaTracking ? '' : $this->userAgent,
|
||||
referer: $options->disableReferrerTracking ? '' : $this->referer,
|
||||
remoteAddress: $options->disableIpTracking ? null : $this->remoteAddress,
|
||||
visitedUrl: $this->visitedUrl,
|
||||
// Keep the fact that the visit was a potential bot, even if we no longer save the user agent
|
||||
potentialBot: $this->potentialBot,
|
||||
geolocation: $this->geolocation,
|
||||
);
|
||||
|
||||
// Keep the fact that the visit was a potential bot, even if we no longer save the user agent
|
||||
$instance->potentialBot = $this->potentialBot;
|
||||
|
||||
return $instance;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user