From bf09990f9c1e54a3a90bf814d998a2625a121157 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 6 Dec 2021 17:10:10 +0100 Subject: [PATCH 1/4] Added support to disable rounding on block size for QR codes --- composer.json | 2 +- config/autoload/qr-codes.global.php | 2 ++ config/constants.php | 1 + module/Core/src/Action/Model/QrCodeParams.php | 16 ++++++++++++++++ module/Core/src/Action/QrCodeAction.php | 3 ++- module/Core/src/Options/QrCodeOptions.php | 12 ++++++++++++ 6 files changed, 34 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index e985843e..b1badf18 100644 --- a/composer.json +++ b/composer.json @@ -47,7 +47,7 @@ "pugx/shortid-php": "^0.7", "ramsey/uuid": "^3.9", "rlanvin/php-ip": "3.0.0-rc2", - "shlinkio/shlink-common": "dev-main#2f3ac05 as 4.2", + "shlinkio/shlink-common": "dev-main#7cc36a6 as 4.2", "shlinkio/shlink-config": "^1.4", "shlinkio/shlink-event-dispatcher": "dev-main#3925299 as 2.3", "shlinkio/shlink-importer": "dev-main#d099072 as 2.5", diff --git a/config/autoload/qr-codes.global.php b/config/autoload/qr-codes.global.php index 1cf6fecb..5f528620 100644 --- a/config/autoload/qr-codes.global.php +++ b/config/autoload/qr-codes.global.php @@ -7,6 +7,7 @@ use function Shlinkio\Shlink\Common\env; 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; return [ @@ -16,6 +17,7 @@ return [ 'margin' => (int) env('DEFAULT_QR_CODE_MARGIN', DEFAULT_QR_CODE_MARGIN), 'format' => env('DEFAULT_QR_CODE_FORMAT', DEFAULT_QR_CODE_FORMAT), 'error_correction' => env('DEFAULT_QR_CODE_ERROR_CORRECTION', DEFAULT_QR_CODE_ERROR_CORRECTION), + 'round_block_size' => (bool) env('DEFAULT_QR_CODE_ROUND_BLOCK_SIZE', DEFAULT_QR_CODE_ROUND_BLOCK_SIZE), ], ]; diff --git a/config/constants.php b/config/constants.php index 6c7aa09e..8171cd66 100644 --- a/config/constants.php +++ b/config/constants.php @@ -18,4 +18,5 @@ 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 MIN_TASK_WORKERS = 4; diff --git a/module/Core/src/Action/Model/QrCodeParams.php b/module/Core/src/Action/Model/QrCodeParams.php index 0e889c32..47fb82d4 100644 --- a/module/Core/src/Action/Model/QrCodeParams.php +++ b/module/Core/src/Action/Model/QrCodeParams.php @@ -9,6 +9,9 @@ use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelInterface; use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelLow; use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelMedium; use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelQuartile; +use Endroid\QrCode\RoundBlockSizeMode\RoundBlockSizeModeInterface; +use Endroid\QrCode\RoundBlockSizeMode\RoundBlockSizeModeMargin; +use Endroid\QrCode\RoundBlockSizeMode\RoundBlockSizeModeNone; use Endroid\QrCode\Writer\PngWriter; use Endroid\QrCode\Writer\SvgWriter; use Endroid\QrCode\Writer\WriterInterface; @@ -31,6 +34,7 @@ final class QrCodeParams private int $margin, private WriterInterface $writer, private ErrorCorrectionLevelInterface $errorCorrectionLevel, + private RoundBlockSizeModeInterface $roundBlockSizeMode, ) { } @@ -43,6 +47,7 @@ final class QrCodeParams self::resolveMargin($query, $defaults), self::resolveWriter($query, $defaults), self::resolveErrorCorrection($query, $defaults), + self::resolveRoundBlockSize($query, $defaults), ); } @@ -90,6 +95,12 @@ final class QrCodeParams }; } + private static function resolveRoundBlockSize(array $query, QrCodeOptions $defaults): RoundBlockSizeModeInterface + { + $doNotRoundBlockSize = ($query['roundBlockSize'] ?? null) === 'false' || ! $defaults->roundBlockSize(); + return $doNotRoundBlockSize ? new RoundBlockSizeModeNone() : new RoundBlockSizeModeMargin(); + } + private static function normalizeParam(string $param): string { return strtolower(trim($param)); @@ -114,4 +125,9 @@ final class QrCodeParams { return $this->errorCorrectionLevel; } + + public function roundBlockSizeMode(): RoundBlockSizeModeInterface + { + return $this->roundBlockSizeMode; + } } diff --git a/module/Core/src/Action/QrCodeAction.php b/module/Core/src/Action/QrCodeAction.php index f8d2e275..7772a5c8 100644 --- a/module/Core/src/Action/QrCodeAction.php +++ b/module/Core/src/Action/QrCodeAction.php @@ -45,7 +45,8 @@ class QrCodeAction implements MiddlewareInterface ->size($params->size()) ->margin($params->margin()) ->writer($params->writer()) - ->errorCorrectionLevel($params->errorCorrectionLevel()); + ->errorCorrectionLevel($params->errorCorrectionLevel()) + ->roundBlockSizeMode($params->roundBlockSizeMode()); return new QrCodeResponse($qrCodeBuilder->build()); } diff --git a/module/Core/src/Options/QrCodeOptions.php b/module/Core/src/Options/QrCodeOptions.php index 80d6e456..3dfc9a53 100644 --- a/module/Core/src/Options/QrCodeOptions.php +++ b/module/Core/src/Options/QrCodeOptions.php @@ -9,6 +9,7 @@ use Laminas\Stdlib\AbstractOptions; 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; class QrCodeOptions extends AbstractOptions @@ -17,6 +18,7 @@ class QrCodeOptions extends AbstractOptions private int $margin = DEFAULT_QR_CODE_MARGIN; private string $format = DEFAULT_QR_CODE_FORMAT; private string $errorCorrection = DEFAULT_QR_CODE_ERROR_CORRECTION; + private bool $roundBlockSize = DEFAULT_QR_CODE_ROUND_BLOCK_SIZE; public function size(): int { @@ -57,4 +59,14 @@ class QrCodeOptions extends AbstractOptions { $this->errorCorrection = $errorCorrection; } + + public function roundBlockSize(): bool + { + return $this->roundBlockSize; + } + + protected function setRoundBlockSize(bool $roundBlockSize): void + { + $this->roundBlockSize = $roundBlockSize; + } } From bdc89e20564ed5da47f7ae0c7e5cded0f02018b2 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 6 Dec 2021 17:15:19 +0100 Subject: [PATCH 2/4] Fixed execution on non-swoole contexts --- config/config.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/config/config.php b/config/config.php index 887aa365..ccb61cbb 100644 --- a/config/config.php +++ b/config/config.php @@ -13,11 +13,17 @@ use Mezzio\Swoole; use function class_exists; use function Shlinkio\Shlink\Common\env; +use const PHP_SAPI; + +$isCli = PHP_SAPI === 'cli'; + return (new ConfigAggregator\ConfigAggregator([ Mezzio\ConfigProvider::class, Mezzio\Router\ConfigProvider::class, Mezzio\Router\FastRouteRouter\ConfigProvider::class, - class_exists(Swoole\ConfigProvider::class) ? Swoole\ConfigProvider::class : new ConfigAggregator\ArrayProvider([]), + $isCli && class_exists(Swoole\ConfigProvider::class) + ? Swoole\ConfigProvider::class + : new ConfigAggregator\ArrayProvider([]), ProblemDetails\ConfigProvider::class, Diactoros\ConfigProvider::class, Common\ConfigProvider::class, From 1a75bd87d82bc1b31bef829db9b3d7f9ffffd648 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 6 Dec 2021 17:35:32 +0100 Subject: [PATCH 3/4] Updated installer with support for QR code block size rounding --- CHANGELOG.md | 1 + composer.json | 2 +- config/autoload/installer.global.php | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9047623..b7f49abe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this ### Added * [#1204](https://github.com/shlinkio/shlink/issues/1204) Added support for `openswoole` and migrated official docker image to `openswoole`. * [#1242](https://github.com/shlinkio/shlink/issues/1242) Added support to import urls and visits from YOURLS. +* [#1235](https://github.com/shlinkio/shlink/issues/1235) Added support to disable rounding QR codes block sizing via config option, env var or query param. ### Changed * [#1218](https://github.com/shlinkio/shlink/issues/1218) Updated to symfony/mercure 0.6. diff --git a/composer.json b/composer.json index b1badf18..09aaf729 100644 --- a/composer.json +++ b/composer.json @@ -51,7 +51,7 @@ "shlinkio/shlink-config": "^1.4", "shlinkio/shlink-event-dispatcher": "dev-main#3925299 as 2.3", "shlinkio/shlink-importer": "dev-main#d099072 as 2.5", - "shlinkio/shlink-installer": "dev-develop#e3f2e64 as 6.3", + "shlinkio/shlink-installer": "dev-develop#7dd00fb as 6.3", "shlinkio/shlink-ip-geolocation": "^2.2", "symfony/console": "^5.4", "symfony/filesystem": "^5.4", diff --git a/config/autoload/installer.global.php b/config/autoload/installer.global.php index 24461e70..def478af 100644 --- a/config/autoload/installer.global.php +++ b/config/autoload/installer.global.php @@ -56,6 +56,7 @@ return [ Option\QrCode\DefaultMarginConfigOption::class, Option\QrCode\DefaultFormatConfigOption::class, Option\QrCode\DefaultErrorCorrectionConfigOption::class, + Option\QrCode\DefaultRoundBlockSizeConfigOption::class, ], 'installation_commands' => [ From 813ae71aad2e76fce27e5cecdf0915fa95f82909 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 6 Dec 2021 18:06:29 +0100 Subject: [PATCH 4/4] Added test checking if auto margin is added to QR codes --- module/Core/src/Action/Model/QrCodeParams.php | 4 +- module/Core/test/Action/QrCodeActionTest.php | 44 ++++++++++++++++++- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/module/Core/src/Action/Model/QrCodeParams.php b/module/Core/src/Action/Model/QrCodeParams.php index 47fb82d4..03643e4c 100644 --- a/module/Core/src/Action/Model/QrCodeParams.php +++ b/module/Core/src/Action/Model/QrCodeParams.php @@ -97,7 +97,9 @@ final class QrCodeParams private static function resolveRoundBlockSize(array $query, QrCodeOptions $defaults): RoundBlockSizeModeInterface { - $doNotRoundBlockSize = ($query['roundBlockSize'] ?? null) === 'false' || ! $defaults->roundBlockSize(); + $doNotRoundBlockSize = isset($query['roundBlockSize']) + ? $query['roundBlockSize'] === 'false' + : ! $defaults->roundBlockSize(); return $doNotRoundBlockSize ? new RoundBlockSizeModeNone() : new RoundBlockSizeModeMargin(); } diff --git a/module/Core/test/Action/QrCodeActionTest.php b/module/Core/test/Action/QrCodeActionTest.php index 1fdc35ef..664a51a2 100644 --- a/module/Core/test/Action/QrCodeActionTest.php +++ b/module/Core/test/Action/QrCodeActionTest.php @@ -25,11 +25,16 @@ use Shlinkio\Shlink\Core\Service\ShortUrl\ShortUrlResolverInterface; use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifier; use function getimagesizefromstring; +use function imagecolorat; +use function imagecreatefromstring; class QrCodeActionTest extends TestCase { use ProphecyTrait; + private const WHITE = 0xFFFFFF; + private const BLACK = 0x0; + private QrCodeAction $action; private ObjectProphecy $urlResolver; private QrCodeOptions $options; @@ -135,7 +140,7 @@ class QrCodeActionTest extends TestCase $delegate = $this->prophesize(RequestHandlerInterface::class); $resp = $this->action->process($req->withAttribute('shortCode', $code), $delegate->reveal()); - [$size] = getimagesizefromstring((string) $resp->getBody()); + [$size] = getimagesizefromstring($resp->getBody()->__toString()); self::assertEquals($expectedSize, $size); } @@ -199,4 +204,41 @@ class QrCodeActionTest extends TestCase 538, ]; } + + /** + * @test + * @dataProvider provideRoundBlockSize + */ + public function imageCanRemoveExtraMarginWhenBlockRoundIsDisabled( + array $defaults, + ?string $roundBlockSize, + int $expectedColor, + ): void { + $this->options->setFromArray($defaults); + $code = 'abc123'; + $req = ServerRequestFactory::fromGlobals() + ->withQueryParams(['size' => 250, 'roundBlockSize' => $roundBlockSize]) + ->withAttribute('shortCode', $code); + + $this->urlResolver->resolveEnabledShortUrl(new ShortUrlIdentifier($code, ''))->willReturn( + ShortUrl::withLongUrl('https://shlink.io'), + ); + $delegate = $this->prophesize(RequestHandlerInterface::class); + + $resp = $this->action->process($req, $delegate->reveal()); + $image = imagecreatefromstring($resp->getBody()->__toString()); + $color = imagecolorat($image, 1, 1); + + self::assertEquals($color, $expectedColor); + } + + public function provideRoundBlockSize(): iterable + { + yield 'no round block param' => [[], null, self::WHITE]; + yield 'no round block param, but disabled by default' => [['round_block_size' => false], null, self::BLACK]; + yield 'round block: "true"' => [[], 'true', self::WHITE]; + yield 'round block: "true", but disabled by default' => [['round_block_size' => false], 'true', self::WHITE]; + yield 'round block: "false"' => [[], 'false', self::BLACK]; + yield 'round block: "false", but enabled by default' => [['round_block_size' => true], 'false', self::BLACK]; + } }