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_COLOR = '#000000'; // Black
const DEFAULT_QR_CODE_BG_COLOR = '#ffffff'; // White const DEFAULT_QR_CODE_BG_COLOR = '#ffffff'; // White
const IP_ADDRESS_REQUEST_ATTRIBUTE = 'remote_address'; 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\ShortUrl\ShortUrlResolverInterface;
use Shlinkio\Shlink\Core\Visit\RequestTrackerInterface; use Shlinkio\Shlink\Core\Visit\RequestTrackerInterface;
use const Shlinkio\Shlink\REDIRECT_URL_REQUEST_ATTRIBUTE;
abstract class AbstractTrackingAction implements MiddlewareInterface, RequestMethodInterface abstract class AbstractTrackingAction implements MiddlewareInterface, RequestMethodInterface
{ {
public function __construct( public function __construct(
@@ -30,9 +32,13 @@ abstract class AbstractTrackingAction implements MiddlewareInterface, RequestMet
try { try {
$shortUrl = $this->urlResolver->resolveEnabledShortUrl($identifier); $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) { } catch (ShortUrlNotFoundException) {
return $this->createErrorResp($request, $handler); return $this->createErrorResp($request, $handler);
} }

View File

@@ -10,7 +10,9 @@ use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface; use Psr\Http\Server\RequestHandlerInterface;
use Shlinkio\Shlink\Core\Visit\RequestTrackerInterface; 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) public function __construct(private RequestTrackerInterface $requestTracker)
{ {
@@ -18,7 +20,12 @@ class NotFoundTrackerMiddleware implements MiddlewareInterface
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{ {
$this->requestTracker->trackNotFoundIfApplicable($request); $response = $handler->handle($request);
return $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 sprintf;
use function trim; use function trim;
use const Shlinkio\Shlink\REDIRECT_URL_REQUEST_ATTRIBUTE;
readonly class ExtraPathRedirectMiddleware implements MiddlewareInterface readonly class ExtraPathRedirectMiddleware implements MiddlewareInterface
{ {
public function __construct( public function __construct(
@@ -73,9 +75,12 @@ readonly class ExtraPathRedirectMiddleware implements MiddlewareInterface
try { try {
$shortUrl = $this->resolver->resolveEnabledShortUrl($identifier); $shortUrl = $this->resolver->resolveEnabledShortUrl($identifier);
$this->requestTracker->trackIfApplicable($shortUrl, $request);
$longUrl = $this->redirectionBuilder->buildShortUrlRedirect($shortUrl, $request, $extraPath); $longUrl = $this->redirectionBuilder->buildShortUrlRedirect($shortUrl, $request, $extraPath);
$this->requestTracker->trackIfApplicable(
$shortUrl,
$request->withAttribute(REDIRECT_URL_REQUEST_ATTRIBUTE, $longUrl),
);
return $this->redirectResponseHelper->buildRedirectResponse($longUrl); return $this->redirectResponseHelper->buildRedirectResponse($longUrl);
} catch (ShortUrlNotFoundException) { } catch (ShortUrlNotFoundException) {
if ($extraPath === null || ! $this->urlShortenerOptions->multiSegmentSlugsEnabled) { if ($extraPath === null || ! $this->urlShortenerOptions->multiSegmentSlugsEnabled) {

View File

@@ -69,7 +69,7 @@ class Visit extends AbstractEntity implements JsonSerializable
potentialBot: $visitor->potentialBot, potentialBot: $visitor->potentialBot,
remoteAddr: self::processAddress($visitor->remoteAddress, $anonymize), remoteAddr: self::processAddress($visitor->remoteAddress, $anonymize),
visitedUrl: $visitor->visitedUrl, visitedUrl: $visitor->visitedUrl,
redirectUrl: null, // TODO redirectUrl: $visitor->redirectUrl,
visitLocation: $geolocation !== null ? VisitLocation::fromGeolocation($geolocation) : null, 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\ipAddressFromRequest;
use function Shlinkio\Shlink\Core\isCrawler; use function Shlinkio\Shlink\Core\isCrawler;
use function substr; use function substr;
use const Shlinkio\Shlink\REDIRECT_URL_REQUEST_ATTRIBUTE;
final readonly class Visitor final readonly class Visitor
{ {
@@ -28,7 +29,7 @@ final readonly class Visitor
public string $visitedUrl, public string $visitedUrl,
public bool $potentialBot, public bool $potentialBot,
public Location|null $geolocation, public Location|null $geolocation,
public string $redirectUrl, public string|null $redirectUrl,
) { ) {
} }
@@ -38,7 +39,7 @@ final readonly class Visitor
string|null $remoteAddress = null, string|null $remoteAddress = null,
string $visitedUrl = '', string $visitedUrl = '',
Location|null $geolocation = null, Location|null $geolocation = null,
string $redirectUrl = '', string|null $redirectUrl = null,
): self { ): self {
return new self( return new self(
userAgent: self::cropToLength($userAgent, self::USER_AGENT_MAX_LENGTH), 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), visitedUrl: self::cropToLength($visitedUrl, self::VISITED_URL_MAX_LENGTH),
potentialBot: isCrawler($userAgent), potentialBot: isCrawler($userAgent),
geolocation: $geolocation, 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), remoteAddress: ipAddressFromRequest($request),
visitedUrl: $request->getUri()->__toString(), visitedUrl: $request->getUri()->__toString(),
geolocation: geolocationFromRequest($request), geolocation: geolocationFromRequest($request),
// TODO redirectUrl: $request->getAttribute(REDIRECT_URL_REQUEST_ATTRIBUTE),
redirectUrl: '',
); );
} }