diff --git a/module/Core/src/Model/ShortUrlsParams.php b/module/Core/src/Model/ShortUrlsParams.php index b3761ea8..ac78b807 100644 --- a/module/Core/src/Model/ShortUrlsParams.php +++ b/module/Core/src/Model/ShortUrlsParams.php @@ -14,11 +14,15 @@ use function Shlinkio\Shlink\Core\parseDateField; final class ShortUrlsParams { public const DEFAULT_ITEMS_PER_PAGE = 10; + public const TAGS_MODE_ANY = 'any'; + public const TAGS_MODE_ALL = 'all'; private int $page; private int $itemsPerPage; private ?string $searchTerm; private array $tags; + /** @var self::TAGS_MODE_ANY|self::TAGS_MODE_ALL */ + private string $tagsMode = self::TAGS_MODE_ANY; private ShortUrlsOrdering $orderBy; private ?DateRange $dateRange; @@ -63,6 +67,7 @@ final class ShortUrlsParams $this->itemsPerPage = (int) ( $inputFilter->getValue(ShortUrlsParamsInputFilter::ITEMS_PER_PAGE) ?? self::DEFAULT_ITEMS_PER_PAGE ); + $this->tagsMode = $inputFilter->getValue(ShortUrlsParamsInputFilter::TAGS_MODE) ?? self::TAGS_MODE_ANY; } public function page(): int @@ -94,4 +99,12 @@ final class ShortUrlsParams { return $this->dateRange; } + + /** + * @return self::TAGS_MODE_ANY|self::TAGS_MODE_ALL + */ + public function tagsMode(): string + { + return $this->tagsMode; + } } diff --git a/module/Core/src/Paginator/Adapter/ShortUrlRepositoryAdapter.php b/module/Core/src/Paginator/Adapter/ShortUrlRepositoryAdapter.php index 93b69d33..0be5403b 100644 --- a/module/Core/src/Paginator/Adapter/ShortUrlRepositoryAdapter.php +++ b/module/Core/src/Paginator/Adapter/ShortUrlRepositoryAdapter.php @@ -25,6 +25,7 @@ class ShortUrlRepositoryAdapter implements AdapterInterface $offset, $this->params->searchTerm(), $this->params->tags(), + $this->params->tagsMode(), $this->params->orderBy(), $this->params->dateRange(), $this->apiKey?->spec(), @@ -36,6 +37,7 @@ class ShortUrlRepositoryAdapter implements AdapterInterface return $this->repository->countList( $this->params->searchTerm(), $this->params->tags(), + $this->params->tagsMode(), $this->params->dateRange(), $this->apiKey?->spec(), ); diff --git a/module/Core/src/Repository/ShortUrlRepository.php b/module/Core/src/Repository/ShortUrlRepository.php index e1b9c419..88a79e34 100644 --- a/module/Core/src/Repository/ShortUrlRepository.php +++ b/module/Core/src/Repository/ShortUrlRepository.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Repository; use Doctrine\DBAL\LockMode; +use Doctrine\DBAL\Platforms\PostgreSQLPlatform; use Doctrine\ORM\Query\Expr\Join; use Doctrine\ORM\QueryBuilder; use Happyr\DoctrineSpecification\Repository\EntitySpecificationRepository; @@ -15,6 +16,7 @@ use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier; use Shlinkio\Shlink\Core\Model\ShortUrlMeta; use Shlinkio\Shlink\Core\Model\ShortUrlsOrdering; +use Shlinkio\Shlink\Core\Model\ShortUrlsParams; use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl; use function array_column; @@ -32,11 +34,12 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU ?int $offset = null, ?string $searchTerm = null, array $tags = [], + string $tagsMode = ShortUrlsParams::TAGS_MODE_ANY, ?ShortUrlsOrdering $orderBy = null, ?DateRange $dateRange = null, ?Specification $spec = null, ): array { - $qb = $this->createListQueryBuilder($searchTerm, $tags, $dateRange, $spec); + $qb = $this->createListQueryBuilder($searchTerm, $tags, $tagsMode, $dateRange, $spec); $qb->select('DISTINCT s') ->setMaxResults($limit) ->setFirstResult($offset); @@ -77,10 +80,11 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU public function countList( ?string $searchTerm = null, array $tags = [], + string $tagsMode = ShortUrlsParams::TAGS_MODE_ANY, ?DateRange $dateRange = null, ?Specification $spec = null, ): int { - $qb = $this->createListQueryBuilder($searchTerm, $tags, $dateRange, $spec); + $qb = $this->createListQueryBuilder($searchTerm, $tags, $tagsMode, $dateRange, $spec); $qb->select('COUNT(DISTINCT s)'); return (int) $qb->getQuery()->getSingleScalarResult(); @@ -89,6 +93,7 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU private function createListQueryBuilder( ?string $searchTerm, array $tags, + string $tagsMode, ?DateRange $dateRange, ?Specification $spec, ): QueryBuilder { @@ -139,8 +144,8 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU { // When ordering DESC, Postgres puts nulls at the beginning while the rest of supported DB engines put them at // the bottom - $dbPlatform = $this->getEntityManager()->getConnection()->getDatabasePlatform()->getName(); - $ordering = $dbPlatform === 'postgresql' ? 'ASC' : 'DESC'; + $dbPlatform = $this->getEntityManager()->getConnection()->getDatabasePlatform(); + $ordering = $dbPlatform instanceof PostgreSQLPlatform ? 'ASC' : 'DESC'; $dql = <<add($this->createNumericInput(self::ITEMS_PER_PAGE, false, Paginator::ALL_ITEMS)); $this->add($this->createTagsInput(self::TAGS, false)); + + $tagsMode = $this->createInput(self::TAGS_MODE, false); + $tagsMode->getValidatorChain()->attach(new InArray([ + 'haystack' => [ShortUrlsParams::TAGS_MODE_ALL, ShortUrlsParams::TAGS_MODE_ANY], + 'strict' => InArray::COMPARE_STRICT, + ])); + $this->add($tagsMode); } } diff --git a/module/Core/test-db/Repository/ShortUrlRepositoryTest.php b/module/Core/test-db/Repository/ShortUrlRepositoryTest.php index cff341b3..b7c83a61 100644 --- a/module/Core/test-db/Repository/ShortUrlRepositoryTest.php +++ b/module/Core/test-db/Repository/ShortUrlRepositoryTest.php @@ -14,6 +14,7 @@ use Shlinkio\Shlink\Core\Entity\Visit; use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier; use Shlinkio\Shlink\Core\Model\ShortUrlMeta; use Shlinkio\Shlink\Core\Model\ShortUrlsOrdering; +use Shlinkio\Shlink\Core\Model\ShortUrlsParams; use Shlinkio\Shlink\Core\Model\Visitor; use Shlinkio\Shlink\Core\Repository\ShortUrlRepository; use Shlinkio\Shlink\Core\ShortUrl\Resolver\PersistenceShortUrlRelationResolver; @@ -127,22 +128,31 @@ class ShortUrlRepositoryTest extends DatabaseTestCase self::assertCount(1, $this->repo->findList(2, 2)); - $result = $this->repo->findList(null, null, null, [], ShortUrlsOrdering::fromRawData([ + $tagsModeAll = ShortUrlsParams::TAGS_MODE_ANY; + $result = $this->repo->findList(null, null, null, [], $tagsModeAll, ShortUrlsOrdering::fromRawData([ 'orderBy' => 'visits-DESC', ])); self::assertCount(3, $result); self::assertSame($bar, $result[0]); - $result = $this->repo->findList(null, null, null, [], null, DateRange::withEndDate(Chronos::now()->subDays(2))); + $result = $this->repo->findList(null, null, null, [], $tagsModeAll, null, DateRange::withEndDate( + Chronos::now()->subDays(2), + )); self::assertCount(1, $result); - self::assertEquals(1, $this->repo->countList(null, [], DateRange::withEndDate(Chronos::now()->subDays(2)))); + self::assertEquals(1, $this->repo->countList(null, [], $tagsModeAll, DateRange::withEndDate( + Chronos::now()->subDays(2), + ))); self::assertSame($foo2, $result[0]); self::assertCount( 2, - $this->repo->findList(null, null, null, [], null, DateRange::withStartDate(Chronos::now()->subDays(2))), + $this->repo->findList(null, null, null, [], $tagsModeAll, null, DateRange::withStartDate( + Chronos::now()->subDays(2), + )), ); - self::assertEquals(2, $this->repo->countList(null, [], DateRange::withStartDate(Chronos::now()->subDays(2)))); + self::assertEquals(2, $this->repo->countList(null, [], $tagsModeAll, DateRange::withStartDate( + Chronos::now()->subDays(2), + ))); } /** @test */ @@ -155,9 +165,14 @@ class ShortUrlRepositoryTest extends DatabaseTestCase $this->getEntityManager()->flush(); - $result = $this->repo->findList(null, null, null, [], ShortUrlsOrdering::fromRawData([ - 'orderBy' => 'longUrl-ASC', - ])); + $result = $this->repo->findList( + null, + null, + null, + [], + ShortUrlsParams::TAGS_MODE_ANY, + ShortUrlsOrdering::fromRawData(['orderBy' => 'longUrl-ASC']), + ); self::assertCount(count($urls), $result); self::assertEquals('a', $result[0]->getLongUrl()); diff --git a/module/Core/test/Paginator/Adapter/ShortUrlRepositoryAdapterTest.php b/module/Core/test/Paginator/Adapter/ShortUrlRepositoryAdapterTest.php index 33fdb8f6..99195818 100644 --- a/module/Core/test/Paginator/Adapter/ShortUrlRepositoryAdapterTest.php +++ b/module/Core/test/Paginator/Adapter/ShortUrlRepositoryAdapterTest.php @@ -46,7 +46,8 @@ class ShortUrlRepositoryAdapterTest extends TestCase $orderBy = $params->orderBy(); $dateRange = $params->dateRange(); - $this->repo->findList(10, 5, $searchTerm, $tags, $orderBy, $dateRange, null)->shouldBeCalledOnce(); + $this->repo->findList(10, 5, $searchTerm, $tags, ShortUrlsParams::TAGS_MODE_ANY, $orderBy, $dateRange, null) + ->shouldBeCalledOnce(); $adapter->getSlice(5, 10); } @@ -70,7 +71,8 @@ class ShortUrlRepositoryAdapterTest extends TestCase $adapter = new ShortUrlRepositoryAdapter($this->repo->reveal(), $params, $apiKey); $dateRange = $params->dateRange(); - $this->repo->countList($searchTerm, $tags, $dateRange, $apiKey->spec())->shouldBeCalledOnce(); + $this->repo->countList($searchTerm, $tags, ShortUrlsParams::TAGS_MODE_ANY, $dateRange, $apiKey->spec()) + ->shouldBeCalledOnce(); $adapter->getNbResults(); }