From ac1737492b2c8f392426060f642eafef4c8d84bb Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Tue, 19 Dec 2023 11:13:13 +0100 Subject: [PATCH 01/10] Update artifact GitHub actions --- .github/workflows/ci-db-tests.yml | 2 +- .github/workflows/ci-mutation-tests.yml | 2 +- .github/workflows/ci-tests.yml | 2 +- .github/workflows/ci.yml | 2 +- .github/workflows/publish-release.yml | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci-db-tests.yml b/.github/workflows/ci-db-tests.yml index f164e7da8..cc653315e 100644 --- a/.github/workflows/ci-db-tests.yml +++ b/.github/workflows/ci-db-tests.yml @@ -36,7 +36,7 @@ jobs: - name: Run tests run: composer test:db:${{ inputs.platform }} - name: Upload code coverage - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: ${{ matrix.php-version == '8.2' && inputs.platform == 'sqlite:ci' }} with: name: coverage-db diff --git a/.github/workflows/ci-mutation-tests.yml b/.github/workflows/ci-mutation-tests.yml index 6bc69eb36..d0d18c15a 100644 --- a/.github/workflows/ci-mutation-tests.yml +++ b/.github/workflows/ci-mutation-tests.yml @@ -22,7 +22,7 @@ jobs: php-version: ${{ matrix.php-version }} php-extensions: openswoole-22.1.0 extensions-cache-key: mutation-tests-extensions-${{ matrix.php-version }}-${{ inputs.test-group }} - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: coverage-${{ inputs.test-group }} path: build diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index 62b7ca2ea..77c055bff 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -29,7 +29,7 @@ jobs: php-extensions: openswoole-22.1.0 extensions-cache-key: tests-extensions-${{ matrix.php-version }}-${{ inputs.test-group }} - run: composer test:${{ inputs.test-group }}:ci - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 if: ${{ matrix.php-version == '8.2' }} with: name: coverage-${{ inputs.test-group }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 37134b81b..bcbe0a485 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -146,7 +146,7 @@ jobs: php-version: ${{ matrix.php-version }} coverage: pcov ini-values: pcov.directory=module - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: path: build - run: mv build/coverage-unit/coverage-unit.cov build/coverage-unit.cov diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 625597a10..3fe1a1a4d 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -24,7 +24,7 @@ jobs: run: ./build.sh ${GITHUB_REF#refs/tags/v} - if: ${{ matrix.swoole == 'no' }} run: ./build.sh ${GITHUB_REF#refs/tags/v} --no-swoole - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: dist-files-${{ matrix.php-version }}-${{ matrix.swoole }} path: build @@ -34,7 +34,7 @@ jobs: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: path: build - name: Publish release with assets From 225905fcdb9e591db56f0b43e7f88900083472be Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Tue, 19 Dec 2023 11:22:40 +0100 Subject: [PATCH 02/10] update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 006766a88..61c182dcc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this ### Changed * Remove dependency on functional-php library +* [#1939](https://github.com/shlinkio/shlink/issues/1939) Fine-tune RoadRunner logs to avoid too many useless info. ### Deprecated * *Nothing* @@ -19,7 +20,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this ### Fixed * [#1947](https://github.com/shlinkio/shlink/issues/1947) Fix error when importing short URLs while using Postgres. -* [#1939](https://github.com/shlinkio/shlink/issues/1939) Fine-tune RoadRunner logs to avoid too many useless info. ## [3.7.0] - 2023-11-25 From 06306aabd5f4ce7692516a7ff6621897b9bbd3ec Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 22 Dec 2023 13:29:22 +0100 Subject: [PATCH 03/10] Allow QR codes to be generated for disabled short URLs --- config/autoload/qr-codes.global.php | 4 ++++ config/constants.php | 2 ++ config/roadrunner/.rr.dev.yml | 2 +- module/Core/src/Action/QrCodeAction.php | 10 ++++++---- module/Core/src/Config/EnvVars.php | 1 + module/Core/src/Options/QrCodeOptions.php | 14 ++++++++------ .../src/ShortUrl/Model/ShortUrlIdentifier.php | 4 ++-- module/Core/src/ShortUrl/ShortUrlResolver.php | 18 ++++++++++++++---- .../src/ShortUrl/ShortUrlResolverInterface.php | 13 +++++++++++++ 9 files changed, 51 insertions(+), 17 deletions(-) diff --git a/config/autoload/qr-codes.global.php b/config/autoload/qr-codes.global.php index dc4f5f9e1..808ff9615 100644 --- a/config/autoload/qr-codes.global.php +++ b/config/autoload/qr-codes.global.php @@ -4,6 +4,7 @@ use Shlinkio\Shlink\Core\Config\EnvVars; +use const Shlinkio\Shlink\DEFAULT_QR_CODE_ENABLED_FOR_DISABLED_SHORT_URLS; use const Shlinkio\Shlink\DEFAULT_QR_CODE_ERROR_CORRECTION; use const Shlinkio\Shlink\DEFAULT_QR_CODE_FORMAT; use const Shlinkio\Shlink\DEFAULT_QR_CODE_MARGIN; @@ -22,6 +23,9 @@ 'round_block_size' => (bool) EnvVars::DEFAULT_QR_CODE_ROUND_BLOCK_SIZE->loadFromEnv( DEFAULT_QR_CODE_ROUND_BLOCK_SIZE, ), + 'enabled_for_disabled_short_urls' => (bool) EnvVars::QR_CODE_FOR_DISABLED_SHORT_URLS->loadFromEnv( + DEFAULT_QR_CODE_ENABLED_FOR_DISABLED_SHORT_URLS, + ), ], ]; diff --git a/config/constants.php b/config/constants.php index 35d4c56e4..f08c135c4 100644 --- a/config/constants.php +++ b/config/constants.php @@ -19,4 +19,6 @@ const DEFAULT_QR_CODE_FORMAT = 'png'; const DEFAULT_QR_CODE_ERROR_CORRECTION = 'l'; const DEFAULT_QR_CODE_ROUND_BLOCK_SIZE = true; +// Deprecated. Shlink 4.0.0 should change default value to `true` +const DEFAULT_QR_CODE_ENABLED_FOR_DISABLED_SHORT_URLS = false; const MIN_TASK_WORKERS = 4; diff --git a/config/roadrunner/.rr.dev.yml b/config/roadrunner/.rr.dev.yml index cdc1f326e..24c3a2fc2 100644 --- a/config/roadrunner/.rr.dev.yml +++ b/config/roadrunner/.rr.dev.yml @@ -35,7 +35,7 @@ logs: http: mode: 'off' # Disable logging as Shlink handles it internally server: - level: debug + level: info metrics: level: debug jobs: diff --git a/module/Core/src/Action/QrCodeAction.php b/module/Core/src/Action/QrCodeAction.php index 6c645726b..a952243ae 100644 --- a/module/Core/src/Action/QrCodeAction.php +++ b/module/Core/src/Action/QrCodeAction.php @@ -18,13 +18,13 @@ use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlIdentifier; use Shlinkio\Shlink\Core\ShortUrl\ShortUrlResolverInterface; -class QrCodeAction implements MiddlewareInterface +readonly class QrCodeAction implements MiddlewareInterface { public function __construct( private ShortUrlResolverInterface $urlResolver, private ShortUrlStringifierInterface $stringifier, private LoggerInterface $logger, - private QrCodeOptions $defaultOptions, + private QrCodeOptions $options, ) { } @@ -33,13 +33,15 @@ public function process(Request $request, RequestHandlerInterface $handler): Res $identifier = ShortUrlIdentifier::fromRedirectRequest($request); try { - $shortUrl = $this->urlResolver->resolveEnabledShortUrl($identifier); + $shortUrl = $this->options->enabledForDisabledShortUrls + ? $this->urlResolver->resolvePublicShortUrl($identifier) + : $this->urlResolver->resolveEnabledShortUrl($identifier); } catch (ShortUrlNotFoundException $e) { $this->logger->warning('An error occurred while creating QR code. {e}', ['e' => $e]); return $handler->handle($request); } - $params = QrCodeParams::fromRequest($request, $this->defaultOptions); + $params = QrCodeParams::fromRequest($request, $this->options); $qrCodeBuilder = Builder::create() ->data($this->stringifier->stringify($shortUrl)) ->size($params->size) diff --git a/module/Core/src/Config/EnvVars.php b/module/Core/src/Config/EnvVars.php index 790bfe3ac..ff64838be 100644 --- a/module/Core/src/Config/EnvVars.php +++ b/module/Core/src/Config/EnvVars.php @@ -43,6 +43,7 @@ enum EnvVars: string case DEFAULT_QR_CODE_FORMAT = 'DEFAULT_QR_CODE_FORMAT'; case DEFAULT_QR_CODE_ERROR_CORRECTION = 'DEFAULT_QR_CODE_ERROR_CORRECTION'; case DEFAULT_QR_CODE_ROUND_BLOCK_SIZE = 'DEFAULT_QR_CODE_ROUND_BLOCK_SIZE'; + case QR_CODE_FOR_DISABLED_SHORT_URLS = 'QR_CODE_FOR_DISABLED_SHORT_URLS'; case DEFAULT_INVALID_SHORT_URL_REDIRECT = 'DEFAULT_INVALID_SHORT_URL_REDIRECT'; case DEFAULT_REGULAR_404_REDIRECT = 'DEFAULT_REGULAR_404_REDIRECT'; case DEFAULT_BASE_URL_REDIRECT = 'DEFAULT_BASE_URL_REDIRECT'; diff --git a/module/Core/src/Options/QrCodeOptions.php b/module/Core/src/Options/QrCodeOptions.php index 1b10c2800..fff278583 100644 --- a/module/Core/src/Options/QrCodeOptions.php +++ b/module/Core/src/Options/QrCodeOptions.php @@ -4,20 +4,22 @@ namespace Shlinkio\Shlink\Core\Options; +use const Shlinkio\Shlink\DEFAULT_QR_CODE_ENABLED_FOR_DISABLED_SHORT_URLS; use const Shlinkio\Shlink\DEFAULT_QR_CODE_ERROR_CORRECTION; use const Shlinkio\Shlink\DEFAULT_QR_CODE_FORMAT; use const Shlinkio\Shlink\DEFAULT_QR_CODE_MARGIN; use const Shlinkio\Shlink\DEFAULT_QR_CODE_ROUND_BLOCK_SIZE; use const Shlinkio\Shlink\DEFAULT_QR_CODE_SIZE; -final class QrCodeOptions +readonly final class QrCodeOptions { public function __construct( - public readonly int $size = DEFAULT_QR_CODE_SIZE, - public readonly int $margin = DEFAULT_QR_CODE_MARGIN, - public readonly string $format = DEFAULT_QR_CODE_FORMAT, - public readonly string $errorCorrection = DEFAULT_QR_CODE_ERROR_CORRECTION, - public readonly bool $roundBlockSize = DEFAULT_QR_CODE_ROUND_BLOCK_SIZE, + public int $size = DEFAULT_QR_CODE_SIZE, + public int $margin = DEFAULT_QR_CODE_MARGIN, + public string $format = DEFAULT_QR_CODE_FORMAT, + public string $errorCorrection = DEFAULT_QR_CODE_ERROR_CORRECTION, + public bool $roundBlockSize = DEFAULT_QR_CODE_ROUND_BLOCK_SIZE, + public bool $enabledForDisabledShortUrls = DEFAULT_QR_CODE_ENABLED_FOR_DISABLED_SHORT_URLS, ) { } } diff --git a/module/Core/src/ShortUrl/Model/ShortUrlIdentifier.php b/module/Core/src/ShortUrl/Model/ShortUrlIdentifier.php index 78becbeda..7ec19df64 100644 --- a/module/Core/src/ShortUrl/Model/ShortUrlIdentifier.php +++ b/module/Core/src/ShortUrl/Model/ShortUrlIdentifier.php @@ -10,9 +10,9 @@ use function sprintf; -final class ShortUrlIdentifier +final readonly class ShortUrlIdentifier { - private function __construct(public readonly string $shortCode, public readonly ?string $domain = null) + private function __construct(public string $shortCode, public ?string $domain = null) { } diff --git a/module/Core/src/ShortUrl/ShortUrlResolver.php b/module/Core/src/ShortUrl/ShortUrlResolver.php index 2c4f7bdc1..4fd0d015d 100644 --- a/module/Core/src/ShortUrl/ShortUrlResolver.php +++ b/module/Core/src/ShortUrl/ShortUrlResolver.php @@ -12,11 +12,11 @@ use Shlinkio\Shlink\Core\ShortUrl\Repository\ShortUrlRepository; use Shlinkio\Shlink\Rest\Entity\ApiKey; -class ShortUrlResolver implements ShortUrlResolverInterface +readonly class ShortUrlResolver implements ShortUrlResolverInterface { public function __construct( - private readonly EntityManagerInterface $em, - private readonly UrlShortenerOptions $urlShortenerOptions, + private EntityManagerInterface $em, + private UrlShortenerOptions $urlShortenerOptions, ) { } @@ -39,11 +39,21 @@ public function resolveShortUrl(ShortUrlIdentifier $identifier, ?ApiKey $apiKey * @throws ShortUrlNotFoundException */ public function resolveEnabledShortUrl(ShortUrlIdentifier $identifier): ShortUrl + { + $shortUrl = $this->resolvePublicShortUrl($identifier); + if (! $shortUrl->isEnabled()) { + throw ShortUrlNotFoundException::fromNotFound($identifier); + } + + return $shortUrl; + } + + public function resolvePublicShortUrl(ShortUrlIdentifier $identifier): ShortUrl { /** @var ShortUrlRepository $shortUrlRepo */ $shortUrlRepo = $this->em->getRepository(ShortUrl::class); $shortUrl = $shortUrlRepo->findOneWithDomainFallback($identifier, $this->urlShortenerOptions->mode); - if (! $shortUrl?->isEnabled()) { + if ($shortUrl === null) { throw ShortUrlNotFoundException::fromNotFound($identifier); } diff --git a/module/Core/src/ShortUrl/ShortUrlResolverInterface.php b/module/Core/src/ShortUrl/ShortUrlResolverInterface.php index f92038c3b..9dd522c0f 100644 --- a/module/Core/src/ShortUrl/ShortUrlResolverInterface.php +++ b/module/Core/src/ShortUrl/ShortUrlResolverInterface.php @@ -17,6 +17,19 @@ interface ShortUrlResolverInterface public function resolveShortUrl(ShortUrlIdentifier $identifier, ?ApiKey $apiKey = null): ShortUrl; /** + * Resolves a public short URL matching provided identifier. + * When trying to match public short URLs, if provided domain is default one, it gets ignored. + * If provided domain is not default, but the short code is found in default domain, we fall back to that short URL. + * + * @throws ShortUrlNotFoundException + */ + public function resolvePublicShortUrl(ShortUrlIdentifier $identifier): ShortUrl; + + /** + * Resolves a public short URL matching provided identifier, only if it's not disabled. + * Disabled short URLs are those which received the max amount of visits, have a `validSince` in the future or have + * a `validUntil` in the past. + * * @throws ShortUrlNotFoundException */ public function resolveEnabledShortUrl(ShortUrlIdentifier $identifier): ShortUrl; From 92b0525b6e3176a8017d2a7c9bfb8b0d6d7b1151 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 23 Dec 2023 11:14:12 +0100 Subject: [PATCH 04/10] Update Twitter badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ee27f0301..7e84d5ae9 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![Latest Stable Version](https://img.shields.io/github/release/shlinkio/shlink.svg?style=flat-square)](https://packagist.org/packages/shlinkio/shlink) [![Docker pulls](https://img.shields.io/docker/pulls/shlinkio/shlink.svg?logo=docker&style=flat-square)](https://hub.docker.com/r/shlinkio/shlink/) [![License](https://img.shields.io/github/license/shlinkio/shlink.svg?style=flat-square)](https://github.com/shlinkio/shlink/blob/main/LICENSE) -[![Twitter](https://img.shields.io/badge/follow-shlinkio-blue.svg?style=flat-square&logo=twitter&color=blue)](https://twitter.com/shlinkio) +[![Twitter](https://img.shields.io/badge/follow-shlinkio-blue.svg?style=flat-square&logo=x&color=black)](https://twitter.com/shlinkio) [![Mastodon](https://img.shields.io/mastodon/follow/109329425426175098?color=%236364ff&domain=https%3A%2F%2Ffosstodon.org&label=follow&logo=mastodon&logoColor=white&style=flat-square)](https://fosstodon.org/@shlinkio) [![Paypal donate](https://img.shields.io/badge/Donate-paypal-blue.svg?style=flat-square&logo=paypal&colorA=aaaaaa)](https://slnk.to/donate) From 639329dbe4438f373480156d863997b2ecbc43f1 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 24 Dec 2023 09:48:44 +0100 Subject: [PATCH 05/10] Update installer --- composer.json | 2 +- config/autoload/installer.global.php | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index db0fa4c3a..3cb28ddc5 100644 --- a/composer.json +++ b/composer.json @@ -49,7 +49,7 @@ "shlinkio/shlink-config": "^2.5", "shlinkio/shlink-event-dispatcher": "^3.1", "shlinkio/shlink-importer": "^5.2.1", - "shlinkio/shlink-installer": "^8.6.1", + "shlinkio/shlink-installer": "dev-develop#62aae8d as 8.7", "shlinkio/shlink-ip-geolocation": "^3.4", "shlinkio/shlink-json": "^1.1", "spiral/roadrunner": "^2023.2", diff --git a/config/autoload/installer.global.php b/config/autoload/installer.global.php index 32f71ea67..45f92153c 100644 --- a/config/autoload/installer.global.php +++ b/config/autoload/installer.global.php @@ -62,6 +62,7 @@ Option\QrCode\DefaultFormatConfigOption::class, Option\QrCode\DefaultErrorCorrectionConfigOption::class, Option\QrCode\DefaultRoundBlockSizeConfigOption::class, + Option\QrCode\EnabledForDisabledShortUrlsConfigOption::class, Option\RabbitMq\RabbitMqEnabledConfigOption::class, Option\RabbitMq\RabbitMqHostConfigOption::class, Option\RabbitMq\RabbitMqUseSslConfigOption::class, From c5977389153277ec986cd682d838f5fc40808b8f Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 24 Dec 2023 10:13:19 +0100 Subject: [PATCH 06/10] Test how URLs are resolved in QrCodeAction --- module/Core/test/Action/QrCodeActionTest.php | 29 ++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/module/Core/test/Action/QrCodeActionTest.php b/module/Core/test/Action/QrCodeActionTest.php index f6850c1f4..9a89ff47f 100644 --- a/module/Core/test/Action/QrCodeActionTest.php +++ b/module/Core/test/Action/QrCodeActionTest.php @@ -230,6 +230,35 @@ public static function provideRoundBlockSize(): iterable ]; } + #[Test, DataProvider('provideEnabled')] + public function qrCodeIsResolvedBasedOnOptions(bool $enabledForDisabledShortUrls): void + { + + if ($enabledForDisabledShortUrls) { + $this->urlResolver->expects($this->once())->method('resolvePublicShortUrl')->willThrowException( + ShortUrlNotFoundException::fromNotFound(ShortUrlIdentifier::fromShortCodeAndDomain('')), + ); + $this->urlResolver->expects($this->never())->method('resolveEnabledShortUrl'); + } else { + $this->urlResolver->expects($this->once())->method('resolveEnabledShortUrl')->willThrowException( + ShortUrlNotFoundException::fromNotFound(ShortUrlIdentifier::fromShortCodeAndDomain('')), + ); + $this->urlResolver->expects($this->never())->method('resolvePublicShortUrl'); + } + + $options = new QrCodeOptions(enabledForDisabledShortUrls: $enabledForDisabledShortUrls); + $this->action($options)->process( + ServerRequestFactory::fromGlobals(), + $this->createMock(RequestHandlerInterface::class), + ); + } + + public static function provideEnabled(): iterable + { + yield 'always enabled' => [true]; + yield 'only enabled short URLs' => [false]; + } + public function action(?QrCodeOptions $options = null): QrCodeAction { return new QrCodeAction( From 8d1776af98082b8a02a89bf6b16e4e23d9a16629 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 24 Dec 2023 10:25:58 +0100 Subject: [PATCH 07/10] Test error when short URLs cannot be resolved --- .../test/ShortUrl/ShortUrlResolverTest.php | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/module/Core/test/ShortUrl/ShortUrlResolverTest.php b/module/Core/test/ShortUrl/ShortUrlResolverTest.php index a95426ba1..729302c98 100644 --- a/module/Core/test/ShortUrl/ShortUrlResolverTest.php +++ b/module/Core/test/ShortUrl/ShortUrlResolverTest.php @@ -59,7 +59,7 @@ public function shortCodeIsProperlyParsed(?ApiKey $apiKey): void } #[Test, DataProviderExternal(ApiKeyDataProviders::class, 'adminApiKeysProvider')] - public function exceptionIsThrownIfShortcodeIsNotFound(?ApiKey $apiKey): void + public function exceptionIsThrownIfShortCodeIsNotFound(?ApiKey $apiKey): void { $shortCode = 'abc123'; $identifier = ShortUrlIdentifier::fromShortCodeAndDomain($shortCode); @@ -73,7 +73,7 @@ public function exceptionIsThrownIfShortcodeIsNotFound(?ApiKey $apiKey): void } #[Test] - public function shortCodeToEnabledShortUrlProperlyParsesShortCode(): void + public function resolveEnabledShortUrlProperlyParsesShortCode(): void { $shortUrl = ShortUrl::withLongUrl('https://expected_url'); $shortCode = $shortUrl->getShortCode(); @@ -89,8 +89,30 @@ public function shortCodeToEnabledShortUrlProperlyParsesShortCode(): void self::assertSame($shortUrl, $result); } + #[Test, DataProvider('provideResolutionMethods')] + public function resolutionThrowsExceptionIfUrlIsNotEnabled(string $method): void + { + $shortCode = 'abc123'; + + $this->repo->expects($this->once())->method('findOneWithDomainFallback')->with( + ShortUrlIdentifier::fromShortCodeAndDomain($shortCode), + ShortUrlMode::STRICT, + )->willReturn(null); + $this->em->expects($this->once())->method('getRepository')->with(ShortUrl::class)->willReturn($this->repo); + + $this->expectException(ShortUrlNotFoundException::class); + + $this->urlResolver->{$method}(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode)); + } + + public static function provideResolutionMethods(): iterable + { + yield 'resolveEnabledShortUrl' => ['resolveEnabledShortUrl']; + yield 'resolvePublicShortUrl' => ['resolvePublicShortUrl']; + } + #[Test, DataProvider('provideDisabledShortUrls')] - public function shortCodeToEnabledShortUrlThrowsExceptionIfUrlIsNotEnabled(ShortUrl $shortUrl): void + public function resolveEnabledShortUrlThrowsExceptionIfUrlIsNotEnabled(ShortUrl $shortUrl): void { $shortCode = $shortUrl->getShortCode(); From 0f0301ae5cdd3097b2b199506a2628606448298e Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 24 Dec 2023 10:27:25 +0100 Subject: [PATCH 08/10] Update changelog --- CHANGELOG.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61c182dcc..4744e4d41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com), and this project adheres to [Semantic Versioning](https://semver.org). +## [Unreleased] +### Added +* *Nothing* + +### Changed +* *Nothing* + +### Deprecated +* *Nothing* + +### Removed +* *Nothing* + +### Fixed +* [#1960](https://github.com/shlinkio/shlink/issues/1960) Allow QR codes to be optionally resolved even when corresponding short URL is not enabled. + + ## [3.7.1] - 2023-12-17 ### Added * *Nothing* From e12bda3f42987ebe28954defdc24c42c0c57ecba Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 24 Dec 2023 10:37:09 +0100 Subject: [PATCH 09/10] Add API test to verify QR codes return a 404 for disabled short URLs --- module/Core/test-api/Action/QrCodeTest.php | 27 ++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 module/Core/test-api/Action/QrCodeTest.php diff --git a/module/Core/test-api/Action/QrCodeTest.php b/module/Core/test-api/Action/QrCodeTest.php new file mode 100644 index 000000000..955e6c7ef --- /dev/null +++ b/module/Core/test-api/Action/QrCodeTest.php @@ -0,0 +1,27 @@ +callShortUrl('custom/qr-code'); + self::assertEquals(200, $response->getStatusCode()); + + // This short URL allow max 2 visits + $this->callShortUrl('custom'); + $this->callShortUrl('custom'); + + // After 2 visits, the QR code should return a 404 + $response = $this->callShortUrl('custom/qr-code'); + self::assertEquals(404, $response->getStatusCode()); + } +} From 62b54ceaaf6ba851648994af90862a0807088df6 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Tue, 26 Dec 2023 16:16:10 +0100 Subject: [PATCH 10/10] Add v3.7.2 to changelog --- CHANGELOG.md | 2 +- composer.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4744e4d41..736daf3be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com), and this project adheres to [Semantic Versioning](https://semver.org). -## [Unreleased] +## [3.7.2] - 2023-12-26 ### Added * *Nothing* diff --git a/composer.json b/composer.json index 3cb28ddc5..b9eef16c8 100644 --- a/composer.json +++ b/composer.json @@ -49,7 +49,7 @@ "shlinkio/shlink-config": "^2.5", "shlinkio/shlink-event-dispatcher": "^3.1", "shlinkio/shlink-importer": "^5.2.1", - "shlinkio/shlink-installer": "dev-develop#62aae8d as 8.7", + "shlinkio/shlink-installer": "^8.7", "shlinkio/shlink-ip-geolocation": "^3.4", "shlinkio/shlink-json": "^1.1", "spiral/roadrunner": "^2023.2",