Added option to disable orphan visitstracking

This commit is contained in:
Alejandro Celaya 2021-02-10 20:09:25 +01:00
parent a18486cc2e
commit 2fc6fb0a9a
6 changed files with 75 additions and 11 deletions

View File

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

View File

@ -126,6 +126,7 @@ return [
'redirect_status_code' => (int) env('REDIRECT_STATUS_CODE', DEFAULT_REDIRECT_STATUS_CODE), 'redirect_status_code' => (int) env('REDIRECT_STATUS_CODE', DEFAULT_REDIRECT_STATUS_CODE),
'redirect_cache_lifetime' => (int) env('REDIRECT_CACHE_LIFETIME', DEFAULT_REDIRECT_CACHE_LIFETIME), 'redirect_cache_lifetime' => (int) env('REDIRECT_CACHE_LIFETIME', DEFAULT_REDIRECT_CACHE_LIFETIME),
'auto_resolve_titles' => (bool) env('AUTO_RESOLVE_TITLES', false), 'auto_resolve_titles' => (bool) env('AUTO_RESOLVE_TITLES', false),
'track_orphan_visits' => (bool) env('TRACK_ORPHAN_VISITS', true),
], ],
'not_found_redirects' => $helper->getNotFoundRedirectsConfig(), 'not_found_redirects' => $helper->getNotFoundRedirectsConfig(),

View File

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

View File

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

View File

@ -10,41 +10,57 @@ use Shlinkio\Shlink\Core\Entity\ShortUrl;
use Shlinkio\Shlink\Core\Entity\Visit; use Shlinkio\Shlink\Core\Entity\Visit;
use Shlinkio\Shlink\Core\EventDispatcher\Event\UrlVisited; use Shlinkio\Shlink\Core\EventDispatcher\Event\UrlVisited;
use Shlinkio\Shlink\Core\Model\Visitor; use Shlinkio\Shlink\Core\Model\Visitor;
use Shlinkio\Shlink\Core\Options\UrlShortenerOptions;
class VisitsTracker implements VisitsTrackerInterface class VisitsTracker implements VisitsTrackerInterface
{ {
private ORM\EntityManagerInterface $em; private ORM\EntityManagerInterface $em;
private EventDispatcherInterface $eventDispatcher; private EventDispatcherInterface $eventDispatcher;
private bool $anonymizeRemoteAddr; private UrlShortenerOptions $options;
public function __construct( public function __construct(
ORM\EntityManagerInterface $em, ORM\EntityManagerInterface $em,
EventDispatcherInterface $eventDispatcher, EventDispatcherInterface $eventDispatcher,
bool $anonymizeRemoteAddr UrlShortenerOptions $options
) { ) {
$this->em = $em; $this->em = $em;
$this->eventDispatcher = $eventDispatcher; $this->eventDispatcher = $eventDispatcher;
$this->anonymizeRemoteAddr = $anonymizeRemoteAddr; $this->options = $options;
} }
public function track(ShortUrl $shortUrl, Visitor $visitor): void 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 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 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 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 private function trackVisit(Visit $visit, Visitor $visitor): void

View File

@ -14,6 +14,7 @@ use Shlinkio\Shlink\Core\Entity\ShortUrl;
use Shlinkio\Shlink\Core\Entity\Visit; use Shlinkio\Shlink\Core\Entity\Visit;
use Shlinkio\Shlink\Core\EventDispatcher\Event\UrlVisited; use Shlinkio\Shlink\Core\EventDispatcher\Event\UrlVisited;
use Shlinkio\Shlink\Core\Model\Visitor; use Shlinkio\Shlink\Core\Model\Visitor;
use Shlinkio\Shlink\Core\Options\UrlShortenerOptions;
use Shlinkio\Shlink\Core\Visit\VisitsTracker; use Shlinkio\Shlink\Core\Visit\VisitsTracker;
class VisitsTrackerTest extends TestCase class VisitsTrackerTest extends TestCase
@ -23,13 +24,15 @@ class VisitsTrackerTest extends TestCase
private VisitsTracker $visitsTracker; private VisitsTracker $visitsTracker;
private ObjectProphecy $em; private ObjectProphecy $em;
private ObjectProphecy $eventDispatcher; private ObjectProphecy $eventDispatcher;
private UrlShortenerOptions $options;
public function setUp(): void public function setUp(): void
{ {
$this->em = $this->prophesize(EntityManager::class); $this->em = $this->prophesize(EntityManager::class);
$this->eventDispatcher = $this->prophesize(EventDispatcherInterface::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 'trackBaseUrlVisit' => ['trackBaseUrlVisit', [Visitor::emptyInstance()]];
yield 'trackRegularNotFoundVisit' => ['trackRegularNotFoundVisit', [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'];
}
} }