diff --git a/CHANGELOG.md b/CHANGELOG.md index d2ae6058..62ad683b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this #### Added -* *Nothing* +* [#829](https://github.com/shlinkio/shlink/issues/829) Added support for QR codes in SVG format, by passing `?format=svg` to the QR code URL. #### Changed diff --git a/docs/swagger/paths/{shortCode}_qr-code.json b/docs/swagger/paths/{shortCode}_qr-code.json index 300a7429..a3fdaffb 100644 --- a/docs/swagger/paths/{shortCode}_qr-code.json +++ b/docs/swagger/paths/{shortCode}_qr-code.json @@ -27,6 +27,19 @@ "maximum": 1000, "default": 300 } + }, + { + "name": "format", + "in": "query", + "description": "The format for the QR code image, being valid values png and svg. Not providing the param or providing any other value will fall back to png.", + "required": false, + "schema": { + "type": "string", + "enum": [ + "png", + "svg" + ] + } } ], "responses": { @@ -38,6 +51,12 @@ "type": "string", "format": "binary" } + }, + "image/svg+xml": { + "schema": { + "type": "string", + "format": "binary" + } } } } diff --git a/module/Core/src/Action/QrCodeAction.php b/module/Core/src/Action/QrCodeAction.php index 979e34fe..4a8b7db5 100644 --- a/module/Core/src/Action/QrCodeAction.php +++ b/module/Core/src/Action/QrCodeAction.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Action; use Endroid\QrCode\QrCode; +use Endroid\QrCode\Writer\SvgWriter; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Server\MiddlewareInterface; @@ -51,6 +52,11 @@ class QrCodeAction implements MiddlewareInterface $qrCode->setSize($this->getSizeParam($request)); $qrCode->setMargin(0); + $format = $request->getQueryParams()['format'] ?? 'png'; + if ($format === 'svg') { + $qrCode->setWriter(new SvgWriter()); + } + return new QrCodeResponse($qrCode); } diff --git a/module/Core/test/Action/QrCodeActionTest.php b/module/Core/test/Action/QrCodeActionTest.php index eb68f0e1..236288dd 100644 --- a/module/Core/test/Action/QrCodeActionTest.php +++ b/module/Core/test/Action/QrCodeActionTest.php @@ -81,4 +81,30 @@ class QrCodeActionTest extends TestCase $this->assertEquals(200, $resp->getStatusCode()); $delegate->handle(Argument::any())->shouldHaveBeenCalledTimes(0); } + + /** + * @test + * @dataProvider provideQueries + */ + public function imageIsReturnedWithExpectedContentTypeBasedOnProvidedFormat( + array $query, + string $expectedContentType + ): void { + $code = 'abc123'; + $this->urlResolver->resolveEnabledShortUrl(new ShortUrlIdentifier($code, ''))->willReturn(new ShortUrl('')); + $delegate = $this->prophesize(RequestHandlerInterface::class); + $req = (new ServerRequest())->withAttribute('shortCode', $code)->withQueryParams($query); + + $resp = $this->action->process($req, $delegate->reveal()); + + $this->assertEquals($expectedContentType, $resp->getHeaderLine('Content-Type')); + } + + public function provideQueries(): iterable + { + yield 'no format' => [[], 'image/png']; + yield 'png format' => [['format' => 'png'], 'image/png']; + yield 'svg format' => [['format' => 'svg'], 'image/svg+xml']; + yield 'unsupported format' => [['format' => 'jpg'], 'image/png']; + } }