Merge pull request #1250 from acelaya-forks/feature/qr-round-block-size

Feature/qr round block size
This commit is contained in:
Alejandro Celaya 2021-12-06 18:19:53 +01:00 committed by GitHub
commit d8735e6a91
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 89 additions and 5 deletions

View file

@ -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.

View file

@ -47,11 +47,11 @@
"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",
"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",

View file

@ -56,6 +56,7 @@ return [
Option\QrCode\DefaultMarginConfigOption::class,
Option\QrCode\DefaultFormatConfigOption::class,
Option\QrCode\DefaultErrorCorrectionConfigOption::class,
Option\QrCode\DefaultRoundBlockSizeConfigOption::class,
],
'installation_commands' => [

View file

@ -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),
],
];

View file

@ -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,

View file

@ -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;

View file

@ -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,14 @@ final class QrCodeParams
};
}
private static function resolveRoundBlockSize(array $query, QrCodeOptions $defaults): RoundBlockSizeModeInterface
{
$doNotRoundBlockSize = isset($query['roundBlockSize'])
? $query['roundBlockSize'] === 'false'
: ! $defaults->roundBlockSize();
return $doNotRoundBlockSize ? new RoundBlockSizeModeNone() : new RoundBlockSizeModeMargin();
}
private static function normalizeParam(string $param): string
{
return strtolower(trim($param));
@ -114,4 +127,9 @@ final class QrCodeParams
{
return $this->errorCorrectionLevel;
}
public function roundBlockSizeMode(): RoundBlockSizeModeInterface
{
return $this->roundBlockSizeMode;
}
}

View file

@ -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());
}

View file

@ -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;
}
}

View file

@ -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];
}
}