From 1444593bbdbf178bd4d388ab961ac3025c563f17 Mon Sep 17 00:00:00 2001 From: Kevin Papst Date: Wed, 28 Oct 2020 20:01:08 +0100 Subject: [PATCH] validate color (#2072) --- src/Entity/ColorTrait.php | 4 +- src/Form/EntityFormTrait.php | 7 +- src/Form/Type/ColorPickerType.php | 3 +- src/Utils/Color.php | 10 +- src/Validator/Constraints/HexColor.php | 27 ++++++ .../Constraints/HexColorValidator.php | 40 ++++++++ tests/Utils/ColorTest.php | 15 ++- .../Constraints/HexColorValidatorTest.php | 97 +++++++++++++++++++ 8 files changed, 189 insertions(+), 14 deletions(-) create mode 100644 src/Validator/Constraints/HexColor.php create mode 100644 src/Validator/Constraints/HexColorValidator.php create mode 100644 tests/Validator/Constraints/HexColorValidatorTest.php diff --git a/src/Entity/ColorTrait.php b/src/Entity/ColorTrait.php index 1bf66d4c..0d39361a 100644 --- a/src/Entity/ColorTrait.php +++ b/src/Entity/ColorTrait.php @@ -11,9 +11,9 @@ namespace App\Entity; use App\Constants; use App\Export\Annotation as Exporter; +use App\Validator\Constraints as Constraints; use Doctrine\ORM\Mapping as ORM; use JMS\Serializer\Annotation as Serializer; -use Symfony\Component\Validator\Constraints as Assert; trait ColorTrait { @@ -28,7 +28,7 @@ trait ColorTrait * @Exporter\Expose(label="label.color") * * @ORM\Column(name="color", type="string", length=7, nullable=true) - * @Assert\Length(min=4, max=7, allowEmptyString=true) + * @Constraints\HexColor() */ private $color = null; diff --git a/src/Form/EntityFormTrait.php b/src/Form/EntityFormTrait.php index 0b9860a0..2b0a5a74 100644 --- a/src/Form/EntityFormTrait.php +++ b/src/Form/EntityFormTrait.php @@ -21,9 +21,10 @@ trait EntityFormTrait { public function addCommonFields(FormBuilderInterface $builder, array $options): void { - $currency = $options['currency']; $builder - ->add('color', ColorPickerType::class) + ->add('color', ColorPickerType::class, [ + 'required' => false, + ]) ; if ($options['include_budget']) { @@ -32,7 +33,7 @@ trait EntityFormTrait 'empty_data' => '0.00', 'label' => 'label.budget', 'required' => false, - 'currency' => $currency, + 'currency' => $options['currency'], ]) ->add('timeBudget', DurationType::class, [ 'empty_data' => 0, diff --git a/src/Form/Type/ColorPickerType.php b/src/Form/Type/ColorPickerType.php index ecfe0de8..23ab5c5d 100644 --- a/src/Form/Type/ColorPickerType.php +++ b/src/Form/Type/ColorPickerType.php @@ -36,9 +36,10 @@ class ColorPickerType extends AbstractType implements DataTransformerInterface $resolver->setDefaults([ 'documentation' => [ 'type' => 'string', - 'description' => sprintf('The color code as hex (default: %s)', self::DEFAULT_COLOR), + 'description' => sprintf('The hexadecimal color code (default: %s)', self::DEFAULT_COLOR), ], 'label' => 'label.color', + 'empty_data' => null, ]); } diff --git a/src/Utils/Color.php b/src/Utils/Color.php index 5e6a86c4..c27b3dfb 100644 --- a/src/Utils/Color.php +++ b/src/Utils/Color.php @@ -78,14 +78,18 @@ final class Color public function getFontContrastColor(string $color): string { - if ($color[0] !== '#') { - throw new \InvalidArgumentException('Invalid color code given, only #hexadecimal is supported.'); + if (empty($color) || $color[0] !== '#') { + // do not throw exception on invalid colors, as they were not validated in the past + $color = Constants::DEFAULT_COLOR; } $color = substr($color, 1); + $length = \strlen($color); - if (\strlen($color) === 3) { + if ($length === 3) { $color = $color[0] . $color[0] . $color[1] . $color[1] . $color[2] . $color[2]; + } elseif ($length !== 6) { + $color = substr(Constants::DEFAULT_COLOR, 1); } $r = hexdec(substr($color, 0, 2)); diff --git a/src/Validator/Constraints/HexColor.php b/src/Validator/Constraints/HexColor.php new file mode 100644 index 00000000..6936ec68 --- /dev/null +++ b/src/Validator/Constraints/HexColor.php @@ -0,0 +1,27 @@ + 'HEX_COLOR_ERROR', + ]; + + public $message = 'The given value is not a valid hexadecimal color.'; +} diff --git a/src/Validator/Constraints/HexColorValidator.php b/src/Validator/Constraints/HexColorValidator.php new file mode 100644 index 00000000..ad63099d --- /dev/null +++ b/src/Validator/Constraints/HexColorValidator.php @@ -0,0 +1,40 @@ +context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($color)) + ->setCode(HexColor::HEX_COLOR_ERROR) + ->addViolation(); + } + } +} diff --git a/tests/Utils/ColorTest.php b/tests/Utils/ColorTest.php index b74a1e54..c1dd049d 100644 --- a/tests/Utils/ColorTest.php +++ b/tests/Utils/ColorTest.php @@ -100,12 +100,17 @@ class ColorTest extends TestCase $this->assertEquals('#000000', $sut->getFontContrastColor('#ffffff')); } - public function testGetFontContrastColorThrowsExceptionOnNonHexadecimalColor() + public function testGetFontContrastColorReturnsContrastForDefaultColorOnInvalidColor() { - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('Invalid color code given, only #hexadecimal is supported.'); - $sut = new Color(); - $sut->getFontContrastColor('000000'); + $this->assertEquals('#000000', $sut->getFontContrastColor('')); + $this->assertEquals('#000000', $sut->getFontContrastColor('000000')); + $this->assertEquals('#000000', $sut->getFontContrastColor(Constants::DEFAULT_COLOR)); + $this->assertEquals('#000000', $sut->getFontContrastColor('#6')); + $this->assertEquals('#000000', $sut->getFontContrastColor('#66')); + $this->assertEquals('#000000', $sut->getFontContrastColor('#6666')); + $this->assertEquals('#000000', $sut->getFontContrastColor('#cccc')); + $this->assertEquals('#000000', $sut->getFontContrastColor('#ccccc')); + $this->assertEquals('#000000', $sut->getFontContrastColor('#ccccccc')); } } diff --git a/tests/Validator/Constraints/HexColorValidatorTest.php b/tests/Validator/Constraints/HexColorValidatorTest.php new file mode 100644 index 00000000..c5335e8c --- /dev/null +++ b/tests/Validator/Constraints/HexColorValidatorTest.php @@ -0,0 +1,97 @@ +expectException(UnexpectedTypeException::class); + + $this->validator->validate('#000', new NotBlank()); + } + + /** + * @dataProvider getValidColors + * @param string $color + */ + public function testConstraintWithValidColor($color) + { + $constraint = new HexColor(); + $this->validator->validate($color, $constraint); + $this->assertNoViolation(); + } + + public function getInvalidColors() + { + yield ['string']; + yield ['000']; + yield ['aaa']; + yield ['000000']; + yield ['fff000']; + yield ['000aaa']; + yield ['fffaaa']; + yield ['#f']; + yield ['#ff']; + yield ['#ffdd']; + yield ['#ffddd']; + yield ['#ffddddd']; + yield [new \stdClass(), 'object']; + yield [[], 'array']; + } + + /** + * @dataProvider getInvalidColors + * @param mixed $color + */ + public function testValidationError($color, $parameterType = null) + { + $constraint = new HexColor(); + + $this->validator->validate($color, $constraint); + + if ($parameterType !== null) { + $expectedFormat = $parameterType; + } else { + $expectedFormat = \is_string($color) ? '"' . $color . '"' : $color; + } + + $this->buildViolation('The given value is not a valid hexadecimal color.') + ->setParameter('{{ value }}', $expectedFormat) + ->setCode(HexColor::HEX_COLOR_ERROR) + ->assertRaised(); + } +}