diff --git a/composer.json b/composer.json index 5576c622..ddf090cb 100644 --- a/composer.json +++ b/composer.json @@ -20,9 +20,9 @@ "ext-pdo": "*", "akrabat/ip-address-middleware": "^2.6", "cakephp/chronos": "^3.1", - "doctrine/dbal": "^4.2", - "doctrine/migrations": "^3.8", - "doctrine/orm": "^3.3", + "doctrine/dbal": "^4.3", + "doctrine/migrations": "^3.9", + "doctrine/orm": "^3.5", "donatj/phpuseragentparser": "^1.10", "endroid/qr-code": "^6.0.5", "friendsofphp/proxy-manager-lts": "^1.0", diff --git a/module/Core/src/Visit/Repository/VisitRepository.php b/module/Core/src/Visit/Repository/VisitRepository.php index ff2fba32..e9123743 100644 --- a/module/Core/src/Visit/Repository/VisitRepository.php +++ b/module/Core/src/Visit/Repository/VisitRepository.php @@ -21,7 +21,6 @@ use Shlinkio\Shlink\Core\Visit\Persistence\VisitsListFiltering; use Shlinkio\Shlink\Core\Visit\Persistence\WithDomainVisitsCountFiltering; use Shlinkio\Shlink\Core\Visit\Persistence\WithDomainVisitsListFiltering; use Shlinkio\Shlink\Core\Visit\Spec\CountOfNonOrphanVisits; -use Shlinkio\Shlink\Core\Visit\Spec\CountOfOrphanVisits; use Shlinkio\Shlink\Rest\ApiKey\Role; use Shlinkio\Shlink\Rest\Entity\ApiKey; @@ -161,15 +160,7 @@ class VisitRepository extends EntitySpecificationRepository implements VisitRepo return []; } - $qb = $this->createAllVisitsQueryBuilder($filtering); - $qb->andWhere($qb->expr()->isNull('v.shortUrl')); - - // Parameters in this query need to be inlined, not bound, as we need to use it as sub-query later - if ($filtering->type) { - $conn = $this->getEntityManager()->getConnection(); - $qb->andWhere($qb->expr()->eq('v.type', $conn->quote($filtering->type->value))); - } - + $qb = $this->createOrphanVisitsQueryBuilder($filtering); return $this->resolveVisitsWithNativeQuery($qb, $filtering->limit, $filtering->offset); } @@ -179,7 +170,32 @@ class VisitRepository extends EntitySpecificationRepository implements VisitRepo return 0; } - return (int) $this->matchSingleScalarResult(new CountOfOrphanVisits($filtering)); + $qb = $this->createOrphanVisitsQueryBuilder($filtering); + $qb->select('COUNT(v.id)'); + + return (int) $qb->getQuery()->getSingleScalarResult(); + } + + private function createOrphanVisitsQueryBuilder(OrphanVisitsCountFiltering $filtering): QueryBuilder + { + $qb = $this->createAllVisitsQueryBuilder($filtering); + $qb->andWhere($qb->expr()->isNull('v.shortUrl')); + + // Parameters in this query need to be inlined, not bound, as we need to use it as sub-query later + $conn = $this->getEntityManager()->getConnection(); + + if ($filtering->type) { + $qb->andWhere($qb->expr()->eq('v.type', $conn->quote($filtering->type->value))); + } + + $domain = $filtering->domain; + if ($domain === Domain::DEFAULT_AUTHORITY) { + // TODO + } elseif ($domain !== null) { + $qb->andWhere($qb->expr()->like('v.visitedUrl', $conn->quote('%' . $domain . '%'))); + } + + return $qb; } /** diff --git a/module/Core/src/Visit/Spec/CountOfOrphanVisits.php b/module/Core/src/Visit/Spec/CountOfOrphanVisits.php deleted file mode 100644 index 9d9cab56..00000000 --- a/module/Core/src/Visit/Spec/CountOfOrphanVisits.php +++ /dev/null @@ -1,37 +0,0 @@ -filtering->dateRange), - ]; - - if ($this->filtering->excludeBots) { - $conditions[] = Spec::eq('potentialBot', false); - } - - if ($this->filtering->type) { - $conditions[] = Spec::eq('type', $this->filtering->type->value); - } - - return Spec::countOf(Spec::andX(...$conditions)); - } -} diff --git a/module/Core/test-db/Visit/Repository/VisitRepositoryTest.php b/module/Core/test-db/Visit/Repository/VisitRepositoryTest.php index 523db21d..6aa4f7b7 100644 --- a/module/Core/test-db/Visit/Repository/VisitRepositoryTest.php +++ b/module/Core/test-db/Visit/Repository/VisitRepositoryTest.php @@ -399,7 +399,7 @@ class VisitRepositoryTest extends DatabaseTestCase Chronos::parse(sprintf('2020-01-0%s', $i + 1)), )); $this->getEntityManager()->persist($this->setDateOnVisit( - fn () => Visit::forRegularNotFound(Visitor::empty()), + fn () => Visit::forRegularNotFound(Visitor::fromParams(visitedUrl: 'https://example.com/foo?1=2')), Chronos::parse(sprintf('2020-01-0%s', $i + 1)), )); @@ -438,6 +438,7 @@ class VisitRepositoryTest extends DatabaseTestCase type: OrphanVisitType::BASE_URL, limit: 4, ))); + self::assertCount(6, $this->repo->findOrphanVisits(new OrphanVisitsListFiltering(domain: 'example.com'))); } #[Test] @@ -457,7 +458,7 @@ class VisitRepositoryTest extends DatabaseTestCase Chronos::parse(sprintf('2020-01-0%s', $i + 1)), )); $this->getEntityManager()->persist($this->setDateOnVisit( - fn () => Visit::forRegularNotFound(Visitor::empty()), + fn () => Visit::forRegularNotFound(Visitor::fromParams(visitedUrl: 'https://example.com/foo/bar')), Chronos::parse(sprintf('2020-01-0%s', $i + 1)), )); } @@ -486,6 +487,9 @@ class VisitRepositoryTest extends DatabaseTestCase self::assertEquals(6, $this->repo->countOrphanVisits(new OrphanVisitsCountFiltering( type: OrphanVisitType::REGULAR_404, ))); + self::assertEquals(6, $this->repo->countOrphanVisits(new OrphanVisitsCountFiltering( + domain: 'example.com', + ))); } #[Test]