mirror of
https://github.com/shlinkio/shlink.git
synced 2025-12-15 14:55:04 -06:00
Merge pull request #2515 from acelaya-forks/remove-qr-codes
Drop support for QR code generation
This commit is contained in:
commit
f9ec4cea62
17
CHANGELOG.md
17
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
|
||||
* [#2514](https://github.com/shlinkio/shlink/issues/2514) Remove support to generate QR codes. This functionality is now handled by Shlink Web Client and Shlink Dashboard.
|
||||
|
||||
### Fixed
|
||||
* *Nothing*
|
||||
|
||||
|
||||
## [4.6.0] - 2025-11-01
|
||||
### Added
|
||||
* [#2327](https://github.com/shlinkio/shlink/issues/2327) Allow filtering short URL lists by those not including certain tags.
|
||||
|
||||
@ -16,9 +16,8 @@ WORKDIR /etc/shlink
|
||||
# Install required PHP extensions
|
||||
RUN \
|
||||
# Temp install dev dependencies needed to compile the extensions
|
||||
# FIXME Deprecated image-related extensions. They can be removed with QR-code support
|
||||
apk add --no-cache --virtual .dev-deps sqlite-dev postgresql-dev icu-dev libzip-dev zlib-dev libpng-dev linux-headers && \
|
||||
docker-php-ext-install -j"$(nproc)" pdo_mysql pdo_pgsql intl calendar sockets bcmath zip gd && \
|
||||
apk add --no-cache --virtual .dev-deps sqlite-dev postgresql-dev icu-dev libzip-dev zlib-dev linux-headers && \
|
||||
docker-php-ext-install -j"$(nproc)" pdo_mysql pdo_pgsql intl calendar sockets bcmath zip && \
|
||||
apk add --no-cache sqlite-libs && \
|
||||
docker-php-ext-install -j"$(nproc)" pdo_sqlite && \
|
||||
# Remove temp dev extensions, and install prod equivalents that are required at runtime
|
||||
|
||||
@ -36,10 +36,9 @@ The idea is that you can just generate a container using the image and provide t
|
||||
|
||||
First, make sure the host where you are going to run shlink fulfills these requirements:
|
||||
|
||||
* PHP 8.3 or 8.4
|
||||
* PHP 8.4 or 8.5
|
||||
* The next PHP extensions: json, curl, pdo, intl, gd and gmp/bcmath.
|
||||
* apcu extension is recommended if you don't plan to use RoadRunner.
|
||||
* xml extension is required if you want to generate QR codes in svg format.
|
||||
* sockets and bcmath extensions are required if you want to integrate with a RabbitMQ instance.
|
||||
* MySQL, MariaDB, PostgreSQL, MicrosoftSQL or SQLite.
|
||||
* You will also need the corresponding pdo variation for the database you are planning to use: `pdo_mysql`, `pdo_pgsql`, `pdo_sqlsrv` or `pdo_sqlite`.
|
||||
|
||||
@ -14,7 +14,6 @@
|
||||
"require": {
|
||||
"php": "^8.3",
|
||||
"ext-curl": "*",
|
||||
"ext-gd": "*",
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"ext-pdo": "*",
|
||||
@ -24,7 +23,6 @@
|
||||
"doctrine/migrations": "^3.9",
|
||||
"doctrine/orm": "^3.5",
|
||||
"donatj/phpuseragentparser": "^1.10",
|
||||
"endroid/qr-code": "^6.0.5",
|
||||
"friendsofphp/proxy-manager-lts": "^1.0",
|
||||
"geoip2/geoip2": "^3.1",
|
||||
"guzzlehttp/guzzle": "^7.9",
|
||||
@ -47,7 +45,7 @@
|
||||
"shlinkio/shlink-config": "^4.0",
|
||||
"shlinkio/shlink-event-dispatcher": "^4.3",
|
||||
"shlinkio/shlink-importer": "^5.6",
|
||||
"shlinkio/shlink-installer": "^9.7",
|
||||
"shlinkio/shlink-installer": "dev-develop#2b9e6bd as 10.0.0",
|
||||
"shlinkio/shlink-ip-geolocation": "^4.4",
|
||||
"shlinkio/shlink-json": "^1.2",
|
||||
"spiral/roadrunner": "^2025.1",
|
||||
|
||||
@ -60,15 +60,6 @@ return [
|
||||
Option\Tracking\DisableIpTrackingConfigOption::class,
|
||||
Option\Tracking\DisableReferrerTrackingConfigOption::class,
|
||||
Option\Tracking\DisableUaTrackingConfigOption::class,
|
||||
Option\QrCode\DefaultSizeConfigOption::class,
|
||||
Option\QrCode\DefaultMarginConfigOption::class,
|
||||
Option\QrCode\DefaultFormatConfigOption::class,
|
||||
Option\QrCode\DefaultErrorCorrectionConfigOption::class,
|
||||
Option\QrCode\DefaultRoundBlockSizeConfigOption::class,
|
||||
Option\QrCode\DefaultColorConfigOption::class,
|
||||
Option\QrCode\DefaultBgColorConfigOption::class,
|
||||
Option\QrCode\DefaultLogoUrlConfigOption::class,
|
||||
Option\QrCode\EnabledForDisabledShortUrlsConfigOption::class,
|
||||
Option\RabbitMq\RabbitMqEnabledConfigOption::class,
|
||||
Option\RabbitMq\RabbitMqHostConfigOption::class,
|
||||
Option\RabbitMq\RabbitMqUseSslConfigOption::class,
|
||||
|
||||
@ -94,14 +94,6 @@ return (static function (): array {
|
||||
],
|
||||
'allowed_methods' => [RequestMethodInterface::METHOD_GET],
|
||||
],
|
||||
[
|
||||
'name' => CoreAction\QrCodeAction::class,
|
||||
'path' => '/{shortCode}/qr-code',
|
||||
'middleware' => [
|
||||
CoreAction\QrCodeAction::class,
|
||||
],
|
||||
'allowed_methods' => [RequestMethodInterface::METHOD_GET],
|
||||
],
|
||||
[
|
||||
'name' => CoreAction\RedirectAction::class,
|
||||
'path' => sprintf('/{shortCode}%s', $shortUrlRouteSuffix),
|
||||
|
||||
@ -38,20 +38,3 @@ const ISO_COUNTRY_CODES = [
|
||||
'TO', 'TT', 'TN', 'TR', 'TM', 'TC', 'TV', 'UG', 'UA', 'AE', 'GB', 'US', 'UM', 'UY', 'UZ', 'VU',
|
||||
'VE', 'VN', 'VG', 'VI', 'WF', 'EH', 'YE', 'ZM', 'ZW',
|
||||
];
|
||||
|
||||
/** @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
|
||||
|
||||
@ -24,9 +24,6 @@ RUN docker-php-ext-install intl
|
||||
RUN apk add --no-cache libzip-dev zlib-dev
|
||||
RUN docker-php-ext-install zip
|
||||
|
||||
RUN apk add --no-cache libpng-dev
|
||||
RUN docker-php-ext-install gd
|
||||
|
||||
RUN apk add --no-cache postgresql-dev
|
||||
RUN docker-php-ext-install pdo_pgsql
|
||||
|
||||
|
||||
@ -25,9 +25,6 @@ RUN docker-php-ext-install intl
|
||||
RUN apk add --no-cache libzip-dev zlib-dev
|
||||
RUN docker-php-ext-install zip
|
||||
|
||||
RUN apk add --no-cache libpng-dev
|
||||
RUN docker-php-ext-install gd
|
||||
|
||||
RUN apk add --no-cache postgresql-dev
|
||||
RUN docker-php-ext-install pdo_pgsql
|
||||
|
||||
|
||||
@ -24,9 +24,6 @@ RUN docker-php-ext-install intl
|
||||
RUN apk add --no-cache libzip-dev zlib-dev
|
||||
RUN docker-php-ext-install zip
|
||||
|
||||
RUN apk add --no-cache libpng-dev
|
||||
RUN docker-php-ext-install gd
|
||||
|
||||
RUN apk add --no-cache postgresql-dev
|
||||
RUN docker-php-ext-install pdo_pgsql
|
||||
|
||||
|
||||
@ -1,121 +0,0 @@
|
||||
{
|
||||
"get": {
|
||||
"deprecated": true,
|
||||
"operationId": "shortUrlQrCode",
|
||||
"tags": [
|
||||
"URL Shortener"
|
||||
],
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"name": "size",
|
||||
"in": "query",
|
||||
"description": "The size of the image to be returned.",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"minimum": 50,
|
||||
"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"],
|
||||
"default": "png"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "margin",
|
||||
"in": "query",
|
||||
"description": "The margin around the QR code image.",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "errorCorrection",
|
||||
"in": "query",
|
||||
"description": "The error correction level to apply to the QR code: **[L]**ow, **[M]**edium, **[Q]**uartile or **[H]**igh. See [docs](https://www.qrcode.com/en/about/error_correction.html).",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"enum": ["L", "M", "Q", "H"],
|
||||
"default": "L"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "roundBlockSize",
|
||||
"in": "query",
|
||||
"description": "Allows to disable block size rounding, which might reduce the readability of the QR code, but ensures no extra margin is added.",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"enum": ["true", "false"],
|
||||
"default": "false"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "color",
|
||||
"in": "query",
|
||||
"description": "The QR code foreground color. It should be an hex representation of a color, in 3 or 6 characters, optionally preceded by the \"#\" character.",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"default": "#000000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "bgColor",
|
||||
"in": "query",
|
||||
"description": "The QR code background color. It should be an hex representation of a color, in 3 or 6 characters, optionally preceded by the \"#\" character.",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"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": {
|
||||
"200": {
|
||||
"description": "QR code in PNG format",
|
||||
"content": {
|
||||
"image/png": {
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "binary"
|
||||
}
|
||||
},
|
||||
"image/svg+xml": {
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "binary"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -133,9 +133,6 @@
|
||||
},
|
||||
"/{shortCode}/track": {
|
||||
"$ref": "paths/{shortCode}_track.json"
|
||||
},
|
||||
"/{shortCode}/qr-code": {
|
||||
"$ref": "paths/{shortCode}_qr-code.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,7 +33,6 @@ return [
|
||||
Config\Options\RedirectOptions::class => [Config\Options\RedirectOptions::class, 'fromEnv'],
|
||||
Config\Options\UrlShortenerOptions::class => [Config\Options\UrlShortenerOptions::class, 'fromEnv'],
|
||||
Config\Options\TrackingOptions::class => [Config\Options\TrackingOptions::class, 'fromEnv'],
|
||||
Config\Options\QrCodeOptions::class => [Config\Options\QrCodeOptions::class, 'fromEnv'],
|
||||
Config\Options\RabbitMqOptions::class => [Config\Options\RabbitMqOptions::class, 'fromEnv'],
|
||||
Config\Options\RobotsOptions::class => [Config\Options\RobotsOptions::class, 'fromEnv'],
|
||||
Config\Options\RealTimeUpdatesOptions::class => [Config\Options\RealTimeUpdatesOptions::class, 'fromEnv'],
|
||||
@ -103,7 +102,6 @@ return [
|
||||
|
||||
Action\RedirectAction::class => ConfigAbstractFactory::class,
|
||||
Action\PixelAction::class => ConfigAbstractFactory::class,
|
||||
Action\QrCodeAction::class => ConfigAbstractFactory::class,
|
||||
Action\RobotsAction::class => ConfigAbstractFactory::class,
|
||||
|
||||
EventDispatcher\PublishingUpdatesGenerator::class => ConfigAbstractFactory::class,
|
||||
@ -209,12 +207,6 @@ return [
|
||||
Util\RedirectResponseHelper::class,
|
||||
],
|
||||
Action\PixelAction::class => [ShortUrl\ShortUrlResolver::class, Visit\RequestTracker::class],
|
||||
Action\QrCodeAction::class => [
|
||||
ShortUrl\ShortUrlResolver::class,
|
||||
ShortUrl\Helper\ShortUrlStringifier::class,
|
||||
'Logger_Shlink',
|
||||
Config\Options\QrCodeOptions::class,
|
||||
],
|
||||
Action\RobotsAction::class => [Crawling\CrawlingHelper::class, Config\Options\RobotsOptions::class],
|
||||
|
||||
ShortUrl\Resolver\PersistenceShortUrlRelationResolver::class => [
|
||||
|
||||
@ -1,161 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Core\Action\Model;
|
||||
|
||||
use Endroid\QrCode\Color\Color;
|
||||
use Endroid\QrCode\Color\ColorInterface;
|
||||
use Endroid\QrCode\ErrorCorrectionLevel;
|
||||
use Endroid\QrCode\RoundBlockSizeMode;
|
||||
use Endroid\QrCode\Writer\PngWriter;
|
||||
use Endroid\QrCode\Writer\SvgWriter;
|
||||
use Endroid\QrCode\Writer\WriterInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Shlinkio\Shlink\Core\Config\Options\QrCodeOptions;
|
||||
|
||||
use function ctype_xdigit;
|
||||
use function hexdec;
|
||||
use function ltrim;
|
||||
use function max;
|
||||
use function min;
|
||||
use function Shlinkio\Shlink\Core\ArrayUtils\contains;
|
||||
use function strlen;
|
||||
use function strtolower;
|
||||
use function substr;
|
||||
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;
|
||||
private const int MAX_SIZE = 1000;
|
||||
private const array SUPPORTED_FORMATS = ['png', 'svg'];
|
||||
|
||||
private function __construct(
|
||||
public int $size,
|
||||
public int $margin,
|
||||
public WriterInterface $writer,
|
||||
public array $writerOptions,
|
||||
public ErrorCorrectionLevel $errorCorrectionLevel,
|
||||
public RoundBlockSizeMode $roundBlockSizeMode,
|
||||
public ColorInterface $color,
|
||||
public ColorInterface $bgColor,
|
||||
public bool $disableLogo,
|
||||
) {
|
||||
}
|
||||
|
||||
public static function fromRequest(ServerRequestInterface $request, QrCodeOptions $defaults): self
|
||||
{
|
||||
$query = $request->getQueryParams();
|
||||
[$writer, $writerOptions] = self::resolveWriterAndWriterOptions($query, $defaults);
|
||||
|
||||
return new self(
|
||||
size: self::resolveSize($query, $defaults),
|
||||
margin: self::resolveMargin($query, $defaults),
|
||||
writer: $writer,
|
||||
writerOptions: $writerOptions,
|
||||
errorCorrectionLevel: self::resolveErrorCorrection($query, $defaults),
|
||||
roundBlockSizeMode: self::resolveRoundBlockSize($query, $defaults),
|
||||
color: self::resolveColor($query, $defaults),
|
||||
bgColor: self::resolveBackgroundColor($query, $defaults),
|
||||
disableLogo: isset($query['logo']) && $query['logo'] === 'disable',
|
||||
);
|
||||
}
|
||||
|
||||
private static function resolveSize(array $query, QrCodeOptions $defaults): int
|
||||
{
|
||||
$size = (int) ($query['size'] ?? $defaults->size);
|
||||
if ($size < self::MIN_SIZE) {
|
||||
return self::MIN_SIZE;
|
||||
}
|
||||
|
||||
return min($size, self::MAX_SIZE);
|
||||
}
|
||||
|
||||
private static function resolveMargin(array $query, QrCodeOptions $defaults): int
|
||||
{
|
||||
$margin = $query['margin'] ?? (string) $defaults->margin;
|
||||
$intMargin = (int) $margin;
|
||||
if ($margin !== (string) $intMargin) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return max($intMargin, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{WriterInterface, array}
|
||||
*/
|
||||
private static function resolveWriterAndWriterOptions(array $query, QrCodeOptions $defaults): array
|
||||
{
|
||||
$qFormat = self::normalizeParam($query['format'] ?? '');
|
||||
$format = contains($qFormat, self::SUPPORTED_FORMATS) ? $qFormat : self::normalizeParam($defaults->format);
|
||||
|
||||
return match ($format) {
|
||||
'svg' => [new SvgWriter(), []],
|
||||
default => [new PngWriter(), [PngWriter::WRITER_OPTION_NUMBER_OF_COLORS => null]],
|
||||
};
|
||||
}
|
||||
|
||||
private static function resolveErrorCorrection(array $query, QrCodeOptions $defaults): ErrorCorrectionLevel
|
||||
{
|
||||
$errorCorrectionLevel = self::normalizeParam($query['errorCorrection'] ?? $defaults->errorCorrection);
|
||||
return match ($errorCorrectionLevel) {
|
||||
'h' => ErrorCorrectionLevel::High,
|
||||
'q' => ErrorCorrectionLevel::Quartile,
|
||||
'm' => ErrorCorrectionLevel::Medium,
|
||||
default => ErrorCorrectionLevel::Low, // 'l'
|
||||
};
|
||||
}
|
||||
|
||||
private static function resolveRoundBlockSize(array $query, QrCodeOptions $defaults): RoundBlockSizeMode
|
||||
{
|
||||
$doNotRoundBlockSize = isset($query['roundBlockSize'])
|
||||
? $query['roundBlockSize'] === 'false'
|
||||
: ! $defaults->roundBlockSize;
|
||||
return $doNotRoundBlockSize ? RoundBlockSizeMode::None : RoundBlockSizeMode::Margin;
|
||||
}
|
||||
|
||||
private static function resolveColor(array $query, QrCodeOptions $defaults): ColorInterface
|
||||
{
|
||||
$color = self::normalizeParam($query['color'] ?? $defaults->color);
|
||||
return self::parseHexColor($color, DEFAULT_QR_CODE_COLOR);
|
||||
}
|
||||
|
||||
private static function resolveBackgroundColor(array $query, QrCodeOptions $defaults): ColorInterface
|
||||
{
|
||||
$bgColor = self::normalizeParam($query['bgColor'] ?? $defaults->bgColor);
|
||||
return self::parseHexColor($bgColor, DEFAULT_QR_CODE_BG_COLOR);
|
||||
}
|
||||
|
||||
private static function parseHexColor(string $hexColor, string|null $fallback): Color
|
||||
{
|
||||
$hexColor = ltrim($hexColor, '#');
|
||||
if (! ctype_xdigit($hexColor) && $fallback !== null) {
|
||||
return self::parseHexColor($fallback, null);
|
||||
}
|
||||
|
||||
if (strlen($hexColor) === 3) {
|
||||
return new Color(
|
||||
(int) hexdec(substr($hexColor, 0, 1) . substr($hexColor, 0, 1)),
|
||||
(int) hexdec(substr($hexColor, 1, 1) . substr($hexColor, 1, 1)),
|
||||
(int) hexdec(substr($hexColor, 2, 1) . substr($hexColor, 2, 1)),
|
||||
);
|
||||
}
|
||||
|
||||
return new Color(
|
||||
(int) hexdec(substr($hexColor, 0, 2)),
|
||||
(int) hexdec(substr($hexColor, 2, 2)),
|
||||
(int) hexdec(substr($hexColor, 4, 2)),
|
||||
);
|
||||
}
|
||||
|
||||
private static function normalizeParam(string $param): string
|
||||
{
|
||||
return strtolower(trim($param));
|
||||
}
|
||||
}
|
||||
@ -1,74 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Core\Action;
|
||||
|
||||
use Endroid\QrCode\Builder\Builder;
|
||||
use Endroid\QrCode\Writer\Result\ResultInterface;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Shlinkio\Shlink\Common\Response\QrCodeResponse;
|
||||
use Shlinkio\Shlink\Core\Action\Model\QrCodeParams;
|
||||
use Shlinkio\Shlink\Core\Config\Options\QrCodeOptions;
|
||||
use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException;
|
||||
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(
|
||||
private ShortUrlResolverInterface $urlResolver,
|
||||
private ShortUrlStringifierInterface $stringifier,
|
||||
private LoggerInterface $logger,
|
||||
private QrCodeOptions $options,
|
||||
) {
|
||||
}
|
||||
|
||||
public function process(Request $request, RequestHandlerInterface $handler): Response
|
||||
{
|
||||
$identifier = ShortUrlIdentifier::fromRedirectRequest($request);
|
||||
|
||||
try {
|
||||
$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->options);
|
||||
$qrCodeBuilder = new Builder(
|
||||
writer: $params->writer,
|
||||
writerOptions: $params->writerOptions,
|
||||
data: $this->stringifier->stringify($shortUrl),
|
||||
errorCorrectionLevel: $params->errorCorrectionLevel,
|
||||
size: $params->size,
|
||||
margin: $params->margin,
|
||||
roundBlockSizeMode: $params->roundBlockSizeMode,
|
||||
foregroundColor: $params->color,
|
||||
backgroundColor: $params->bgColor,
|
||||
);
|
||||
|
||||
return new QrCodeResponse($this->buildQrCode($qrCodeBuilder, $params));
|
||||
}
|
||||
|
||||
private function buildQrCode(Builder $qrCodeBuilder, QrCodeParams $params): ResultInterface
|
||||
{
|
||||
$logoUrl = $this->options->logoUrl;
|
||||
if ($logoUrl === null || $params->disableLogo) {
|
||||
return $qrCodeBuilder->build();
|
||||
}
|
||||
|
||||
return $qrCodeBuilder->build(
|
||||
logoPath: $logoUrl,
|
||||
logoResizeToHeight: (int) ($params->size / 4),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -13,14 +13,6 @@ use function Shlinkio\Shlink\Config\env;
|
||||
use function Shlinkio\Shlink\Config\parseEnvVar;
|
||||
use function sprintf;
|
||||
|
||||
use const Shlinkio\Shlink\DEFAULT_QR_CODE_BG_COLOR;
|
||||
use const Shlinkio\Shlink\DEFAULT_QR_CODE_COLOR;
|
||||
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;
|
||||
use const Shlinkio\Shlink\DEFAULT_REDIRECT_CACHE_LIFETIME;
|
||||
use const Shlinkio\Shlink\DEFAULT_REDIRECT_STATUS_CODE;
|
||||
use const Shlinkio\Shlink\DEFAULT_SHORT_CODES_LENGTH;
|
||||
@ -97,24 +89,6 @@ enum EnvVars: string
|
||||
|
||||
/** @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
|
||||
{
|
||||
@ -173,15 +147,6 @@ enum EnvVars: string
|
||||
self::MERCURE_ENABLED => self::MERCURE_PUBLIC_HUB_URL->existsInEnv(),
|
||||
self::MERCURE_INTERNAL_HUB_URL => self::MERCURE_PUBLIC_HUB_URL->loadFromEnv(),
|
||||
|
||||
self::DEFAULT_QR_CODE_SIZE, => DEFAULT_QR_CODE_SIZE,
|
||||
self::DEFAULT_QR_CODE_MARGIN, => DEFAULT_QR_CODE_MARGIN,
|
||||
self::DEFAULT_QR_CODE_FORMAT, => DEFAULT_QR_CODE_FORMAT,
|
||||
self::DEFAULT_QR_CODE_ERROR_CORRECTION, => DEFAULT_QR_CODE_ERROR_CORRECTION,
|
||||
self::DEFAULT_QR_CODE_ROUND_BLOCK_SIZE, => DEFAULT_QR_CODE_ROUND_BLOCK_SIZE,
|
||||
self::QR_CODE_FOR_DISABLED_SHORT_URLS, => DEFAULT_QR_CODE_ENABLED_FOR_DISABLED_SHORT_URLS,
|
||||
self::DEFAULT_QR_CODE_COLOR, => DEFAULT_QR_CODE_COLOR,
|
||||
self::DEFAULT_QR_CODE_BG_COLOR, => DEFAULT_QR_CODE_BG_COLOR,
|
||||
|
||||
self::RABBITMQ_ENABLED, self::RABBITMQ_USE_SSL => false,
|
||||
self::RABBITMQ_PORT => 5672,
|
||||
self::RABBITMQ_VHOST => '/',
|
||||
|
||||
@ -1,48 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Core\Config\Options;
|
||||
|
||||
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||
|
||||
use const Shlinkio\Shlink\DEFAULT_QR_CODE_BG_COLOR;
|
||||
use const Shlinkio\Shlink\DEFAULT_QR_CODE_COLOR;
|
||||
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;
|
||||
|
||||
/** @deprecated */
|
||||
final readonly class QrCodeOptions
|
||||
{
|
||||
public function __construct(
|
||||
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,
|
||||
public string $color = DEFAULT_QR_CODE_COLOR,
|
||||
public string $bgColor = DEFAULT_QR_CODE_BG_COLOR,
|
||||
public string|null $logoUrl = null,
|
||||
) {
|
||||
}
|
||||
|
||||
public static function fromEnv(): self
|
||||
{
|
||||
return new self(
|
||||
size: (int) EnvVars::DEFAULT_QR_CODE_SIZE->loadFromEnv(),
|
||||
margin: (int) EnvVars::DEFAULT_QR_CODE_MARGIN->loadFromEnv(),
|
||||
format: EnvVars::DEFAULT_QR_CODE_FORMAT->loadFromEnv(),
|
||||
errorCorrection: EnvVars::DEFAULT_QR_CODE_ERROR_CORRECTION->loadFromEnv(),
|
||||
roundBlockSize: (bool) EnvVars::DEFAULT_QR_CODE_ROUND_BLOCK_SIZE->loadFromEnv(),
|
||||
enabledForDisabledShortUrls: (bool) EnvVars::QR_CODE_FOR_DISABLED_SHORT_URLS->loadFromEnv(),
|
||||
color: EnvVars::DEFAULT_QR_CODE_COLOR->loadFromEnv(),
|
||||
bgColor: EnvVars::DEFAULT_QR_CODE_BG_COLOR->loadFromEnv(),
|
||||
logoUrl: EnvVars::DEFAULT_QR_CODE_LOGO_URL->loadFromEnv(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioApiTest\Shlink\Core\Action;
|
||||
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Shlinkio\Shlink\TestUtils\ApiTest\ApiTestCase;
|
||||
|
||||
/** @deprecated */
|
||||
class QrCodeTest extends ApiTestCase
|
||||
{
|
||||
#[Test]
|
||||
public function returnsQrCodeEvenIfShortUrlIsNotEnabled(): void
|
||||
{
|
||||
// The QR code successfully resolves at first
|
||||
$response = $this->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 short URL returns a 404, but the QR code should still work
|
||||
self::assertEquals(404, $this->callShortUrl('custom')->getStatusCode());
|
||||
self::assertEquals(200, $this->callShortUrl('custom/qr-code')->getStatusCode());
|
||||
}
|
||||
}
|
||||
@ -1,303 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Core\Action;
|
||||
|
||||
use Laminas\Diactoros\Response;
|
||||
use Laminas\Diactoros\ServerRequest;
|
||||
use Laminas\Diactoros\ServerRequestFactory;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
use Shlinkio\Shlink\Common\Response\QrCodeResponse;
|
||||
use Shlinkio\Shlink\Core\Action\QrCodeAction;
|
||||
use Shlinkio\Shlink\Core\Config\Options\QrCodeOptions;
|
||||
use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifier;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlIdentifier;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\ShortUrlResolverInterface;
|
||||
|
||||
use function getimagesizefromstring;
|
||||
use function hexdec;
|
||||
use function imagecolorat;
|
||||
use function imagecreatefromstring;
|
||||
|
||||
use const Shlinkio\Shlink\DEFAULT_QR_CODE_COLOR;
|
||||
|
||||
class QrCodeActionTest extends TestCase
|
||||
{
|
||||
private const int WHITE = 0xFFFFFF;
|
||||
private const int BLACK = 0x0;
|
||||
|
||||
private MockObject & ShortUrlResolverInterface $urlResolver;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->urlResolver = $this->createMock(ShortUrlResolverInterface::class);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function aNotFoundShortCodeWillDelegateIntoNextMiddleware(): void
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$this->urlResolver->expects($this->once())->method('resolveEnabledShortUrl')->with(
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain($shortCode, ''),
|
||||
)->willThrowException(ShortUrlNotFoundException::fromNotFound(ShortUrlIdentifier::fromShortCodeAndDomain('')));
|
||||
$handler = $this->createMock(RequestHandlerInterface::class);
|
||||
$handler->expects($this->once())->method('handle')->withAnyParameters()->willReturn(new Response());
|
||||
|
||||
$this->action()->process((new ServerRequest())->withAttribute('shortCode', $shortCode), $handler);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function aCorrectRequestReturnsTheQrCodeResponse(): void
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$this->urlResolver->expects($this->once())->method('resolveEnabledShortUrl')->with(
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain($shortCode, ''),
|
||||
)->willReturn(ShortUrl::createFake());
|
||||
$handler = $this->createMock(RequestHandlerInterface::class);
|
||||
$handler->expects($this->never())->method('handle');
|
||||
|
||||
$resp = $this->action()->process((new ServerRequest())->withAttribute('shortCode', $shortCode), $handler);
|
||||
|
||||
self::assertInstanceOf(QrCodeResponse::class, $resp);
|
||||
self::assertEquals(200, $resp->getStatusCode());
|
||||
}
|
||||
|
||||
#[Test, DataProvider('provideQueries')]
|
||||
public function imageIsReturnedWithExpectedContentTypeBasedOnProvidedFormat(
|
||||
string $defaultFormat,
|
||||
array $query,
|
||||
string $expectedContentType,
|
||||
): void {
|
||||
$code = 'abc123';
|
||||
$this->urlResolver->method('resolveEnabledShortUrl')->willReturn(ShortUrl::createFake());
|
||||
$handler = $this->createMock(RequestHandlerInterface::class);
|
||||
$req = (new ServerRequest())->withAttribute('shortCode', $code)->withQueryParams($query);
|
||||
|
||||
$resp = $this->action(new QrCodeOptions(format: $defaultFormat))->process($req, $handler);
|
||||
|
||||
self::assertEquals($expectedContentType, $resp->getHeaderLine('Content-Type'));
|
||||
}
|
||||
|
||||
public static function provideQueries(): iterable
|
||||
{
|
||||
yield 'no format, png default' => ['png', [], 'image/png'];
|
||||
yield 'no format, svg default' => ['svg', [], 'image/svg+xml'];
|
||||
yield 'png format, png default' => ['png', ['format' => 'png'], 'image/png'];
|
||||
yield 'png format, svg default' => ['svg', ['format' => 'png'], 'image/png'];
|
||||
yield 'svg format, png default' => ['png', ['format' => 'svg'], 'image/svg+xml'];
|
||||
yield 'svg format, svg default' => ['svg', ['format' => 'svg'], 'image/svg+xml'];
|
||||
yield 'unsupported format, png default' => ['png', ['format' => 'jpg'], 'image/png'];
|
||||
yield 'unsupported format, svg default' => ['svg', ['format' => 'jpg'], 'image/svg+xml'];
|
||||
}
|
||||
|
||||
#[Test, DataProvider('provideRequestsWithSize')]
|
||||
public function imageIsReturnedWithExpectedSize(
|
||||
QrCodeOptions $defaultOptions,
|
||||
ServerRequestInterface $req,
|
||||
int $expectedSize,
|
||||
): void {
|
||||
$code = 'abc123';
|
||||
$this->urlResolver->method('resolveEnabledShortUrl')->willReturn(ShortUrl::createFake());
|
||||
$handler = $this->createMock(RequestHandlerInterface::class);
|
||||
|
||||
$resp = $this->action($defaultOptions)->process($req->withAttribute('shortCode', $code), $handler);
|
||||
$result = getimagesizefromstring($resp->getBody()->__toString());
|
||||
self::assertNotFalse($result);
|
||||
|
||||
[$size] = $result;
|
||||
self::assertEquals($expectedSize, $size);
|
||||
}
|
||||
|
||||
public static function provideRequestsWithSize(): iterable
|
||||
{
|
||||
yield 'different margin and size defaults' => [
|
||||
new QrCodeOptions(size: 660, margin: 40),
|
||||
ServerRequestFactory::fromGlobals(),
|
||||
740,
|
||||
];
|
||||
yield 'no size' => [new QrCodeOptions(), ServerRequestFactory::fromGlobals(), 300];
|
||||
yield 'no size, different default' => [new QrCodeOptions(size: 500), ServerRequestFactory::fromGlobals(), 500];
|
||||
yield 'size in query' => [
|
||||
new QrCodeOptions(),
|
||||
ServerRequestFactory::fromGlobals()->withQueryParams(['size' => '123']),
|
||||
123,
|
||||
];
|
||||
yield 'size in query, default margin' => [
|
||||
new QrCodeOptions(margin: 25),
|
||||
ServerRequestFactory::fromGlobals()->withQueryParams(['size' => '123']),
|
||||
173,
|
||||
];
|
||||
yield 'margin' => [
|
||||
new QrCodeOptions(),
|
||||
ServerRequestFactory::fromGlobals()->withQueryParams(['margin' => '35']),
|
||||
370,
|
||||
];
|
||||
yield 'margin and different default' => [
|
||||
new QrCodeOptions(size: 400),
|
||||
ServerRequestFactory::fromGlobals()->withQueryParams(['margin' => '35']),
|
||||
470,
|
||||
];
|
||||
yield 'margin and size' => [
|
||||
new QrCodeOptions(),
|
||||
ServerRequestFactory::fromGlobals()->withQueryParams(['margin' => '100', 'size' => '200']),
|
||||
400,
|
||||
];
|
||||
yield 'negative margin' => [
|
||||
new QrCodeOptions(),
|
||||
ServerRequestFactory::fromGlobals()->withQueryParams(['margin' => '-50']),
|
||||
300,
|
||||
];
|
||||
yield 'negative margin, default margin' => [
|
||||
new QrCodeOptions(margin: 10),
|
||||
ServerRequestFactory::fromGlobals()->withQueryParams(['margin' => '-50']),
|
||||
300,
|
||||
];
|
||||
yield 'non-numeric margin' => [
|
||||
new QrCodeOptions(),
|
||||
ServerRequestFactory::fromGlobals()->withQueryParams(['margin' => 'foo']),
|
||||
300,
|
||||
];
|
||||
yield 'negative margin and size' => [
|
||||
new QrCodeOptions(),
|
||||
ServerRequestFactory::fromGlobals()->withQueryParams(['margin' => '-1', 'size' => '150']),
|
||||
150,
|
||||
];
|
||||
yield 'negative margin and size, default margin' => [
|
||||
new QrCodeOptions(margin: 5),
|
||||
ServerRequestFactory::fromGlobals()->withQueryParams(['margin' => '-1', 'size' => '150']),
|
||||
150,
|
||||
];
|
||||
yield 'non-numeric margin and size' => [
|
||||
new QrCodeOptions(),
|
||||
ServerRequestFactory::fromGlobals()->withQueryParams(['margin' => 'foo', 'size' => '538']),
|
||||
538,
|
||||
];
|
||||
}
|
||||
|
||||
#[Test, DataProvider('provideRoundBlockSize')]
|
||||
public function imageCanRemoveExtraMarginWhenBlockRoundIsDisabled(
|
||||
QrCodeOptions $defaultOptions,
|
||||
string|null $roundBlockSize,
|
||||
int $expectedColor,
|
||||
): void {
|
||||
$code = 'abc123';
|
||||
$req = ServerRequestFactory::fromGlobals()
|
||||
->withQueryParams(['size' => 250, 'roundBlockSize' => $roundBlockSize])
|
||||
->withAttribute('shortCode', $code);
|
||||
|
||||
$this->urlResolver->method('resolveEnabledShortUrl')->willReturn(ShortUrl::withLongUrl('https://shlink.io'));
|
||||
$handler = $this->createMock(RequestHandlerInterface::class);
|
||||
|
||||
$resp = $this->action($defaultOptions)->process($req, $handler);
|
||||
$image = imagecreatefromstring($resp->getBody()->__toString());
|
||||
self::assertNotFalse($image);
|
||||
|
||||
$color = imagecolorat($image, 1, 1);
|
||||
self::assertEquals($expectedColor, $color);
|
||||
}
|
||||
|
||||
public static function provideRoundBlockSize(): iterable
|
||||
{
|
||||
yield 'no round block param' => [new QrCodeOptions(), null, self::WHITE];
|
||||
yield 'no round block param, but disabled by default' => [
|
||||
new QrCodeOptions(roundBlockSize: false),
|
||||
null,
|
||||
self::BLACK,
|
||||
];
|
||||
yield 'round block: "true"' => [new QrCodeOptions(), 'true', self::WHITE];
|
||||
yield 'round block: "true", but disabled by default' => [
|
||||
new QrCodeOptions(roundBlockSize: false),
|
||||
'true',
|
||||
self::WHITE,
|
||||
];
|
||||
yield 'round block: "false"' => [new QrCodeOptions(), 'false', self::BLACK];
|
||||
yield 'round block: "false", but enabled by default' => [
|
||||
new QrCodeOptions(roundBlockSize: true),
|
||||
'false',
|
||||
self::BLACK,
|
||||
];
|
||||
}
|
||||
|
||||
#[Test, DataProvider('provideColors')]
|
||||
public function properColorsAreUsed(string|null $queryColor, string|null $optionsColor, int $expectedColor): void
|
||||
{
|
||||
$code = 'abc123';
|
||||
$req = ServerRequestFactory::fromGlobals()
|
||||
->withQueryParams(['color' => $queryColor])
|
||||
->withAttribute('shortCode', $code);
|
||||
|
||||
$this->urlResolver->method('resolveEnabledShortUrl')->willReturn(ShortUrl::withLongUrl('https://shlink.io'));
|
||||
$handler = $this->createMock(RequestHandlerInterface::class);
|
||||
|
||||
$resp = $this->action(
|
||||
new QrCodeOptions(size: 250, roundBlockSize: false, color: $optionsColor ?? DEFAULT_QR_CODE_COLOR),
|
||||
)->process($req, $handler);
|
||||
$image = imagecreatefromstring($resp->getBody()->__toString());
|
||||
self::assertNotFalse($image);
|
||||
|
||||
$resultingColor = imagecolorat($image, 1, 1);
|
||||
self::assertEquals($expectedColor, $resultingColor);
|
||||
}
|
||||
|
||||
public static function provideColors(): iterable
|
||||
{
|
||||
yield 'no query, no default' => [null, null, self::BLACK];
|
||||
yield '6-char-query black' => ['000000', null, self::BLACK];
|
||||
yield '6-char-query white' => ['ffffff', null, self::WHITE];
|
||||
yield '6-char-query red' => ['ff0000', null, (int) hexdec('ff0000')];
|
||||
yield '3-char-query black' => ['000', null, self::BLACK];
|
||||
yield '3-char-query white' => ['fff', null, self::WHITE];
|
||||
yield '3-char-query red' => ['f00', null, (int) hexdec('ff0000')];
|
||||
yield '3-char-default red' => [null, 'f00', (int) hexdec('ff0000')];
|
||||
yield 'invalid color in query' => ['zzzzzzzz', null, self::BLACK];
|
||||
yield 'invalid color in query with default' => ['zzzzzzzz', 'aa88cc', self::BLACK];
|
||||
yield 'invalid color in default' => [null, 'zzzzzzzz', self::BLACK];
|
||||
}
|
||||
|
||||
#[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|null $options = null): QrCodeAction
|
||||
{
|
||||
return new QrCodeAction(
|
||||
$this->urlResolver,
|
||||
new ShortUrlStringifier(),
|
||||
new NullLogger(),
|
||||
$options ?? new QrCodeOptions(enabledForDisabledShortUrls: false),
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user