mirror of
https://github.com/chillerlan/php-qrcode.git
synced 2026-08-30 12:07:32 +00:00
:octocat: QROutputInterface::moduleValueIsValid() improved checks & tests
This commit is contained in:
@@ -32,7 +32,7 @@ class QREps extends QROutputAbstract{
|
||||
}
|
||||
|
||||
// check the first values of the array
|
||||
foreach($value as $i => $val){
|
||||
foreach(array_values($value) as $i => $val){
|
||||
|
||||
if($i > 3){
|
||||
break;
|
||||
@@ -61,8 +61,8 @@ class QREps extends QROutputAbstract{
|
||||
break;
|
||||
}
|
||||
|
||||
// clamp value and convert from 0-255 to 0-1 RGB/CMYK range
|
||||
$values[] = round((max(0, min(255, $val)) / 255), 6);
|
||||
// clamp value and convert from int 0-255 to float 0-1 RGB/CMYK range
|
||||
$values[] = round((max(0, min(255, intval($val))) / 255), 6);
|
||||
}
|
||||
|
||||
return $values;
|
||||
|
||||
+20
-8
@@ -15,7 +15,7 @@ use chillerlan\QRCode\Data\QRMatrix;
|
||||
use chillerlan\Settings\SettingsContainerInterface;
|
||||
use FPDF;
|
||||
|
||||
use function class_exists, count, is_array, is_numeric, max, min;
|
||||
use function array_values, class_exists, count, intval, is_array, is_numeric, max, min;
|
||||
|
||||
/**
|
||||
* QRFpdf output module (requires fpdf)
|
||||
@@ -54,27 +54,39 @@ class QRFpdf extends QROutputAbstract{
|
||||
}
|
||||
|
||||
// check the first 3 values of the array
|
||||
for($i = 0; $i < 3; $i++){
|
||||
if(!is_numeric($value[$i])){
|
||||
foreach(array_values($value) as $i => $val){
|
||||
|
||||
if($i > 2){
|
||||
break;
|
||||
}
|
||||
|
||||
if(!is_numeric($val)){
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $value
|
||||
*
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function getModuleValue($value):array{
|
||||
$v = [];
|
||||
$values = [];
|
||||
|
||||
for($i = 0; $i < 3; $i++){
|
||||
// clamp value
|
||||
$v[] = (int)max(0, min(255, $value[$i]));
|
||||
foreach(array_values($value) as $i => $val){
|
||||
|
||||
if($i > 2){
|
||||
break;
|
||||
}
|
||||
|
||||
$values[] = max(0, min(255, intval($val)));
|
||||
}
|
||||
|
||||
return $v;
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+22
-10
@@ -15,9 +15,9 @@ namespace chillerlan\QRCode\Output;
|
||||
use chillerlan\QRCode\Data\QRMatrix;
|
||||
use chillerlan\Settings\SettingsContainerInterface;
|
||||
use ErrorException, Throwable;
|
||||
use function count, extension_loaded, imagecolorallocate, imagecolortransparent, imagecreatetruecolor,
|
||||
imagedestroy, imagefilledellipse, imagefilledrectangle, imagegif, imagejpeg, imagepng, imagescale, is_array, is_numeric,
|
||||
max, min, ob_end_clean, ob_get_contents, ob_start, restore_error_handler, set_error_handler;
|
||||
use function array_values, count, extension_loaded, imagecolorallocate, imagecolortransparent, imagecreatetruecolor,
|
||||
imagedestroy, imagefilledellipse, imagefilledrectangle, imagegif, imagejpeg, imagepng, imagescale, intval,
|
||||
is_array, is_numeric, max, min, ob_end_clean, ob_get_contents, ob_start, restore_error_handler, set_error_handler;
|
||||
|
||||
/**
|
||||
* Converts the matrix into GD images, raw or base64 output (requires ext-gd)
|
||||
@@ -89,29 +89,41 @@ class QRGdImage extends QROutputAbstract{
|
||||
}
|
||||
|
||||
// check the first 3 values of the array
|
||||
for($i = 0; $i < 3; $i++){
|
||||
if(!is_numeric($value[$i])){
|
||||
foreach(array_values($value) as $i => $val){
|
||||
|
||||
if($i > 2){
|
||||
break;
|
||||
}
|
||||
|
||||
if(!is_numeric($val)){
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $value
|
||||
*
|
||||
* @inheritDoc
|
||||
* @throws \chillerlan\QRCode\Output\QRCodeOutputException
|
||||
*/
|
||||
protected function getModuleValue($value):int{
|
||||
$v = [];
|
||||
$values = [];
|
||||
|
||||
for($i = 0; $i < 3; $i++){
|
||||
// clamp value
|
||||
$v[] = (int)max(0, min(255, $value[$i]));
|
||||
foreach(array_values($value) as $i => $val){
|
||||
|
||||
if($i > 2){
|
||||
break;
|
||||
}
|
||||
|
||||
$values[] = max(0, min(255, intval($val)));
|
||||
}
|
||||
|
||||
/** @phan-suppress-next-line PhanParamTooFewInternalUnpack */
|
||||
$color = imagecolorallocate($this->image, ...$v);
|
||||
$color = imagecolorallocate($this->image, ...$values);
|
||||
|
||||
if($color === false){
|
||||
throw new QRCodeOutputException('could not set color: imagecolorallocate() error');
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace chillerlan\QRCode\Output;
|
||||
use chillerlan\QRCode\Data\QRMatrix;
|
||||
use chillerlan\Settings\SettingsContainerInterface;
|
||||
use finfo, Imagick, ImagickDraw, ImagickPixel;
|
||||
use function extension_loaded, is_string;
|
||||
use function extension_loaded, in_array, is_string, preg_match, strlen;
|
||||
use const FILEINFO_MIME_TYPE;
|
||||
|
||||
/**
|
||||
@@ -60,16 +60,44 @@ class QRImagick extends QROutputAbstract{
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo: check/validate possible values
|
||||
* note: we're not necessarily validating the several values, just checking the general syntax
|
||||
*
|
||||
* @see https://www.php.net/manual/imagickpixel.construct.php
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function moduleValueIsValid($value):bool{
|
||||
return is_string($value);
|
||||
|
||||
if(!is_string($value)){
|
||||
return false;
|
||||
}
|
||||
|
||||
$value = trim($value);
|
||||
|
||||
// hex notation
|
||||
// #rgb(a)
|
||||
// #rrggbb(aa)
|
||||
// #rrrrggggbbbb(aaaa)
|
||||
// ...
|
||||
if(preg_match('/^#[a-f]+$/i', $value) && in_array((strlen($value) - 1), [3, 4, 6, 8, 9, 12, 16, 24, 32], true)){
|
||||
return true;
|
||||
}
|
||||
|
||||
// css (-like) func(...values)
|
||||
if(preg_match('#^(graya?|hs(b|la?)|rgba?)\([\d .,%]+\)$#i', $value)){
|
||||
return true;
|
||||
}
|
||||
|
||||
// predefined css color
|
||||
if(preg_match('/^[a-z]+$/i', $value)){
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @throws \ImagickPixelException
|
||||
*/
|
||||
protected function getModuleValue($value):ImagickPixel{
|
||||
return new ImagickPixel($value);
|
||||
|
||||
+30
-2
@@ -10,7 +10,7 @@
|
||||
|
||||
namespace chillerlan\QRCode\Output;
|
||||
|
||||
use function is_string, strip_tags, trim;
|
||||
use function is_string, preg_match, strip_tags, trim;
|
||||
|
||||
/**
|
||||
* Abstract for markup types: HTML, SVG, ... XML anyone?
|
||||
@@ -18,10 +18,38 @@ use function is_string, strip_tags, trim;
|
||||
abstract class QRMarkup extends QROutputAbstract{
|
||||
|
||||
/**
|
||||
* note: we're not necessarily validating the several values, just checking the general syntax
|
||||
* note: css4 colors are not included
|
||||
*
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/color_value
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function moduleValueIsValid($value):bool{
|
||||
return is_string($value);
|
||||
|
||||
if(!is_string($value)){
|
||||
return false;
|
||||
}
|
||||
|
||||
$value = trim(strip_tags($value), " '\"\r\n\t");
|
||||
|
||||
// hex notation
|
||||
// #rgb(a)
|
||||
// #rrggbb(aa)
|
||||
if(preg_match('/^#([\da-f]{3}){1,2}$|^#([\da-f]{4}){1,2}$/i', $value)){
|
||||
return true;
|
||||
}
|
||||
|
||||
// css: hsla/rgba(...values)
|
||||
if(preg_match('#^(hsla?|rgba?)\([\d .,%/]+\)$#i', $value)){
|
||||
return true;
|
||||
}
|
||||
|
||||
// predefined css color
|
||||
if(preg_match('/^[a-z]+$/i', $value)){
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace chillerlan\QRCode\Output;
|
||||
|
||||
use chillerlan\QRCode\Data\QRMatrix;
|
||||
use function array_chunk, implode, sprintf;
|
||||
use function array_chunk, implode, is_string, preg_match, sprintf, trim;
|
||||
|
||||
/**
|
||||
* SVG output
|
||||
@@ -23,6 +23,27 @@ use function array_chunk, implode, sprintf;
|
||||
*/
|
||||
class QRMarkupSVG extends QRMarkup{
|
||||
|
||||
/**
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function moduleValueIsValid($value):bool{
|
||||
|
||||
if(!is_string($value)){
|
||||
return false;
|
||||
}
|
||||
|
||||
$value = trim($value);
|
||||
|
||||
// url(...)
|
||||
if(preg_match('~^url\([-/#a-z\d]+\)$~i', $value)){
|
||||
return true;
|
||||
}
|
||||
|
||||
// otherwise check for standard css notation
|
||||
return parent::moduleValueIsValid($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
@@ -89,7 +89,7 @@ interface QROutputInterface{
|
||||
/**
|
||||
* Determines whether the given value is valid
|
||||
*
|
||||
* @param mixed|null $value
|
||||
* @param mixed $value
|
||||
*/
|
||||
public static function moduleValueIsValid($value):bool;
|
||||
|
||||
|
||||
@@ -19,6 +19,18 @@ class QREpsTest extends QROutputTestAbstract{
|
||||
protected string $FQN = QREps::class;
|
||||
protected string $type = QROutputInterface::EPS;
|
||||
|
||||
public static function moduleValueProvider():array{
|
||||
return [
|
||||
'valid: 3 int' => [[123, 123, 123], true],
|
||||
'valid: 4 int' => [[123, 123, 123, 123], true],
|
||||
'valid: w/invalid extra element' => [[123, 123, 123, 123, 'abc'], true],
|
||||
'valid: numeric string' => [['123', '123', '123'], true],
|
||||
'invalid: wrong type' => ['foo', false],
|
||||
'invalid: array too short' => [[1, 2], false],
|
||||
'invalid: contains non-number' => [[1, 'b', 3], false],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
@@ -36,6 +36,17 @@ final class QRFpdfTest extends QROutputTestAbstract{
|
||||
parent::setUp();
|
||||
}
|
||||
|
||||
public static function moduleValueProvider():array{
|
||||
return [
|
||||
'valid: int' => [[123, 123, 123], true],
|
||||
'valid: w/invalid extra element' => [[123, 123, 123, 'abc'], true],
|
||||
'valid: numeric string' => [['123', '123', '123'], true],
|
||||
'invalid: wrong type' => ['foo', false],
|
||||
'invalid: array too short' => [[1, 2], false],
|
||||
'invalid: contains non-number' => [[1, 'b', 3], false],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
@@ -33,6 +33,17 @@ abstract class QRGdImageTestAbstract extends QROutputTestAbstract{
|
||||
parent::setUp();
|
||||
}
|
||||
|
||||
public static function moduleValueProvider():array{
|
||||
return [
|
||||
'valid: int' => [[123, 123, 123], true],
|
||||
'valid: w/invalid extra element' => [[123, 123, 123, 'abc'], true],
|
||||
'valid: numeric string' => [['123', '123', '123'], true],
|
||||
'invalid: wrong type' => ['foo', false],
|
||||
'invalid: array too short' => [[1, 2], false],
|
||||
'invalid: contains non-number' => [[1, 'b', 3], false],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
@@ -37,6 +37,30 @@ final class QRImagickTest extends QROutputTestAbstract{
|
||||
parent::setUp();
|
||||
}
|
||||
|
||||
public static function moduleValueProvider():array{
|
||||
return [
|
||||
'invalid: wrong type' => [[], false],
|
||||
'valid: hex color (3)' => ['#abc', true],
|
||||
'valid: hex color (4)' => ['#abcd', true],
|
||||
'valid: hex color (6)' => ['#aabbcc', true],
|
||||
'valid: hex color (8)' => ['#aabbccdd', true],
|
||||
'valid: hex color (32)' => ['#aaaaaaaabbbbbbbbccccccccdddddddd', true],
|
||||
'invalid: hex color (non-hex)' => ['#aabbcxyz', false],
|
||||
'invalid: hex color (too short)' => ['#aa', false],
|
||||
'invalid: hex color (too long)' => ['#aaaaaaaabbbbbbbbccccccccdddddddd00', false],
|
||||
'invalid: hex color (5)' => ['#aabbc', false],
|
||||
'invalid: hex color (7)' => ['#aabbccd', false],
|
||||
'valid: rgb(...%)' => ['rgb(100.0%, 0.0%, 0.0%)', true],
|
||||
'valid: rgba(...)' => [' rgba(255, 0, 0, 1.0) ', true],
|
||||
'valid: hsb(...)' => ['hsb(33.3333%, 100%, 75%)', true],
|
||||
'valid: hsla(...)' => ['hsla(120, 255, 191.25, 1.0)', true],
|
||||
'invalid: rgba(non-numeric)' => ['rgba(255, 0, whatever, 0, 1.0)', false],
|
||||
'invalid: rgba(extra-char)' => ['rgba(255, 0, 0, 1.0);', false],
|
||||
'valid: csscolor' => ['purple', true],
|
||||
'invalid: c5s c0lor' => ['c5sc0lor', false],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
@@ -20,4 +20,30 @@ final class QRMarkupSVGTest extends QRMarkupTestAbstract{
|
||||
protected string $FQN = QRMarkupSVG::class;
|
||||
protected string $type = QROutputInterface::MARKUP_SVG;
|
||||
|
||||
public static function moduleValueProvider():array{
|
||||
return [
|
||||
// css colors from parent
|
||||
'valid: hex color (3)' => ['#abc', true],
|
||||
'valid: hex color (4)' => ['#abcd', true],
|
||||
'valid: hex color (6)' => ['#aabbcc', true],
|
||||
'valid: hex color (8)' => ['#aabbccdd', true],
|
||||
'invalid: hex color (non-hex)' => ['#aabbcxyz', false],
|
||||
'invalid: hex color (too short)' => ['#aa', false],
|
||||
'invalid: hex color (5)' => ['#aabbc', false],
|
||||
'invalid: hex color (7)' => ['#aabbccd', false],
|
||||
'valid: rgb(...%)' => ['rgb(100.0%, 0.0%, 0.0%)', true],
|
||||
'valid: rgba(...)' => [' rgba(255, 0, 0, 1.0) ', true],
|
||||
'valid: hsl(...)' => ['hsl(120, 60%, 50%)', true],
|
||||
'valid: hsla(...)' => ['hsla(120, 255, 191.25, 1.0)', true],
|
||||
'invalid: rgba(non-numeric)' => ['rgba(255, 0, whatever, 0, 1.0)', false],
|
||||
'invalid: rgba(extra-char)' => ['rgba(255, 0, 0, 1.0);', false],
|
||||
'valid: csscolor' => ['purple', true],
|
||||
'invalid: c5sc0lor' => ['c5sc0lor', false],
|
||||
|
||||
// SVG
|
||||
'valid: url(#id)' => ['url(#fillGradient)', true],
|
||||
'invalid: url(link)' => ['url(https://example.com/noop)', false],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,6 +17,28 @@ use chillerlan\QRCode\Data\QRMatrix;
|
||||
*/
|
||||
abstract class QRMarkupTestAbstract extends QROutputTestAbstract{
|
||||
|
||||
public static function moduleValueProvider():array{
|
||||
return [
|
||||
'invalid: wrong type' => [[], false],
|
||||
'valid: hex color (3)' => ['#abc', true],
|
||||
'valid: hex color (4)' => ['#abcd', true],
|
||||
'valid: hex color (6)' => ['#aabbcc', true],
|
||||
'valid: hex color (8)' => ['#aabbccdd', true],
|
||||
'invalid: hex color (non-hex)' => ['#aabbcxyz', false],
|
||||
'invalid: hex color (too short)' => ['#aa', false],
|
||||
'invalid: hex color (5)' => ['#aabbc', false],
|
||||
'invalid: hex color (7)' => ['#aabbccd', false],
|
||||
'valid: rgb(...%)' => ['rgb(100.0%, 0.0%, 0.0%)', true],
|
||||
'valid: rgba(...)' => [' rgba(255, 0, 0, 1.0) ', true],
|
||||
'valid: hsl(...)' => ['hsl(120, 60%, 50%)', true],
|
||||
'valid: hsla(...)' => ['hsla(120, 255, 191.25, 1.0)', true],
|
||||
'invalid: rgba(non-numeric)' => ['rgba(255, 0, whatever, 0, 1.0)', false],
|
||||
'invalid: rgba(extra-char)' => ['rgba(255, 0, 0, 1.0);', false],
|
||||
'valid: csscolor' => ['purple', true],
|
||||
'invalid: c5sc0lor' => ['c5sc0lor', false],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
@@ -67,15 +67,28 @@ abstract class QROutputTestAbstract extends TestCase{
|
||||
$this->outputInterface->dump('/foo/bar.test');
|
||||
}
|
||||
|
||||
abstract public static function moduleValueProvider():array;
|
||||
|
||||
/**
|
||||
* covers the module values settings
|
||||
* @param mixed $value
|
||||
* @param bool $expected
|
||||
*
|
||||
* @dataProvider moduleValueProvider
|
||||
*/
|
||||
abstract public function testSetModuleValues():void;
|
||||
public function testValidateModuleValues($value, bool $expected):void{
|
||||
/** @noinspection PhpUndefinedMethodInspection */
|
||||
$this::assertSame($expected, $this->FQN::moduleValueIsValid($value));
|
||||
}
|
||||
|
||||
/*
|
||||
* additional, non-essential, potentially inaccurate coverage tests
|
||||
*/
|
||||
|
||||
/**
|
||||
* covers the module values settings
|
||||
*/
|
||||
abstract public function testSetModuleValues():void;
|
||||
|
||||
/**
|
||||
* coverage of the built-in output modules
|
||||
*/
|
||||
|
||||
@@ -19,4 +19,13 @@ abstract class QRStringTestAbstract extends QROutputTestAbstract{
|
||||
|
||||
protected string $FQN = QRString::class;
|
||||
|
||||
public static function moduleValueProvider():array{
|
||||
return [
|
||||
'invalid: wrong type' => [[], false],
|
||||
'valid: string' => ['abc', true],
|
||||
'valid: zero length string' => ['', true],
|
||||
'valid: empty string' => [' ', true],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user