mirror of
https://github.com/shlinkio/shlink.git
synced 2026-03-06 23:33:13 +08:00
Add option to do loosely matches on short URLs when mode is loosely
This commit is contained in:
@@ -9,8 +9,8 @@ use function str_replace;
|
||||
|
||||
class MultiSegmentSlugProcessor
|
||||
{
|
||||
private const SINGLE_SHORT_CODE_PATTERN = '{shortCode}';
|
||||
private const MULTI_SHORT_CODE_PATTERN = '{shortCode:.+}';
|
||||
private const SINGLE_SEGMENT_PATTERN = '{shortCode}';
|
||||
private const MULTI_SEGMENT_PATTERN = '{shortCode:.+}';
|
||||
|
||||
public function __invoke(array $config): array
|
||||
{
|
||||
@@ -21,7 +21,7 @@ class MultiSegmentSlugProcessor
|
||||
|
||||
$config['routes'] = map($config['routes'] ?? [], static function (array $route): array {
|
||||
['path' => $path] = $route;
|
||||
$route['path'] = str_replace(self::SINGLE_SHORT_CODE_PATTERN, self::MULTI_SHORT_CODE_PATTERN, $path);
|
||||
$route['path'] = str_replace(self::SINGLE_SEGMENT_PATTERN, self::MULTI_SEGMENT_PATTERN, $path);
|
||||
return $route;
|
||||
});
|
||||
|
||||
|
||||
@@ -14,42 +14,41 @@ use Shlinkio\Shlink\Common\Doctrine\Type\ChronosDateTimeType;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlCreation;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlIdentifier;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlMode;
|
||||
use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl;
|
||||
|
||||
use function count;
|
||||
use function strtolower;
|
||||
|
||||
class ShortUrlRepository extends EntitySpecificationRepository implements ShortUrlRepositoryInterface
|
||||
{
|
||||
public function findOneWithDomainFallback(ShortUrlIdentifier $identifier): ?ShortUrl
|
||||
public function findOneWithDomainFallback(ShortUrlIdentifier $identifier, ShortUrlMode $shortUrlMode): ?ShortUrl
|
||||
{
|
||||
// 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();
|
||||
$ordering = $dbPlatform instanceof PostgreSQLPlatform ? 'ASC' : 'DESC';
|
||||
$isStrict = $shortUrlMode === ShortUrlMode::STRICT;
|
||||
|
||||
$dql = <<<DQL
|
||||
SELECT s
|
||||
FROM Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl AS s
|
||||
LEFT JOIN s.domain AS d
|
||||
WHERE s.shortCode = :shortCode
|
||||
AND (s.domain IS NULL OR d.authority = :domain)
|
||||
ORDER BY s.domain {$ordering}
|
||||
DQL;
|
||||
$qb = $this->createQueryBuilder('s');
|
||||
$qb->leftJoin('s.domain', 'd')
|
||||
->where($qb->expr()->eq($isStrict ? 's.shortCode' : 'LOWER(s.shortCode)', ':shortCode'))
|
||||
->setParameter('shortCode', $isStrict ? $identifier->shortCode : strtolower($identifier->shortCode))
|
||||
->andWhere($qb->expr()->orX(
|
||||
$qb->expr()->isNull('s.domain'),
|
||||
$qb->expr()->eq('d.authority', ':domain')
|
||||
))
|
||||
->setParameter('domain', $identifier->domain);
|
||||
|
||||
$query = $this->getEntityManager()->createQuery($dql);
|
||||
$query->setMaxResults(1)
|
||||
->setParameters([
|
||||
'shortCode' => $identifier->shortCode,
|
||||
'domain' => $identifier->domain,
|
||||
]);
|
||||
|
||||
// Since we ordered by domain, we will have first the URL matching provided domain, followed by the one
|
||||
// with no domain (if any), so it is safe to fetch 1 max result and we will get:
|
||||
// Since we order by domain, we will have first the URL matching provided domain, followed by the one
|
||||
// with no domain (if any), so it is safe to fetch 1 max result, and we will get:
|
||||
// * The short URL matching both the short code and the domain, or
|
||||
// * The short URL matching the short code but without any domain, or
|
||||
// * No short URL at all
|
||||
$qb->orderBy('s.domain', $ordering)
|
||||
->setMaxResults(1);
|
||||
|
||||
return $query->getOneOrNullResult();
|
||||
return $qb->getQuery()->getOneOrNullResult();
|
||||
}
|
||||
|
||||
public function findOne(ShortUrlIdentifier $identifier, ?Specification $spec = null): ?ShortUrl
|
||||
|
||||
@@ -10,11 +10,12 @@ use Happyr\DoctrineSpecification\Specification\Specification;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlCreation;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlIdentifier;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlMode;
|
||||
use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl;
|
||||
|
||||
interface ShortUrlRepositoryInterface extends ObjectRepository, EntitySpecificationRepositoryInterface
|
||||
{
|
||||
public function findOneWithDomainFallback(ShortUrlIdentifier $identifier): ?ShortUrl;
|
||||
public function findOneWithDomainFallback(ShortUrlIdentifier $identifier, ShortUrlMode $shortUrlMode): ?ShortUrl;
|
||||
|
||||
public function findOne(ShortUrlIdentifier $identifier, ?Specification $spec = null): ?ShortUrl;
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace Shlinkio\Shlink\Core\ShortUrl;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException;
|
||||
use Shlinkio\Shlink\Core\Options\UrlShortenerOptions;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlIdentifier;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Repository\ShortUrlRepository;
|
||||
@@ -13,8 +14,10 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
||||
|
||||
class ShortUrlResolver implements ShortUrlResolverInterface
|
||||
{
|
||||
public function __construct(private readonly EntityManagerInterface $em)
|
||||
{
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $em,
|
||||
private readonly UrlShortenerOptions $urlShortenerOptions,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,7 +42,7 @@ class ShortUrlResolver implements ShortUrlResolverInterface
|
||||
{
|
||||
/** @var ShortUrlRepository $shortUrlRepo */
|
||||
$shortUrlRepo = $this->em->getRepository(ShortUrl::class);
|
||||
$shortUrl = $shortUrlRepo->findOneWithDomainFallback($identifier);
|
||||
$shortUrl = $shortUrlRepo->findOneWithDomainFallback($identifier, $this->urlShortenerOptions->mode);
|
||||
if (! $shortUrl?->isEnabled()) {
|
||||
throw ShortUrlNotFoundException::fromNotFound($identifier);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user