diff --git a/src/Symfony/Component/Validator/CHANGELOG.md b/src/Symfony/Component/Validator/CHANGELOG.md index 7ab8051c1a92b..d874ed07deece 100644 --- a/src/Symfony/Component/Validator/CHANGELOG.md +++ b/src/Symfony/Component/Validator/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +2.4.0 +----- + + * added `minRatio`, `maxRatio`, `allowSquare`, `allowLandscape`, and `allowPortrait` to Image validator + 2.3.0 ----- diff --git a/src/Symfony/Component/Validator/Constraints/Image.php b/src/Symfony/Component/Validator/Constraints/Image.php index a23106489f354..9fa8725c6da15 100644 --- a/src/Symfony/Component/Validator/Constraints/Image.php +++ b/src/Symfony/Component/Validator/Constraints/Image.php @@ -23,6 +23,11 @@ class Image extends File public $maxWidth = null; public $maxHeight = null; public $minHeight = null; + public $maxRatio = null; + public $minRatio = null; + public $allowSquare = true; + public $allowLandscape = true; + public $allowPortrait = true; public $mimeTypesMessage = 'This file is not a valid image.'; public $sizeNotDetectedMessage = 'The size of the image could not be detected.'; @@ -30,4 +35,9 @@ class Image extends File public $minWidthMessage = 'The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px.'; public $maxHeightMessage = 'The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px.'; public $minHeightMessage = 'The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px.'; + public $maxRatioMessage = 'The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}.'; + public $minRatioMessage = 'The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}.'; + public $allowSquareMessage = 'The image is square ({{ width }}x{{ height }}px). Square images are not allowed.'; + public $allowLandscapeMessage = 'The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed.'; + public $allowPortraitMessage = 'The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed.'; } diff --git a/src/Symfony/Component/Validator/Constraints/ImageValidator.php b/src/Symfony/Component/Validator/Constraints/ImageValidator.php index 79e6bdcddbbb1..76ce8767fcea8 100644 --- a/src/Symfony/Component/Validator/Constraints/ImageValidator.php +++ b/src/Symfony/Component/Validator/Constraints/ImageValidator.php @@ -38,7 +38,9 @@ public function validate($value, Constraint $constraint) } if (null === $constraint->minWidth && null === $constraint->maxWidth - && null === $constraint->minHeight && null === $constraint->maxHeight) { + && null === $constraint->minHeight && null === $constraint->maxHeight + && null === $constraint->minRatio && null === $constraint->maxRatio + && $constraint->allowSquare && $constraint->allowLandscape && $constraint->allowPortrait) { return; } @@ -109,5 +111,55 @@ public function validate($value, Constraint $constraint) )); } } + + $ratio = $width / $height; + + if (null !== $constraint->minRatio) { + if (!is_numeric((string) $constraint->minRatio)) { + throw new ConstraintDefinitionException(sprintf('"%s" is not a valid minimum ratio', $constraint->minRatio)); + } + + if ($ratio < $constraint->minRatio) { + $this->context->addViolation($constraint->minRatioMessage, array( + '{{ ratio }}' => $ratio, + '{{ min_ratio }}' => $constraint->minRatio + )); + } + } + + if (null !== $constraint->maxRatio) { + if (!is_numeric((string) $constraint->maxRatio)) { + throw new ConstraintDefinitionException(sprintf('"%s" is not a valid maximum ratio', $constraint->maxRatio)); + } + + if ($ratio > $constraint->maxRatio) { + $this->context->addViolation($constraint->maxRatioMessage, array( + '{{ ratio }}' => $ratio, + '{{ max_ratio }}' => $constraint->maxRatio + )); + } + } + + if (!$constraint->allowSquare && $width == $height) { + $this->context->addViolation($constraint->allowSquareMessage, array( + '{{ width }}' => $width, + '{{ height }}' => $height + )); + } + + if (!$constraint->allowLandscape && $width > $height) { + $this->context->addViolation($constraint->allowLandscapeMessage, array( + '{{ width }}' => $width, + '{{ height }}' => $height + )); + } + + if (!$constraint->allowPortrait && $width < $height) { + $this->context->addViolation($constraint->allowPortraitMessage, array( + '{{ width }}' => $width, + '{{ height }}' => $height + )); + } + } } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/Fixtures/test_landscape.gif b/src/Symfony/Component/Validator/Tests/Constraints/Fixtures/test_landscape.gif new file mode 100644 index 0000000000000..870123532c3b9 Binary files /dev/null and b/src/Symfony/Component/Validator/Tests/Constraints/Fixtures/test_landscape.gif differ diff --git a/src/Symfony/Component/Validator/Tests/Constraints/Fixtures/test_portrait.gif b/src/Symfony/Component/Validator/Tests/Constraints/Fixtures/test_portrait.gif new file mode 100644 index 0000000000000..cc480ca88ed7f Binary files /dev/null and b/src/Symfony/Component/Validator/Tests/Constraints/Fixtures/test_portrait.gif differ diff --git a/src/Symfony/Component/Validator/Tests/Constraints/ImageValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/ImageValidatorTest.php index 88545016243d1..12cdbf0380544 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/ImageValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/ImageValidatorTest.php @@ -20,6 +20,8 @@ class ImageValidatorTest extends \PHPUnit_Framework_TestCase protected $validator; protected $path; protected $image; + protected $imageLandscape; + protected $imagePortrait; protected function setUp() { @@ -27,6 +29,8 @@ protected function setUp() $this->validator = new ImageValidator(); $this->validator->initialize($this->context); $this->image = __DIR__.'/Fixtures/test.gif'; + $this->imageLandscape = __DIR__.'/Fixtures/test_landscape.gif'; + $this->imagePortrait = __DIR__.'/Fixtures/test_portrait.gif'; } public function testNullIsValid() @@ -223,4 +227,141 @@ public function testInvalidMaxHeight() $this->validator->validate($this->image, $constraint); } + + public function testRatioTooSmall() + { + if (!class_exists('Symfony\Component\HttpFoundation\File\File')) { + $this->markTestSkipped('The "HttpFoundation" component is not available'); + } + + $constraint = new Image(array( + 'minRatio' => 2, + 'minRatioMessage' => 'myMessage', + )); + + $this->context->expects($this->once()) + ->method('addViolation') + ->with('myMessage', array( + '{{ ratio }}' => 1, + '{{ min_ratio }}' => 2, + )); + + $this->validator->validate($this->image, $constraint); + } + + public function testRatioTooBig() + { + if (!class_exists('Symfony\Component\HttpFoundation\File\File')) { + $this->markTestSkipped('The "HttpFoundation" component is not available'); + } + + $constraint = new Image(array( + 'maxRatio' => 0.5, + 'maxRatioMessage' => 'myMessage', + )); + + $this->context->expects($this->once()) + ->method('addViolation') + ->with('myMessage', array( + '{{ ratio }}' => 1, + '{{ max_ratio }}' => 0.5, + )); + + $this->validator->validate($this->image, $constraint); + } + + /** + * @expectedException \Symfony\Component\Validator\Exception\ConstraintDefinitionException + */ + public function testInvalidMinRatio() + { + if (!class_exists('Symfony\Component\HttpFoundation\File\File')) { + $this->markTestSkipped('The "HttpFoundation" component is not available'); + } + + $constraint = new Image(array( + 'minRatio' => '1abc', + )); + + $this->validator->validate($this->image, $constraint); + } + + /** + * @expectedException \Symfony\Component\Validator\Exception\ConstraintDefinitionException + */ + public function testInvalidMaxRatio() + { + if (!class_exists('Symfony\Component\HttpFoundation\File\File')) { + $this->markTestSkipped('The "HttpFoundation" component is not available'); + } + + $constraint = new Image(array( + 'maxRatio' => '1abc', + )); + + $this->validator->validate($this->image, $constraint); + } + + public function testSquareNotAllowed() + { + if (!class_exists('Symfony\Component\HttpFoundation\File\File')) { + $this->markTestSkipped('The "HttpFoundation" component is not available'); + } + + $constraint = new Image(array( + 'allowSquare' => false, + 'allowSquareMessage' => 'myMessage', + )); + + $this->context->expects($this->once()) + ->method('addViolation') + ->with('myMessage', array( + '{{ width }}' => 2, + '{{ height }}' => 2, + )); + + $this->validator->validate($this->image, $constraint); + } + + public function testLandscapeNotAllowed() + { + if (!class_exists('Symfony\Component\HttpFoundation\File\File')) { + $this->markTestSkipped('The "HttpFoundation" component is not available'); + } + + $constraint = new Image(array( + 'allowLandscape' => false, + 'allowLandscapeMessage' => 'myMessage', + )); + + $this->context->expects($this->once()) + ->method('addViolation') + ->with('myMessage', array( + '{{ width }}' => 2, + '{{ height }}' => 1, + )); + + $this->validator->validate($this->imageLandscape, $constraint); + } + + public function testPortraitNotAllowed() + { + if (!class_exists('Symfony\Component\HttpFoundation\File\File')) { + $this->markTestSkipped('The "HttpFoundation" component is not available'); + } + + $constraint = new Image(array( + 'allowPortrait' => false, + 'allowPortraitMessage' => 'myMessage', + )); + + $this->context->expects($this->once()) + ->method('addViolation') + ->with('myMessage', array( + '{{ width }}' => 1, + '{{ height }}' => 2, + )); + + $this->validator->validate($this->imagePortrait, $constraint); + } }