diff --git a/config/autoload/app_options.global.php b/config/autoload/app_options.global.php index f64f9cff..0b7ec937 100644 --- a/config/autoload/app_options.global.php +++ b/config/autoload/app_options.global.php @@ -7,7 +7,6 @@ return [ 'app_options' => [ 'name' => 'Shlink', 'version' => '%SHLINK_VERSION%', - 'disable_track_param' => null, ], ]; diff --git a/config/autoload/tracking.global.php b/config/autoload/tracking.global.php new file mode 100644 index 00000000..1ddec8b8 --- /dev/null +++ b/config/autoload/tracking.global.php @@ -0,0 +1,34 @@ + [ + // Tells if IP addresses should be anonymized before persisting, to fulfil data protection regulations + // This applies only if IP address tracking is enabled + 'anonymize_remote_addr' => true, + + // Tells if visits to not-found URLs should be tracked. The disable_tracking option takes precedence + 'track_orphan_visits' => true, + + // A query param that, if provided, will disable tracking of one particular visit. Always takes precedence + 'disable_track_param' => null, + + // If true, visits will not be tracked at all + 'disable_tracking' => false, + + // If true, visits will be tracked, but neither the IP address or the location will be resolved + 'disable_ip_tracking' => false, + + // If true, visits will be tracked including the IP address, but the location won't be resolved + 'disable_location_tracking' => false, + + // If true, the referrer will not be tracked + 'disable_referrer_tracking' => false, + + // If true, the user agent will not be tracked + 'disable_ua_tracking' => false, + ], + +]; diff --git a/config/autoload/url-shortener.global.php b/config/autoload/url-shortener.global.php index 3751b1e9..d7cd8b02 100644 --- a/config/autoload/url-shortener.global.php +++ b/config/autoload/url-shortener.global.php @@ -14,13 +14,11 @@ return [ 'hostname' => '', ], 'validate_url' => false, // Deprecated - 'anonymize_remote_addr' => true, 'visits_webhooks' => [], 'default_short_codes_length' => DEFAULT_SHORT_CODES_LENGTH, 'redirect_status_code' => DEFAULT_REDIRECT_STATUS_CODE, 'redirect_cache_lifetime' => DEFAULT_REDIRECT_CACHE_LIFETIME, 'auto_resolve_titles' => false, - 'track_orphan_visits' => true, ], ]; diff --git a/docker/config/shlink_in_docker.local.php b/docker/config/shlink_in_docker.local.php index acecda14..b467237e 100644 --- a/docker/config/shlink_in_docker.local.php +++ b/docker/config/shlink_in_docker.local.php @@ -94,10 +94,6 @@ $helper = new class { return [ - 'app_options' => [ - 'disable_track_param' => env('DISABLE_TRACK_PARAM'), - ], - 'delete_short_urls' => [ 'check_visits_threshold' => true, 'visits_threshold' => (int) env('DELETE_SHORT_URL_THRESHOLD', DEFAULT_DELETE_SHORT_URL_THRESHOLD), @@ -113,13 +109,22 @@ return [ 'hostname' => env('SHORT_DOMAIN_HOST', ''), ], 'validate_url' => (bool) env('VALIDATE_URLS', false), - 'anonymize_remote_addr' => (bool) env('ANONYMIZE_REMOTE_ADDR', true), 'visits_webhooks' => $helper->getVisitsWebhooks(), 'default_short_codes_length' => $helper->getDefaultShortCodesLength(), 'redirect_status_code' => (int) env('REDIRECT_STATUS_CODE', DEFAULT_REDIRECT_STATUS_CODE), 'redirect_cache_lifetime' => (int) env('REDIRECT_CACHE_LIFETIME', DEFAULT_REDIRECT_CACHE_LIFETIME), 'auto_resolve_titles' => (bool) env('AUTO_RESOLVE_TITLES', false), + ], + + 'tracking' => [ + 'anonymize_remote_addr' => (bool) env('ANONYMIZE_REMOTE_ADDR', true), 'track_orphan_visits' => (bool) env('TRACK_ORPHAN_VISITS', true), + 'disable_track_param' => env('DISABLE_TRACK_PARAM'), + 'disable_tracking' => (bool) env('DISABLE_TRACKING', false), + 'disable_ip_tracking' => (bool) env('DISABLE_IP_TRACKING', false), + 'disable_location_tracking' => (bool) env('DISABLE_LOCATION_TRACKING', false), + 'disable_referrer_tracking' => (bool) env('DISABLE_REFERRER_TRACKING', false), + 'disable_ua_tracking' => (bool) env('DISABLE_UA_TRACKING', false), ], 'not_found_redirects' => $helper->getNotFoundRedirectsConfig(), diff --git a/module/Core/config/dependencies.config.php b/module/Core/config/dependencies.config.php index 479b497a..b84c74a4 100644 --- a/module/Core/config/dependencies.config.php +++ b/module/Core/config/dependencies.config.php @@ -24,6 +24,7 @@ return [ Options\DeleteShortUrlsOptions::class => ConfigAbstractFactory::class, Options\NotFoundRedirectOptions::class => ConfigAbstractFactory::class, Options\UrlShortenerOptions::class => ConfigAbstractFactory::class, + Options\TrackingOptions::class => ConfigAbstractFactory::class, Service\UrlShortener::class => ConfigAbstractFactory::class, Service\ShortUrlService::class => ConfigAbstractFactory::class, @@ -75,6 +76,7 @@ return [ Options\DeleteShortUrlsOptions::class => ['config.delete_short_urls'], Options\NotFoundRedirectOptions::class => ['config.not_found_redirects'], Options\UrlShortenerOptions::class => ['config.url_shortener'], + Options\TrackingOptions::class => ['config.tracking'], Service\UrlShortener::class => [ ShortUrl\Helper\ShortUrlTitleResolutionHelper::class, @@ -85,7 +87,7 @@ return [ Visit\VisitsTracker::class => [ 'em', EventDispatcherInterface::class, - Options\UrlShortenerOptions::class, + Options\TrackingOptions::class, ], Service\ShortUrlService::class => [ 'em', @@ -112,14 +114,14 @@ return [ Action\RedirectAction::class => [ Service\ShortUrl\ShortUrlResolver::class, Visit\VisitsTracker::class, - Options\AppOptions::class, + Options\TrackingOptions::class, Util\RedirectResponseHelper::class, 'Logger_Shlink', ], Action\PixelAction::class => [ Service\ShortUrl\ShortUrlResolver::class, Visit\VisitsTracker::class, - Options\AppOptions::class, + Options\TrackingOptions::class, 'Logger_Shlink', ], Action\QrCodeAction::class => [ diff --git a/module/Core/src/Action/AbstractTrackingAction.php b/module/Core/src/Action/AbstractTrackingAction.php index b6a119b2..567e930c 100644 --- a/module/Core/src/Action/AbstractTrackingAction.php +++ b/module/Core/src/Action/AbstractTrackingAction.php @@ -18,7 +18,7 @@ use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException; use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier; use Shlinkio\Shlink\Core\Model\Visitor; -use Shlinkio\Shlink\Core\Options\AppOptions; +use Shlinkio\Shlink\Core\Options\TrackingOptions; use Shlinkio\Shlink\Core\Service\ShortUrl\ShortUrlResolverInterface; use Shlinkio\Shlink\Core\Visit\VisitsTrackerInterface; @@ -29,18 +29,18 @@ abstract class AbstractTrackingAction implements MiddlewareInterface, RequestMet { private ShortUrlResolverInterface $urlResolver; private VisitsTrackerInterface $visitTracker; - private AppOptions $appOptions; + private TrackingOptions $trackingOptions; private LoggerInterface $logger; public function __construct( ShortUrlResolverInterface $urlResolver, VisitsTrackerInterface $visitTracker, - AppOptions $appOptions, + TrackingOptions $trackingOptions, ?LoggerInterface $logger = null ) { $this->urlResolver = $urlResolver; $this->visitTracker = $visitTracker; - $this->appOptions = $appOptions; + $this->trackingOptions = $trackingOptions; $this->logger = $logger ?? new NullLogger(); } @@ -48,7 +48,7 @@ abstract class AbstractTrackingAction implements MiddlewareInterface, RequestMet { $identifier = ShortUrlIdentifier::fromRedirectRequest($request); $query = $request->getQueryParams(); - $disableTrackParam = $this->appOptions->getDisableTrackParam(); + $disableTrackParam = $this->trackingOptions->getDisableTrackParam(); try { $shortUrl = $this->urlResolver->resolveEnabledShortUrl($identifier); diff --git a/module/Core/src/Action/RedirectAction.php b/module/Core/src/Action/RedirectAction.php index d346456b..7da67b59 100644 --- a/module/Core/src/Action/RedirectAction.php +++ b/module/Core/src/Action/RedirectAction.php @@ -21,11 +21,11 @@ class RedirectAction extends AbstractTrackingAction implements StatusCodeInterfa public function __construct( ShortUrlResolverInterface $urlResolver, VisitsTrackerInterface $visitTracker, - Options\AppOptions $appOptions, + Options\TrackingOptions $trackingOptions, RedirectResponseHelperInterface $redirectResponseHelper, ?LoggerInterface $logger = null ) { - parent::__construct($urlResolver, $visitTracker, $appOptions, $logger); + parent::__construct($urlResolver, $visitTracker, $trackingOptions, $logger); $this->redirectResponseHelper = $redirectResponseHelper; } diff --git a/module/Core/src/Config/DeprecatedConfigParser.php b/module/Core/src/Config/DeprecatedConfigParser.php index 92074bfc..b3421146 100644 --- a/module/Core/src/Config/DeprecatedConfigParser.php +++ b/module/Core/src/Config/DeprecatedConfigParser.php @@ -6,6 +6,7 @@ namespace Shlinkio\Shlink\Core\Config; use function Functional\compose; +/** @deprecated */ class DeprecatedConfigParser { public function __invoke(array $config): array diff --git a/module/Core/src/Config/SimplifiedConfigParser.php b/module/Core/src/Config/SimplifiedConfigParser.php index b578799b..2b0b1d71 100644 --- a/module/Core/src/Config/SimplifiedConfigParser.php +++ b/module/Core/src/Config/SimplifiedConfigParser.php @@ -19,7 +19,7 @@ use function uksort; class SimplifiedConfigParser { private const SIMPLIFIED_CONFIG_MAPPING = [ - 'disable_track_param' => ['app_options', 'disable_track_param'], + 'disable_track_param' => ['tracking', 'disable_track_param'], 'short_domain_schema' => ['url_shortener', 'domain', 'schema'], 'short_domain_host' => ['url_shortener', 'domain', 'hostname'], 'validate_url' => ['url_shortener', 'validate_url'], @@ -38,7 +38,7 @@ class SimplifiedConfigParser 'mercure_public_hub_url' => ['mercure', 'public_hub_url'], 'mercure_internal_hub_url' => ['mercure', 'internal_hub_url'], 'mercure_jwt_secret' => ['mercure', 'jwt_secret'], - 'anonymize_remote_addr' => ['url_shortener', 'anonymize_remote_addr'], + 'anonymize_remote_addr' => ['tracking', 'anonymize_remote_addr'], 'redirect_status_code' => ['url_shortener', 'redirect_status_code'], 'redirect_cache_lifetime' => ['url_shortener', 'redirect_cache_lifetime'], 'port' => ['mezzio-swoole', 'swoole-http-server', 'port'], diff --git a/module/Core/src/Options/AppOptions.php b/module/Core/src/Options/AppOptions.php index 66d76126..8fde2663 100644 --- a/module/Core/src/Options/AppOptions.php +++ b/module/Core/src/Options/AppOptions.php @@ -12,7 +12,6 @@ class AppOptions extends AbstractOptions { private string $name = ''; private string $version = '1.0'; - private ?string $disableTrackParam = null; public function getName(): string { @@ -36,16 +35,10 @@ class AppOptions extends AbstractOptions return $this; } - /** - */ - public function getDisableTrackParam(): ?string - { - return $this->disableTrackParam; - } - + /** @deprecated */ protected function setDisableTrackParam(?string $disableTrackParam): self { - $this->disableTrackParam = $disableTrackParam; + // Keep just for backwards compatibility during hydration return $this; } diff --git a/module/Core/src/Options/TrackingOptions.php b/module/Core/src/Options/TrackingOptions.php new file mode 100644 index 00000000..0e1762b0 --- /dev/null +++ b/module/Core/src/Options/TrackingOptions.php @@ -0,0 +1,99 @@ +anonymizeRemoteAddr; + } + + protected function setAnonymizeRemoteAddr(bool $anonymizeRemoteAddr): void + { + $this->anonymizeRemoteAddr = $anonymizeRemoteAddr; + } + + public function trackOrphanVisits(): bool + { + return $this->trackOrphanVisits; + } + + protected function setTrackOrphanVisits(bool $trackOrphanVisits): void + { + $this->trackOrphanVisits = $trackOrphanVisits; + } + + public function getDisableTrackParam(): ?string + { + return $this->disableTrackParam; + } + + protected function setDisableTrackParam(?string $disableTrackParam): void + { + $this->disableTrackParam = $disableTrackParam; + } + + public function disableTracking(): bool + { + return $this->disableTracking; + } + + protected function setDisableTracking(bool $disableTracking): void + { + $this->disableTracking = $disableTracking; + } + + public function disableIpTracking(): bool + { + return $this->disableIpTracking; + } + + protected function setDisableIpTracking(bool $disableIpTracking): void + { + $this->disableIpTracking = $disableIpTracking; + } + + public function disableLocationTracking(): bool + { + return $this->disableLocationTracking; + } + + protected function setDisableLocationTracking(bool $disableLocationTracking): void + { + $this->disableLocationTracking = $disableLocationTracking; + } + + public function disableReferrerTracking(): bool + { + return $this->disableReferrerTracking; + } + + protected function setDisableReferrerTracking(bool $disableReferrerTracking): void + { + $this->disableReferrerTracking = $disableReferrerTracking; + } + + public function disableUaTracking(): bool + { + return $this->disableUaTracking; + } + + protected function setDisableUaTracking(bool $disableUaTracking): void + { + $this->disableUaTracking = $disableUaTracking; + } +} diff --git a/module/Core/src/Options/UrlShortenerOptions.php b/module/Core/src/Options/UrlShortenerOptions.php index e1956203..a0005da2 100644 --- a/module/Core/src/Options/UrlShortenerOptions.php +++ b/module/Core/src/Options/UrlShortenerOptions.php @@ -19,8 +19,6 @@ class UrlShortenerOptions extends AbstractOptions private int $redirectStatusCode = DEFAULT_REDIRECT_STATUS_CODE; private int $redirectCacheLifetime = DEFAULT_REDIRECT_CACHE_LIFETIME; private bool $autoResolveTitles = false; - private bool $anonymizeRemoteAddr = true; - private bool $trackOrphanVisits = true; public function isUrlValidationEnabled(): bool { @@ -69,23 +67,15 @@ class UrlShortenerOptions extends AbstractOptions $this->autoResolveTitles = $autoResolveTitles; } - public function anonymizeRemoteAddr(): bool - { - return $this->anonymizeRemoteAddr; - } - + /** @deprecated */ protected function setAnonymizeRemoteAddr(bool $anonymizeRemoteAddr): void { - $this->anonymizeRemoteAddr = $anonymizeRemoteAddr; - } - - public function trackOrphanVisits(): bool - { - return $this->trackOrphanVisits; + // Keep just for backwards compatibility during hydration } + /** @deprecated */ protected function setTrackOrphanVisits(bool $trackOrphanVisits): void { - $this->trackOrphanVisits = $trackOrphanVisits; + // Keep just for backwards compatibility during hydration } } diff --git a/module/Core/src/Visit/VisitsTracker.php b/module/Core/src/Visit/VisitsTracker.php index f8c82b49..fb820b24 100644 --- a/module/Core/src/Visit/VisitsTracker.php +++ b/module/Core/src/Visit/VisitsTracker.php @@ -10,18 +10,18 @@ use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Entity\Visit; use Shlinkio\Shlink\Core\EventDispatcher\Event\UrlVisited; use Shlinkio\Shlink\Core\Model\Visitor; -use Shlinkio\Shlink\Core\Options\UrlShortenerOptions; +use Shlinkio\Shlink\Core\Options\TrackingOptions; class VisitsTracker implements VisitsTrackerInterface { private ORM\EntityManagerInterface $em; private EventDispatcherInterface $eventDispatcher; - private UrlShortenerOptions $options; + private TrackingOptions $options; public function __construct( ORM\EntityManagerInterface $em, EventDispatcherInterface $eventDispatcher, - UrlShortenerOptions $options + TrackingOptions $options ) { $this->em = $em; $this->eventDispatcher = $eventDispatcher; diff --git a/module/Core/test/Action/PixelActionTest.php b/module/Core/test/Action/PixelActionTest.php index 065cc2c4..6df2498a 100644 --- a/module/Core/test/Action/PixelActionTest.php +++ b/module/Core/test/Action/PixelActionTest.php @@ -14,7 +14,7 @@ use Shlinkio\Shlink\Common\Response\PixelResponse; use Shlinkio\Shlink\Core\Action\PixelAction; use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier; -use Shlinkio\Shlink\Core\Options\AppOptions; +use Shlinkio\Shlink\Core\Options\TrackingOptions; use Shlinkio\Shlink\Core\Service\ShortUrl\ShortUrlResolverInterface; use Shlinkio\Shlink\Core\Visit\VisitsTracker; @@ -34,7 +34,7 @@ class PixelActionTest extends TestCase $this->action = new PixelAction( $this->urlResolver->reveal(), $this->visitTracker->reveal(), - new AppOptions(), + new TrackingOptions(), ); } diff --git a/module/Core/test/Action/RedirectActionTest.php b/module/Core/test/Action/RedirectActionTest.php index f869e2c4..dde9144c 100644 --- a/module/Core/test/Action/RedirectActionTest.php +++ b/module/Core/test/Action/RedirectActionTest.php @@ -42,7 +42,7 @@ class RedirectActionTest extends TestCase $this->action = new RedirectAction( $this->urlResolver->reveal(), $this->visitTracker->reveal(), - new Options\AppOptions(['disableTrackParam' => 'foobar']), + new Options\TrackingOptions(['disableTrackParam' => 'foobar']), $this->redirectRespHelper->reveal(), ); } diff --git a/module/Core/test/Config/SimplifiedConfigParserTest.php b/module/Core/test/Config/SimplifiedConfigParserTest.php index 6f040bb6..f4e5c8f0 100644 --- a/module/Core/test/Config/SimplifiedConfigParserTest.php +++ b/module/Core/test/Config/SimplifiedConfigParserTest.php @@ -22,7 +22,7 @@ class SimplifiedConfigParserTest extends TestCase public function properlyMapsSimplifiedConfig(): void { $config = [ - 'app_options' => [ + 'tracking' => [ 'disable_track_param' => 'foo', ], @@ -70,8 +70,9 @@ class SimplifiedConfigParserTest extends TestCase 'port' => 8888, ]; $expected = [ - 'app_options' => [ + 'tracking' => [ 'disable_track_param' => 'bar', + 'anonymize_remote_addr' => false, ], 'entity_manager' => [ @@ -96,7 +97,6 @@ class SimplifiedConfigParserTest extends TestCase 'https://third-party.io/foo', ], 'default_short_codes_length' => 8, - 'anonymize_remote_addr' => false, 'redirect_status_code' => 301, 'redirect_cache_lifetime' => 90, ], diff --git a/module/Core/test/Visit/VisitsTrackerTest.php b/module/Core/test/Visit/VisitsTrackerTest.php index bba4e919..e39a4522 100644 --- a/module/Core/test/Visit/VisitsTrackerTest.php +++ b/module/Core/test/Visit/VisitsTrackerTest.php @@ -14,7 +14,7 @@ use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Entity\Visit; use Shlinkio\Shlink\Core\EventDispatcher\Event\UrlVisited; use Shlinkio\Shlink\Core\Model\Visitor; -use Shlinkio\Shlink\Core\Options\UrlShortenerOptions; +use Shlinkio\Shlink\Core\Options\TrackingOptions; use Shlinkio\Shlink\Core\Visit\VisitsTracker; class VisitsTrackerTest extends TestCase @@ -24,7 +24,7 @@ class VisitsTrackerTest extends TestCase private VisitsTracker $visitsTracker; private ObjectProphecy $em; private ObjectProphecy $eventDispatcher; - private UrlShortenerOptions $options; + private TrackingOptions $options; public function setUp(): void { @@ -35,7 +35,7 @@ class VisitsTrackerTest extends TestCase }); $this->eventDispatcher = $this->prophesize(EventDispatcherInterface::class); - $this->options = new UrlShortenerOptions(); + $this->options = new TrackingOptions(); $this->visitsTracker = new VisitsTracker($this->em->reveal(), $this->eventDispatcher->reveal(), $this->options); }