diff --git a/CHANGELOG.md b/CHANGELOG.md index 241b734ed..f9c81cd08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this All [URI-reserved characters](https://datatracker.ietf.org/doc/html/rfc3986#section-2.2) were disallowed up until now, but from now on, only the gen-delimiters are. +* [#2229](https://github.com/shlinkio/shlink/issues/2229) Add option to dynamically disable the logo on QR codes. + ### Changed * [#2281](https://github.com/shlinkio/shlink/issues/2281) Update docker image to PHP 8.4 * [#2124](https://github.com/shlinkio/shlink/issues/2124) Improve how Shlink decides if a GeoLite db file needs to be downloaded, and reduces the chances for API limits to be reached. diff --git a/docs/swagger/paths/{shortCode}_qr-code.json b/docs/swagger/paths/{shortCode}_qr-code.json index 39be2dc9e..bb95f7ef1 100644 --- a/docs/swagger/paths/{shortCode}_qr-code.json +++ b/docs/swagger/paths/{shortCode}_qr-code.json @@ -85,6 +85,16 @@ "type": "string", "default": "#ffffff" } + }, + { + "name": "logo", + "in": "query", + "description": "Currently used to disable the logo that was set via configuration options. It may be used in future to dynamically choose from multiple logos.", + "required": false, + "schema": { + "type": "string", + "enum": ["disable"] + } } ], "responses": { diff --git a/module/Core/src/Action/Model/QrCodeParams.php b/module/Core/src/Action/Model/QrCodeParams.php index 8bc5b317e..459c99b7d 100644 --- a/module/Core/src/Action/Model/QrCodeParams.php +++ b/module/Core/src/Action/Model/QrCodeParams.php @@ -28,20 +28,21 @@ use const Shlinkio\Shlink\DEFAULT_QR_CODE_BG_COLOR; use const Shlinkio\Shlink\DEFAULT_QR_CODE_COLOR; -final class QrCodeParams +final readonly class QrCodeParams { private const int MIN_SIZE = 50; private const int MAX_SIZE = 1000; private const array SUPPORTED_FORMATS = ['png', 'svg']; private function __construct( - public readonly int $size, - public readonly int $margin, - public readonly WriterInterface $writer, - public readonly ErrorCorrectionLevel $errorCorrectionLevel, - public readonly RoundBlockSizeMode $roundBlockSizeMode, - public readonly ColorInterface $color, - public readonly ColorInterface $bgColor, + public int $size, + public int $margin, + public WriterInterface $writer, + public ErrorCorrectionLevel $errorCorrectionLevel, + public RoundBlockSizeMode $roundBlockSizeMode, + public ColorInterface $color, + public ColorInterface $bgColor, + public bool $disableLogo, ) { } @@ -57,6 +58,7 @@ public static function fromRequest(ServerRequestInterface $request, QrCodeOption roundBlockSizeMode: self::resolveRoundBlockSize($query, $defaults), color: self::resolveColor($query, $defaults), bgColor: self::resolveBackgroundColor($query, $defaults), + disableLogo: isset($query['logo']) && $query['logo'] === 'disable', ); } diff --git a/module/Core/src/Action/QrCodeAction.php b/module/Core/src/Action/QrCodeAction.php index 607fab50b..fbc83c218 100644 --- a/module/Core/src/Action/QrCodeAction.php +++ b/module/Core/src/Action/QrCodeAction.php @@ -60,7 +60,7 @@ public function process(Request $request, RequestHandlerInterface $handler): Res private function buildQrCode(Builder $qrCodeBuilder, QrCodeParams $params): ResultInterface { $logoUrl = $this->options->logoUrl; - if ($logoUrl === null) { + if ($logoUrl === null || $params->disableLogo) { return $qrCodeBuilder->build(); } diff --git a/module/Core/test/Action/QrCodeActionTest.php b/module/Core/test/Action/QrCodeActionTest.php index 688badee1..750717bf0 100644 --- a/module/Core/test/Action/QrCodeActionTest.php +++ b/module/Core/test/Action/QrCodeActionTest.php @@ -9,6 +9,7 @@ use Laminas\Diactoros\ServerRequestFactory; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestWith; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestInterface; @@ -294,11 +295,15 @@ public function qrCodeIsResolvedBasedOnOptions(bool $enabledForDisabledShortUrls } #[Test] - public function logoIsAddedToQrCodeIfOptionIsDefined(): void + #[TestWith([[], '4696E5'])] // The logo has Shlink's brand color + #[TestWith([['logo' => 'disable'], '000000'])] // No logo will be added if explicitly disabled + public function logoIsAddedToQrCodeIfOptionIsDefined(array $query, string $expectedColor): void { - $logoUrl = 'https://avatars.githubusercontent.com/u/20341790?v=4'; // Shlink logo + $logoUrl = 'https://avatars.githubusercontent.com/u/20341790?v=4'; // Shlink's logo $code = 'abc123'; - $req = ServerRequestFactory::fromGlobals()->withAttribute('shortCode', $code); + $req = ServerRequestFactory::fromGlobals() + ->withAttribute('shortCode', $code) + ->withQueryParams($query); $this->urlResolver->method('resolveEnabledShortUrl')->with( ShortUrlIdentifier::fromShortCodeAndDomain($code), @@ -309,9 +314,9 @@ public function logoIsAddedToQrCodeIfOptionIsDefined(): void $image = imagecreatefromstring($resp->getBody()->__toString()); self::assertNotFalse($image); - // At around 100x100 px we can already find the logo, which has Shlink's brand color + // At around 100x100 px we can already find the logo, if present $resultingColor = imagecolorat($image, 100, 100); - self::assertEquals(hexdec('4696E5'), $resultingColor); + self::assertEquals(hexdec($expectedColor), $resultingColor); } public static function provideEnabled(): iterable