Browse Source

Added option to disable orphan visitstracking

Alejandro Celaya 3 years ago
parent
commit
2fc6fb0a9a

+ 1 - 0
config/autoload/url-shortener.global.php

@@ -20,6 +20,7 @@ return [
         'redirect_status_code' => DEFAULT_REDIRECT_STATUS_CODE,
         'redirect_cache_lifetime' => DEFAULT_REDIRECT_CACHE_LIFETIME,
         'auto_resolve_titles' => false,
+        'track_orphan_visits' => true,
     ],
 
 ];

+ 1 - 0
docker/config/shlink_in_docker.local.php

@@ -126,6 +126,7 @@ return [
         '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),
+        'track_orphan_visits' => (bool) env('TRACK_ORPHAN_VISITS', true),
     ],
 
     'not_found_redirects' => $helper->getNotFoundRedirectsConfig(),

+ 1 - 1
module/Core/config/dependencies.config.php

@@ -85,7 +85,7 @@ return [
         Visit\VisitsTracker::class => [
             'em',
             EventDispatcherInterface::class,
-            'config.url_shortener.anonymize_remote_addr',
+            Options\UrlShortenerOptions::class,
         ],
         Service\ShortUrlService::class => [
             'em',

+ 23 - 2
module/Core/src/Options/UrlShortenerOptions.php

@@ -19,6 +19,8 @@ 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
     {
@@ -62,9 +64,28 @@ class UrlShortenerOptions extends AbstractOptions
         return $this->autoResolveTitles;
     }
 
-    protected function setAutoResolveTitles(bool $autoResolveTitles): self
+    protected function setAutoResolveTitles(bool $autoResolveTitles): void
     {
         $this->autoResolveTitles = $autoResolveTitles;
-        return $this;
+    }
+
+    public function anonymizeRemoteAddr(): bool
+    {
+        return $this->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;
     }
 }

+ 23 - 7
module/Core/src/Visit/VisitsTracker.php

@@ -10,41 +10,57 @@ 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;
 
 class VisitsTracker implements VisitsTrackerInterface
 {
     private ORM\EntityManagerInterface $em;
     private EventDispatcherInterface $eventDispatcher;
-    private bool $anonymizeRemoteAddr;
+    private UrlShortenerOptions $options;
 
     public function __construct(
         ORM\EntityManagerInterface $em,
         EventDispatcherInterface $eventDispatcher,
-        bool $anonymizeRemoteAddr
+        UrlShortenerOptions $options
     ) {
         $this->em = $em;
         $this->eventDispatcher = $eventDispatcher;
-        $this->anonymizeRemoteAddr = $anonymizeRemoteAddr;
+        $this->options = $options;
     }
 
     public function track(ShortUrl $shortUrl, Visitor $visitor): void
     {
-        $this->trackVisit(Visit::forValidShortUrl($shortUrl, $visitor, $this->anonymizeRemoteAddr), $visitor);
+        $this->trackVisit(
+            Visit::forValidShortUrl($shortUrl, $visitor, $this->options->anonymizeRemoteAddr()),
+            $visitor,
+        );
     }
 
     public function trackInvalidShortUrlVisit(Visitor $visitor): void
     {
-        $this->trackVisit(Visit::forInvalidShortUrl($visitor, $this->anonymizeRemoteAddr), $visitor);
+        if (! $this->options->trackOrphanVisits()) {
+            return;
+        }
+
+        $this->trackVisit(Visit::forInvalidShortUrl($visitor, $this->options->anonymizeRemoteAddr()), $visitor);
     }
 
     public function trackBaseUrlVisit(Visitor $visitor): void
     {
-        $this->trackVisit(Visit::forBasePath($visitor, $this->anonymizeRemoteAddr), $visitor);
+        if (! $this->options->trackOrphanVisits()) {
+            return;
+        }
+
+        $this->trackVisit(Visit::forBasePath($visitor, $this->options->anonymizeRemoteAddr()), $visitor);
     }
 
     public function trackRegularNotFoundVisit(Visitor $visitor): void
     {
-        $this->trackVisit(Visit::forRegularNotFound($visitor, $this->anonymizeRemoteAddr), $visitor);
+        if (! $this->options->trackOrphanVisits()) {
+            return;
+        }
+
+        $this->trackVisit(Visit::forRegularNotFound($visitor, $this->options->anonymizeRemoteAddr()), $visitor);
     }
 
     private function trackVisit(Visit $visit, Visitor $visitor): void

+ 26 - 1
module/Core/test/Visit/VisitsTrackerTest.php

@@ -14,6 +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\Visit\VisitsTracker;
 
 class VisitsTrackerTest extends TestCase
@@ -23,13 +24,15 @@ class VisitsTrackerTest extends TestCase
     private VisitsTracker $visitsTracker;
     private ObjectProphecy $em;
     private ObjectProphecy $eventDispatcher;
+    private UrlShortenerOptions $options;
 
     public function setUp(): void
     {
         $this->em = $this->prophesize(EntityManager::class);
         $this->eventDispatcher = $this->prophesize(EventDispatcherInterface::class);
+        $this->options = new UrlShortenerOptions();
 
-        $this->visitsTracker = new VisitsTracker($this->em->reveal(), $this->eventDispatcher->reveal(), true);
+        $this->visitsTracker = new VisitsTracker($this->em->reveal(), $this->eventDispatcher->reveal(), $this->options);
     }
 
     /**
@@ -53,4 +56,26 @@ class VisitsTrackerTest extends TestCase
         yield 'trackBaseUrlVisit' => ['trackBaseUrlVisit', [Visitor::emptyInstance()]];
         yield 'trackRegularNotFoundVisit' => ['trackRegularNotFoundVisit', [Visitor::emptyInstance()]];
     }
+
+    /**
+     * @test
+     * @dataProvider provideOrphanTrackingMethodNames
+     */
+    public function orphanVisitsAreNotTrackedWhenDisabled(string $method): void
+    {
+        $this->options->trackOrphanVisits = false;
+
+        $this->visitsTracker->{$method}(Visitor::emptyInstance());
+
+        $this->eventDispatcher->dispatch(Argument::cetera())->shouldNotHaveBeenCalled();
+        $this->em->persist(Argument::cetera())->shouldNotHaveBeenCalled();
+        $this->em->flush()->shouldNotHaveBeenCalled();
+    }
+
+    public function provideOrphanTrackingMethodNames(): iterable
+    {
+        yield 'trackInvalidShortUrlVisit' => ['trackInvalidShortUrlVisit'];
+        yield 'trackBaseUrlVisit' => ['trackBaseUrlVisit'];
+        yield 'trackRegularNotFoundVisit' => ['trackRegularNotFoundVisit'];
+    }
 }