Ensured domain is taken into account when checking if a slug is in use

This commit is contained in:
Alejandro Celaya
2019-10-01 21:42:35 +02:00
parent 8da6b336f5
commit 495643f4f1
9 changed files with 130 additions and 36 deletions

View File

@@ -22,6 +22,8 @@ class PersistenceDomainResolver implements DomainResolverInterface
return null;
}
return $this->em->getRepository(Domain::class)->findOneBy(['authority' => $domain]) ?? new Domain($domain);
/** @var Domain|null $existingDomain */
$existingDomain = $this->em->getRepository(Domain::class)->findOneBy(['authority' => $domain]);
return $existingDomain ?? new Domain($domain);
}
}

View File

@@ -12,7 +12,10 @@ use Shlinkio\Shlink\Core\Domain\Resolver\SimpleDomainResolver;
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
use Zend\Diactoros\Uri;
use function array_reduce;
use function count;
use function Functional\contains;
use function Functional\invoke;
class ShortUrl extends AbstractEntity
{
@@ -161,4 +164,28 @@ class ShortUrl extends AbstractEntity
return $this->domain->getAuthority();
}
public function matchesCriteria(ShortUrlMeta $meta, array $tags): bool
{
if ($meta->hasMaxVisits() && $meta->getMaxVisits() !== $this->maxVisits) {
return false;
}
if ($meta->hasValidSince() && ! $meta->getValidSince()->eq($this->validSince)) {
return false;
}
if ($meta->hasValidUntil() && ! $meta->getValidUntil()->eq($this->validUntil)) {
return false;
}
$shortUrlTags = invoke($this->getTags(), '__toString');
$hasAllTags = count($shortUrlTags) === count($tags) && array_reduce(
$tags,
function (bool $hasAllTags, string $tag) use ($shortUrlTags) {
return $hasAllTags && contains($shortUrlTags, $tag);
},
true
);
return $hasAllTags;
}
}

View File

@@ -7,8 +7,13 @@ use function sprintf;
class NonUniqueSlugException extends InvalidArgumentException
{
public static function fromSlug(string $slug): self
public static function fromSlug(string $slug, ?string $domain): self
{
return new self(sprintf('Provided slug "%s" is not unique.', $slug));
$suffix = '';
if ($domain !== null) {
$suffix = sprintf(' for domain "%s"', $domain);
}
return new self(sprintf('Provided slug "%s" is not unique%s.', $slug, $suffix));
}
}

View File

@@ -138,4 +138,25 @@ DQL;
$result = $query->getOneOrNullResult();
return $result === null || $result->maxVisitsReached() ? null : $result;
}
public function slugIsInUse(string $slug, ?string $domain = null): bool
{
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->select('COUNT(DISTINCT s.id)')
->from(ShortUrl::class, 's')
->where($qb->expr()->isNotNull('s.shortCode'))
->andWhere($qb->expr()->eq('s.shortCode', ':slug'))
->setParameter('slug', $slug);
if ($domain !== null) {
$qb->join('s.domain', 'd')
->andWhere($qb->expr()->eq('d.authority', ':authority'))
->setParameter('authority', $domain);
} else {
$qb->andWhere($qb->expr()->isNull('s.domain'));
}
$result = (int) $qb->getQuery()->getSingleScalarResult();
return $result > 0;
}
}

View File

@@ -27,4 +27,6 @@ interface ShortUrlRepositoryInterface extends ObjectRepository
public function countList(?string $searchTerm = null, array $tags = []): int;
public function findOneByShortCode(string $shortCode): ?ShortUrl;
public function slugIsInUse(string $slug, ?string $domain): bool;
}

View File

@@ -23,11 +23,8 @@ use Shlinkio\Shlink\Core\Util\TagManagerTrait;
use Throwable;
use function array_reduce;
use function count;
use function floor;
use function fmod;
use function Functional\contains;
use function Functional\invoke;
use function preg_match;
use function strlen;
@@ -121,30 +118,11 @@ class UrlShortener implements UrlShortenerInterface
// Iterate short URLs until one that matches is found, or return null otherwise
return array_reduce($shortUrls, function (?ShortUrl $found, ShortUrl $shortUrl) use ($tags, $meta) {
if ($found) {
if ($found !== null) {
return $found;
}
if ($meta->hasMaxVisits() && $meta->getMaxVisits() !== $shortUrl->getMaxVisits()) {
return null;
}
if ($meta->hasValidSince() && ! $meta->getValidSince()->eq($shortUrl->getValidSince())) {
return null;
}
if ($meta->hasValidUntil() && ! $meta->getValidUntil()->eq($shortUrl->getValidUntil())) {
return null;
}
$shortUrlTags = invoke($shortUrl->getTags(), '__toString');
$hasAllTags = count($shortUrlTags) === count($tags) && array_reduce(
$tags,
function (bool $hasAllTags, string $tag) use ($shortUrlTags) {
return $hasAllTags && contains($shortUrlTags, $tag);
},
true
);
return $hasAllTags ? $shortUrl : null;
return $shortUrl->matchesCriteria($meta, $tags) ? $shortUrl : null;
});
}
@@ -166,12 +144,13 @@ class UrlShortener implements UrlShortenerInterface
}
$customSlug = $meta->getCustomSlug();
$domain = $meta->getDomain();
/** @var ShortUrlRepository $repo */
$repo = $this->em->getRepository(ShortUrl::class);
$shortUrlsCount = $repo->count(['shortCode' => $customSlug]);
$shortUrlsCount = $repo->slugIsInUse($customSlug, $domain);
if ($shortUrlsCount > 0) {
throw NonUniqueSlugException::fromSlug($customSlug);
throw NonUniqueSlugException::fromSlug($customSlug, $domain);
}
}