Deprecate QR code generation endpoint

This commit is contained in:
Alejandro Celaya
2025-04-22 08:41:58 +02:00
parent 851929ebef
commit b15e832cf4
10 changed files with 61 additions and 45 deletions

View File

@@ -4,6 +4,24 @@ 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
* [#2408](https://github.com/shlinkio/shlink/issues/2408) Generating QR codes via `/{short-code}/qr-code` is now deprecated and will be removed in Shlink 5.0. Use the equivalent capability from web clients instead.
### Removed
* *Nothing*
### Fixed
* *Nothing*
## [4.4.6] - 2025-03-20
### Added
* *Nothing*

View File

@@ -43,7 +43,7 @@
"pagerfanta/core": "^3.8",
"ramsey/uuid": "^4.7",
"shlinkio/doctrine-specification": "^2.2",
"shlinkio/shlink-common": "^7.0",
"shlinkio/shlink-common": "dev-main#e601317 as 7.1",
"shlinkio/shlink-config": "^4.0",
"shlinkio/shlink-event-dispatcher": "^4.2",
"shlinkio/shlink-importer": "^5.6",

View File

@@ -13,13 +13,22 @@ const DEFAULT_REDIRECT_STATUS_CODE = RedirectStatus::STATUS_302;
const DEFAULT_REDIRECT_CACHE_LIFETIME = 30;
const LOCAL_LOCK_FACTORY = 'Shlinkio\Shlink\LocalLockFactory';
const LOOSE_URI_MATCHER = '/(.+)\:(.+)/i'; // Matches anything starting with a schema.
const DEFAULT_QR_CODE_SIZE = 300;
const DEFAULT_QR_CODE_MARGIN = 0;
const DEFAULT_QR_CODE_FORMAT = 'png';
const DEFAULT_QR_CODE_ERROR_CORRECTION = 'l';
const DEFAULT_QR_CODE_ROUND_BLOCK_SIZE = true;
const DEFAULT_QR_CODE_ENABLED_FOR_DISABLED_SHORT_URLS = true;
const DEFAULT_QR_CODE_COLOR = '#000000'; // Black
const DEFAULT_QR_CODE_BG_COLOR = '#ffffff'; // White
const IP_ADDRESS_REQUEST_ATTRIBUTE = 'remote_address';
const REDIRECT_URL_REQUEST_ATTRIBUTE = 'redirect_url';
/** @deprecated */
const DEFAULT_QR_CODE_SIZE = 300;
/** @deprecated */
const DEFAULT_QR_CODE_MARGIN = 0;
/** @deprecated */
const DEFAULT_QR_CODE_FORMAT = 'png';
/** @deprecated */
const DEFAULT_QR_CODE_ERROR_CORRECTION = 'l';
/** @deprecated */
const DEFAULT_QR_CODE_ROUND_BLOCK_SIZE = true;
/** @deprecated */
const DEFAULT_QR_CODE_ENABLED_FOR_DISABLED_SHORT_URLS = true;
/** @deprecated */
const DEFAULT_QR_CODE_COLOR = '#000000'; // Black
/** @deprecated */
const DEFAULT_QR_CODE_BG_COLOR = '#ffffff'; // White

View File

@@ -1,11 +1,12 @@
{
"get": {
"deprecated": true,
"operationId": "shortUrlQrCode",
"tags": [
"URL Shortener"
],
"summary": "Short URL QR code",
"description": "Generates a QR code image pointing to a short URL.<br />Since this is not an API endpoint but an image one, when an invalid value is provided for any of the query params, they will fall to their default values instead of throwing an error.",
"summary": "[Deprecated] Short URL QR code",
"description": "**[Deprecated]** Use an external mechanism to generate QR codes. Shlink dashboard and shlink-web-client provide their own.",
"parameters": [
{
"$ref": "../parameters/shortCode.json"

View File

@@ -28,6 +28,7 @@ use function trim;
use const Shlinkio\Shlink\DEFAULT_QR_CODE_BG_COLOR;
use const Shlinkio\Shlink\DEFAULT_QR_CODE_COLOR;
/** @deprecated */
final readonly class QrCodeParams
{
private const int MIN_SIZE = 50;

View File

@@ -19,6 +19,7 @@ use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifierInterface;
use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlIdentifier;
use Shlinkio\Shlink\Core\ShortUrl\ShortUrlResolverInterface;
/** @deprecated */
readonly class QrCodeAction implements MiddlewareInterface
{
public function __construct(

View File

@@ -56,15 +56,6 @@ enum EnvVars: string
case MATOMO_BASE_URL = 'MATOMO_BASE_URL';
case MATOMO_SITE_ID = 'MATOMO_SITE_ID';
case MATOMO_API_TOKEN = 'MATOMO_API_TOKEN';
case DEFAULT_QR_CODE_SIZE = 'DEFAULT_QR_CODE_SIZE';
case DEFAULT_QR_CODE_MARGIN = 'DEFAULT_QR_CODE_MARGIN';
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_QR_CODE_COLOR = 'DEFAULT_QR_CODE_COLOR';
case DEFAULT_QR_CODE_BG_COLOR = 'DEFAULT_QR_CODE_BG_COLOR';
case DEFAULT_QR_CODE_LOGO_URL = 'DEFAULT_QR_CODE_LOGO_URL';
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';
@@ -95,6 +86,24 @@ enum EnvVars: string
case SKIP_INITIAL_GEOLITE_DOWNLOAD = 'SKIP_INITIAL_GEOLITE_DOWNLOAD';
/** @deprecated Use REDIRECT_EXTRA_PATH */
case REDIRECT_APPEND_EXTRA_PATH = 'REDIRECT_APPEND_EXTRA_PATH';
/** @deprecated */
case DEFAULT_QR_CODE_SIZE = 'DEFAULT_QR_CODE_SIZE';
/** @deprecated */
case DEFAULT_QR_CODE_MARGIN = 'DEFAULT_QR_CODE_MARGIN';
/** @deprecated */
case DEFAULT_QR_CODE_FORMAT = 'DEFAULT_QR_CODE_FORMAT';
/** @deprecated */
case DEFAULT_QR_CODE_ERROR_CORRECTION = 'DEFAULT_QR_CODE_ERROR_CORRECTION';
/** @deprecated */
case DEFAULT_QR_CODE_ROUND_BLOCK_SIZE = 'DEFAULT_QR_CODE_ROUND_BLOCK_SIZE';
/** @deprecated */
case QR_CODE_FOR_DISABLED_SHORT_URLS = 'QR_CODE_FOR_DISABLED_SHORT_URLS';
/** @deprecated */
case DEFAULT_QR_CODE_COLOR = 'DEFAULT_QR_CODE_COLOR';
/** @deprecated */
case DEFAULT_QR_CODE_BG_COLOR = 'DEFAULT_QR_CODE_BG_COLOR';
/** @deprecated */
case DEFAULT_QR_CODE_LOGO_URL = 'DEFAULT_QR_CODE_LOGO_URL';
public function loadFromEnv(): mixed
{

View File

@@ -15,6 +15,7 @@ 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;
/** @deprecated */
final readonly class QrCodeOptions
{
public function __construct(

View File

@@ -7,6 +7,7 @@ namespace ShlinkioApiTest\Shlink\Core\Action;
use PHPUnit\Framework\Attributes\Test;
use Shlinkio\Shlink\TestUtils\ApiTest\ApiTestCase;
/** @deprecated */
class QrCodeTest extends ApiTestCase
{
#[Test]

View File

@@ -9,7 +9,6 @@ use Laminas\Diactoros\ServerRequest;
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;
@@ -286,30 +285,6 @@ class QrCodeActionTest extends TestCase
);
}
#[Test]
#[TestWith([[], '4696E5'])] // The logo has Shlink's brand color
#[TestWith([['logo' => 'invalid'], '4696E5'])] // Invalid `logo` values are ignored. Default logo is still rendered
#[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's logo
$code = 'abc123';
$req = ServerRequestFactory::fromGlobals()
->withAttribute('shortCode', $code)
->withQueryParams($query);
$this->urlResolver->method('resolveEnabledShortUrl')->willReturn(ShortUrl::withLongUrl('https://shlink.io'));
$handler = $this->createMock(RequestHandlerInterface::class);
$resp = $this->action(new QrCodeOptions(size: 250, logoUrl: $logoUrl))->process($req, $handler);
$image = imagecreatefromstring($resp->getBody()->__toString());
self::assertNotFalse($image);
// At around 100x100 px we can already find the logo, if present
$resultingColor = imagecolorat($image, 100, 100);
self::assertEquals(hexdec($expectedColor), $resultingColor);
}
public static function provideEnabled(): iterable
{
yield 'always enabled' => [true];