Save where a visitor is redirected for any kind of tracked visit

This commit is contained in:
Alejandro Celaya
2024-11-24 13:21:48 +01:00
parent 89f70114e4
commit 86cc2b717c
6 changed files with 32 additions and 13 deletions

View File

@@ -22,3 +22,4 @@ const DEFAULT_QR_CODE_ENABLED_FOR_DISABLED_SHORT_URLS = true;
const DEFAULT_QR_CODE_COLOR = '#000000'; // Black
const DEFAULT_QR_CODE_BG_COLOR = '#ffffff'; // White
const IP_ADDRESS_REQUEST_ATTRIBUTE = 'remote_address';
const REDIRECT_URL_REQUEST_ATTRIBUTE = 'redirect_url';

View File

@@ -16,6 +16,8 @@ use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlIdentifier;
use Shlinkio\Shlink\Core\ShortUrl\ShortUrlResolverInterface;
use Shlinkio\Shlink\Core\Visit\RequestTrackerInterface;
use const Shlinkio\Shlink\REDIRECT_URL_REQUEST_ATTRIBUTE;
abstract class AbstractTrackingAction implements MiddlewareInterface, RequestMethodInterface
{
public function __construct(
@@ -30,9 +32,13 @@ abstract class AbstractTrackingAction implements MiddlewareInterface, RequestMet
try {
$shortUrl = $this->urlResolver->resolveEnabledShortUrl($identifier);
$this->requestTracker->trackIfApplicable($shortUrl, $request);
$response = $this->createSuccessResp($shortUrl, $request);
$this->requestTracker->trackIfApplicable($shortUrl, $request->withAttribute(
REDIRECT_URL_REQUEST_ATTRIBUTE,
$response->hasHeader('Location') ? $response->getHeaderLine('Location') : null,
));
return $this->createSuccessResp($shortUrl, $request);
return $response;
} catch (ShortUrlNotFoundException) {
return $this->createErrorResp($request, $handler);
}

View File

@@ -10,7 +10,9 @@ use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Shlinkio\Shlink\Core\Visit\RequestTrackerInterface;
class NotFoundTrackerMiddleware implements MiddlewareInterface
use const Shlinkio\Shlink\REDIRECT_URL_REQUEST_ATTRIBUTE;
readonly class NotFoundTrackerMiddleware implements MiddlewareInterface
{
public function __construct(private RequestTrackerInterface $requestTracker)
{
@@ -18,7 +20,12 @@ class NotFoundTrackerMiddleware implements MiddlewareInterface
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$this->requestTracker->trackNotFoundIfApplicable($request);
return $handler->handle($request);
$response = $handler->handle($request);
$this->requestTracker->trackNotFoundIfApplicable($request->withAttribute(
REDIRECT_URL_REQUEST_ATTRIBUTE,
$response->hasHeader('Location') ? $response->getHeaderLine('Location') : null,
));
return $response;
}
}

View File

@@ -25,6 +25,8 @@ use function implode;
use function sprintf;
use function trim;
use const Shlinkio\Shlink\REDIRECT_URL_REQUEST_ATTRIBUTE;
readonly class ExtraPathRedirectMiddleware implements MiddlewareInterface
{
public function __construct(
@@ -73,9 +75,12 @@ readonly class ExtraPathRedirectMiddleware implements MiddlewareInterface
try {
$shortUrl = $this->resolver->resolveEnabledShortUrl($identifier);
$this->requestTracker->trackIfApplicable($shortUrl, $request);
$longUrl = $this->redirectionBuilder->buildShortUrlRedirect($shortUrl, $request, $extraPath);
$this->requestTracker->trackIfApplicable(
$shortUrl,
$request->withAttribute(REDIRECT_URL_REQUEST_ATTRIBUTE, $longUrl),
);
return $this->redirectResponseHelper->buildRedirectResponse($longUrl);
} catch (ShortUrlNotFoundException) {
if ($extraPath === null || ! $this->urlShortenerOptions->multiSegmentSlugsEnabled) {

View File

@@ -69,7 +69,7 @@ class Visit extends AbstractEntity implements JsonSerializable
potentialBot: $visitor->potentialBot,
remoteAddr: self::processAddress($visitor->remoteAddress, $anonymize),
visitedUrl: $visitor->visitedUrl,
redirectUrl: null, // TODO
redirectUrl: $visitor->redirectUrl,
visitLocation: $geolocation !== null ? VisitLocation::fromGeolocation($geolocation) : null,
);
}

View File

@@ -12,6 +12,7 @@ use function Shlinkio\Shlink\Core\geolocationFromRequest;
use function Shlinkio\Shlink\Core\ipAddressFromRequest;
use function Shlinkio\Shlink\Core\isCrawler;
use function substr;
use const Shlinkio\Shlink\REDIRECT_URL_REQUEST_ATTRIBUTE;
final readonly class Visitor
{
@@ -28,7 +29,7 @@ final readonly class Visitor
public string $visitedUrl,
public bool $potentialBot,
public Location|null $geolocation,
public string $redirectUrl,
public string|null $redirectUrl,
) {
}
@@ -38,7 +39,7 @@ final readonly class Visitor
string|null $remoteAddress = null,
string $visitedUrl = '',
Location|null $geolocation = null,
string $redirectUrl = '',
string|null $redirectUrl = null,
): self {
return new self(
userAgent: self::cropToLength($userAgent, self::USER_AGENT_MAX_LENGTH),
@@ -49,7 +50,7 @@ final readonly class Visitor
visitedUrl: self::cropToLength($visitedUrl, self::VISITED_URL_MAX_LENGTH),
potentialBot: isCrawler($userAgent),
geolocation: $geolocation,
redirectUrl: self::cropToLength($redirectUrl, self::REDIRECT_URL_MAX_LENGTH),
redirectUrl: $redirectUrl === null ? null : self::cropToLength($redirectUrl, self::REDIRECT_URL_MAX_LENGTH),
);
}
@@ -66,8 +67,7 @@ final readonly class Visitor
remoteAddress: ipAddressFromRequest($request),
visitedUrl: $request->getUri()->__toString(),
geolocation: geolocationFromRequest($request),
// TODO
redirectUrl: '',
redirectUrl: $request->getAttribute(REDIRECT_URL_REQUEST_ATTRIBUTE),
);
}