diff --git a/docs/index.rst b/docs/index.rst index 371b4c2c..48daa706 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -15,6 +15,7 @@ Contents: Installation Examples Configuration options + Validation Upload Behavior Interfaces diff --git a/docs/validation.rst b/docs/validation.rst new file mode 100644 index 00000000..3f9f9659 --- /dev/null +++ b/docs/validation.rst @@ -0,0 +1,251 @@ +Validation +---------- + +By default, no validation rules are loaded or attached to the table. You must +explicitly load the validation provider(s) and attach each rule if needed. + +Installation +^^^^^^^^^^^^ + +This plugin allows you to only load the validation rules that cover you needs. +At this point there are 3 validation providers: + + - UploadValidation (validation rules useful for any upload) + - ImageValidation (validation rules specifically for images) + - DefaultValidation (loads all of the above) + +Since by default, no validation rules are loaded, you should start with that: + +.. code:: php + + provider('upload', \Josegonzalez\Upload\Validation\UploadValidation::class); + // OR + $validator->provider('upload', \Josegonzalez\Upload\Validation\ImageValidation::class); + // OR + $validator->provider('upload', \Josegonzalez\Upload\Validation\DefaultValidation::class); + + ?> + +Afterwards, you can use its rules like: + +.. code:: php + + add('file', 'customName', [ + 'rule' => 'nameOfTheRule', + 'message' => 'yourErrorMessage', + 'provider' => 'upload' + ]); + + ?> + +It might come in handy to only use a validation rule when there actually is an uploaded file: + +.. code:: php + + add('file', 'customName', [ + 'rule' => 'nameOfTheRule', + 'message' => 'yourErrorMessage', + 'provider' => 'upload', + 'on' => function($context) { + return isset($context['data']['file']) && $context['data']['file']['error'] == UPLOAD_ERR_OK; + } + ]); + + ?> + +More information on conditional validation can be found `here `__. + +UploadValidation +^^^^^^^^^^^^^^^^ + +**isUnderPhpSizeLimit** + +Check that the file does not exceed the max file size specified by PHP + +.. code:: php + + add('file', 'fileUnderPhpSizeLimit', [ + 'rule' => 'isUnderPhpSizeLimit', + 'message' => 'This file is too large', + 'provider' => 'upload' + ]); + + ?> + +**isUnderFormSizeLimit** + +Check that the file does not exceed the max file size specified in the +HTML Form + +.. code:: php + + add('file', 'fileUnderFormSizeLimit', [ + 'rule' => 'isUnderFormSizeLimit', + 'message' => 'This file is too large', + 'provider' => 'upload' + ]); + + ?> + +**isCompletedUpload** + +Check that the file was completely uploaded + +.. code:: php + + add('file', 'fileCompletedUpload', [ + 'rule' => 'isCompletedUpload', + 'message' => 'This file could not be uploaded completely', + 'provider' => 'upload' + ]); + + ?> + +**isFileUpload** + +Check that a file was uploaded + +.. code:: php + + add('file', 'fileFileUpload', [ + 'rule' => 'isFileUpload', + 'message' => 'There was no file found to upload', + 'provider' => 'upload' + ]); + + ?> + +**isSuccessfulWrite** + +Check that the file was successfully written to the server + +.. code:: php + + add('file', 'fileSuccessfulWrite', [ + 'rule' => 'isSuccessfulWrite', + 'message' => 'This upload failed', + 'provider' => 'upload' + ]); + + ?> + +**isBelowMaxSize** + +Check that the file is below the maximum file upload size (checked in +bytes) + +.. code:: php + + add('file', 'fileBelowMaxSize', [ + 'rule' => ['isBelowMaxSize', 1024], + 'message' => 'This file is too large', + 'provider' => 'upload' + ]); + + ?> + +**isAboveMinSize** + +Check that the file is above the minimum file upload size (checked in +bytes) + +.. code:: php + + add('file', 'fileAboveMinSize', [ + 'rule' => ['isAboveMinSize', 1024], + 'message' => 'This file is too small', + 'provider' => 'upload' + ]); + + ?> + +ImageValidation +^^^^^^^^^^^^^^^ + +**isAboveMinHeight** + +Check that the file is above the minimum height requirement (checked in +pixels) + +.. code:: php + + add('file', 'fileAboveMinHeight', [ + 'rule' => ['isAboveMinHeight', 200], + 'message' => 'This image should at least be 200px high', + 'provider' => 'upload' + ]); + + ?> + +**isBelowMaxHeight** + +Check that the file is below the maximum height requirement (checked in +pixels) + +.. code:: php + + add('file', 'fileBelowMaxHeight', [ + 'rule' => ['isBelowMaxHeight', 200], + 'message' => 'This image should not be higher than 200px', + 'provider' => 'upload' + ]); + + ?> + +**isAboveMinWidth** + +Check that the file is above the minimum width requirement (checked in +pixels) + +.. code:: php + + add('file', 'fileAboveMinWidth', [ + 'rule' => ['isAboveMinWidth', 200], + 'message' => 'This image should at least be 200px wide', + 'provider' => 'upload' + ]); + + ?> + +**isBelowMaxWidth** + +Check that the file is below the maximum width requirement (checked in +pixels) + +.. code:: php + + add('file', 'fileBelowMaxWidth', [ + 'rule' => ['isBelowMaxWidth', 200], + 'message' => 'This image should not be wider than 200px', + 'provider' => 'upload' + ]); + + ?> diff --git a/src/Validation/DefaultValidation.php b/src/Validation/DefaultValidation.php new file mode 100644 index 00000000..64601af5 --- /dev/null +++ b/src/Validation/DefaultValidation.php @@ -0,0 +1,12 @@ + 0 && $imgWidth >= $width; + } + + /** + * Check that the file is below the maximum width requirement + * + * @param mixed $check Value to check + * @param int $width Width of Image + * @return bool Success + */ + public static function isBelowMaxWidth($check, $width) + { + // Non-file uploads also mean the height is too big + if (!isset($check['tmp_name']) || !strlen($check['tmp_name'])) { + return false; + } + list($imgWidth) = getimagesize($check['tmp_name']); + return $width > 0 && $imgWidth <= $width; + } + + /** + * Check that the file is above the minimum height requirement + * + * @param mixed $check Value to check + * @param int $height Height of Image + * @return bool Success + */ + public static function isAboveMinHeight($check, $height) + { + // Non-file uploads also mean the height is too big + if (!isset($check['tmp_name']) || !strlen($check['tmp_name'])) { + return false; + } + list(, $imgHeight) = getimagesize($check['tmp_name']); + return $height > 0 && $imgHeight >= $height; + } + + /** + * Check that the file is below the maximum height requirement + * + * @param mixed $check Value to check + * @param int $height Height of Image + * @return bool Success + */ + public static function isBelowMaxHeight($check, $height) + { + // Non-file uploads also mean the height is too big + if (!isset($check['tmp_name']) || !strlen($check['tmp_name'])) { + return false; + } + list(, $imgHeight) = getimagesize($check['tmp_name']); + return $height > 0 && $imgHeight <= $height; + } +} diff --git a/src/Validation/Traits/UploadValidationTrait.php b/src/Validation/Traits/UploadValidationTrait.php new file mode 100644 index 00000000..9392be85 --- /dev/null +++ b/src/Validation/Traits/UploadValidationTrait.php @@ -0,0 +1,97 @@ += $size; + } + + /** + * Check that the file is below the maximum file upload size + * + * @param mixed $check Value to check + * @param int $size Maximum file size + * @return bool Success + */ + public static function isBelowMaxSize($check, $size) + { + // Non-file uploads also mean the size is too small + if (!isset($check['size']) || !strlen($check['size'])) { + return false; + } + return $check['size'] <= $size; + } +} diff --git a/src/Validation/UploadValidation.php b/src/Validation/UploadValidation.php new file mode 100644 index 00000000..a4408065 --- /dev/null +++ b/src/Validation/UploadValidation.php @@ -0,0 +1,10 @@ +vfs = new Vfs; + mkdir($this->vfs->path('/tmp')); + + // Write sample image with dimensions: 20x20 + $img = fopen($this->vfs->path('/tmp/tmpimage'), "wb"); + fwrite($img, base64_decode('iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAB3RJTUUH4AMUECwX5I9GIwAAACFJREFUOMtj/P//PwM1ARMDlcGogaMGjho4auCogUPFQABpCwMlgqgSYAAAAABJRU5ErkJggg==')); + fclose($img); + + $this->data = [ + 'name' => 'sample.txt', + 'type' => 'text/plain', + 'tmp_name' => $this->vfs->path('/tmp/tmpimage'), + 'size' => 200, + 'error' => UPLOAD_ERR_OK + ]; + } + + public function teardown() + { + parent::tearDown(); + } + + public function testIsAboveMinWidth() + { + $this->assertTrue(ImageValidation::isAboveMinWidth($this->data, 10)); + $this->assertFalse(ImageValidation::isAboveMinWidth($this->data, 30)); + + // Test if no tmp_name is set or specified + $this->data['tmp_name'] = ''; + $this->assertFalse(ImageValidation::isAboveMinWidth($this->data, 10)); + + unset($this->data['tmp_name']); + $this->assertFalse(ImageValidation::isAboveMinWidth($this->data, 10)); + } + + public function testIsBelowMaxWidth() + { + $this->assertTrue(ImageValidation::isBelowMaxWidth($this->data, 30)); + $this->assertFalse(ImageValidation::isBelowMaxWidth($this->data, 10)); + + // Test if no tmp_name is set or specified + $this->data['tmp_name'] = ''; + $this->assertFalse(ImageValidation::isBelowMaxWidth($this->data, 10)); + + unset($this->data['tmp_name']); + $this->assertFalse(ImageValidation::isBelowMaxWidth($this->data, 10)); + } + + public function testIsAboveMinHeight() + { + $this->assertTrue(ImageValidation::isAboveMinHeight($this->data, 10)); + $this->assertFalse(ImageValidation::isAboveMinHeight($this->data, 30)); + + // Test if no tmp_name is set or specified + $this->data['tmp_name'] = ''; + $this->assertFalse(ImageValidation::isAboveMinHeight($this->data, 10)); + + unset($this->data['tmp_name']); + $this->assertFalse(ImageValidation::isAboveMinHeight($this->data, 10)); + } + + public function testIsBelowMaxHeight() + { + $this->assertTrue(ImageValidation::isBelowMaxHeight($this->data, 30)); + $this->assertFalse(ImageValidation::isBelowMaxHeight($this->data, 10)); + + // Test if no tmp_name is set or specified + $this->data['tmp_name'] = ''; + $this->assertFalse(ImageValidation::isBelowMaxHeight($this->data, 10)); + + unset($this->data['tmp_name']); + $this->assertFalse(ImageValidation::isBelowMaxHeight($this->data, 10)); + } +} diff --git a/tests/TestCase/Validation/UploadValidationTest.php b/tests/TestCase/Validation/UploadValidationTest.php new file mode 100644 index 00000000..cfa0c627 --- /dev/null +++ b/tests/TestCase/Validation/UploadValidationTest.php @@ -0,0 +1,83 @@ +data = [ + 'name' => 'sample.txt', + 'type' => 'text/plain', + 'tmp_name' => '/tmp/tmpfile', + 'size' => 200 + ]; + } + + public function teardown() + { + parent::tearDown(); + } + + public function testIsUnderPhpSizeLimit() + { + $this->assertTrue(UploadValidation::isUnderPhpSizeLimit($this->data + ['error' => UPLOAD_ERR_OK])); + $this->assertFalse(UploadValidation::isUnderPhpSizeLimit($this->data + ['error' => UPLOAD_ERR_INI_SIZE])); + } + + public function testIsUnderFormSizeLimit() + { + $this->assertTrue(UploadValidation::isUnderFormSizeLimit($this->data + ['error' => UPLOAD_ERR_OK])); + $this->assertFalse(UploadValidation::isUnderFormSizeLimit($this->data + ['error' => UPLOAD_ERR_FORM_SIZE])); + } + + public function testIsCompletedUpload() + { + $this->assertTrue(UploadValidation::isCompletedUpload($this->data + ['error' => UPLOAD_ERR_OK])); + $this->assertFalse(UploadValidation::isCompletedUpload($this->data + ['error' => UPLOAD_ERR_PARTIAL])); + } + + public function testIsFileUpload() + { + $this->assertTrue(UploadValidation::isFileUpload($this->data + ['error' => UPLOAD_ERR_OK])); + $this->assertFalse(UploadValidation::isFileUpload($this->data + ['error' => UPLOAD_ERR_NO_FILE])); + } + + public function testIsSuccessfulWrite() + { + $this->assertTrue(UploadValidation::isSuccessfulWrite($this->data + ['error' => UPLOAD_ERR_OK])); + $this->assertFalse(UploadValidation::isSuccessfulWrite($this->data + ['error' => UPLOAD_ERR_CANT_WRITE])); + } + + public function testIsAboveMinSize() + { + $this->assertTrue(UploadValidation::isAboveMinSize($this->data, 200)); + $this->assertFalse(UploadValidation::isAboveMinSize($this->data, 250)); + + // Test if no size is set or specified + $this->data['size'] = ''; + $this->assertFalse(UploadValidation::isAboveMinSize($this->data, 200)); + + unset($this->data['size']); + $this->assertFalse(UploadValidation::isAboveMinSize($this->data, 200)); + } + + public function testIsBelowMaxSize() + { + $this->assertTrue(UploadValidation::isBelowMaxSize($this->data, 200)); + $this->assertFalse(UploadValidation::isBelowMaxSize($this->data, 150)); + + // Test if no size is set or specified + $this->data['size'] = ''; + $this->assertFalse(UploadValidation::isBelowMaxSize($this->data, 200)); + + unset($this->data['size']); + $this->assertFalse(UploadValidation::isBelowMaxSize($this->data, 200)); + } +}