From 9f29f4001f984489be5a1e7b3a4c14f9d4c2f198 Mon Sep 17 00:00:00 2001 From: ruzann <40264956+ruzann@users.noreply.github.com> Date: Wed, 18 Jul 2018 13:11:41 +0400 Subject: [PATCH 01/16] Allow setting a failure message for required Collections This patch modifies the `CollectionInputFilter` to allow injecting a `Zend\Validator\NotEmpty` instance. When the collection is marked "required", but receives an empty data set, it will use that validator to provide a validation failure message. (If no validator was set, it lazy-loads one.) When using factory-based collections, you may now also provide a `required_message` key as a sibling to `required`; when set, this message will be used in place of the default `NotEmpty` validation error message. --- src/CollectionInputFilter.php | 50 ++++++++++++++++++++++++++++++ src/Factory.php | 3 ++ test/CollectionInputFilterTest.php | 37 +++++++++++++++++++++- 3 files changed, 89 insertions(+), 1 deletion(-) diff --git a/src/CollectionInputFilter.php b/src/CollectionInputFilter.php index 33b28c84..e198be7e 100644 --- a/src/CollectionInputFilter.php +++ b/src/CollectionInputFilter.php @@ -10,6 +10,7 @@ namespace Zend\InputFilter; use Traversable; +use Zend\Validator\NotEmpty; class CollectionInputFilter extends InputFilter { @@ -43,6 +44,11 @@ class CollectionInputFilter extends InputFilter */ protected $inputFilter; + /** + * @var NotEmpty + */ + protected $notEmptyValidator; + /** * Set the input filter to use when looping the data * @@ -164,6 +170,30 @@ public function setData($data) return $this; } + /** + * @return NotEmpty + */ + public function getNotEmptyValidator() + { + if ($this->notEmptyValidator === null) { + $this->notEmptyValidator = new NotEmpty(); + } + + return $this->notEmptyValidator; + } + + /** + * @param NotEmpty $notEmptyValidator + * + * @return $this + */ + public function setNotEmptyValidator(NotEmpty $notEmptyValidator) + { + $this->notEmptyValidator = $notEmptyValidator; + + return $this; + } + /** * {@inheritdoc} * @param mixed $context Ignored, but present to retain signature compatibility. @@ -176,6 +206,7 @@ public function isValid($context = null) if ($this->getCount() < 1) { if ($this->isRequired) { + $this->collectionMessages[] = $this->prepareRequiredValidationFailureMessage(); $valid = false; } } @@ -295,4 +326,23 @@ public function getUnknown() return $unknownInputs; } + + /** + * @return array + */ + protected function prepareRequiredValidationFailureMessage() + { + $notEmptyValidator = $this->getNotEmptyValidator(); + $templates = $notEmptyValidator->getOption('messageTemplates'); + $message = $templates[NotEmpty::IS_EMPTY]; + $translator = $notEmptyValidator->getTranslator(); + + if ($translator) { + $message = $translator->translate($message, $notEmptyValidator->getTranslatorTextDomain()); + } + + return [ + NotEmpty::IS_EMPTY => $message, + ]; + } } diff --git a/src/Factory.php b/src/Factory.php index 01f299e4..53459320 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -319,6 +319,9 @@ public function createInputFilter($inputFilterSpecification) if (isset($inputFilterSpecification['required'])) { $inputFilter->setIsRequired($inputFilterSpecification['required']); } + if (isset($inputFilterSpecification['required_message'])) { + $inputFilter->getNotEmptyValidator()->setMessage($inputFilterSpecification['required_message']); + } return $inputFilter; } diff --git a/test/CollectionInputFilterTest.php b/test/CollectionInputFilterTest.php index 72eace01..d06e4d66 100644 --- a/test/CollectionInputFilterTest.php +++ b/test/CollectionInputFilterTest.php @@ -171,7 +171,7 @@ public function dataVsValidProvider() 'Required: F, Count: N, Valid: T' => [!$isRequired, null, $colRaw, $validIF , $colRaw, $colFiltered, true , []], 'Required: F, Count: N, Valid: F' => [!$isRequired, null, $colRaw, $invalidIF, $colRaw, $colFiltered, false, $colMessages], 'Required: F, Count: +1, Valid: F' => [!$isRequired, 2, $colRaw, $invalidIF, $colRaw, $colFiltered, false, $colMessages], - 'Required: T, Data: [], Valid: X' => [ $isRequired, null, [] , $noValidIF, [] , [] , false, []], + 'Required: T, Data: [], Valid: X' => [ $isRequired, null, [] , $noValidIF, [] , [] , false, [['isEmpty' => 'Value is required and can\'t be empty']]], 'Required: F, Data: [], Valid: X' => [!$isRequired, null, [] , $noValidIF, [] , [] , true , []], ]; // @codingStandardsIgnoreEnd @@ -735,4 +735,39 @@ public function testDuplicatedErrorMessages() ], ], $inputFilter->getMessages()); } + + public function testSetNotEmptyValidator() + { + $notEmptyValidator = new NotEmpty(); + $this->inputFilter->setNotEmptyValidator($notEmptyValidator); + $this->assertEquals( + $notEmptyValidator->getMessageTemplates()[NotEmpty::IS_EMPTY], + $this->inputFilter->getNotEmptyValidator()->getMessageTemplates()[NotEmpty::IS_EMPTY] + ); + + $value = 'foo'; + $notEmptyValidator = new NotEmpty(); + $notEmptyValidator->setMessage($value); + $this->inputFilter->setNotEmptyValidator($notEmptyValidator); + $this->assertEquals( + $value, + $this->inputFilter->getNotEmptyValidator()->getMessageTemplates()[NotEmpty::IS_EMPTY] + ); + } + + public function testGetNotEmptyValidator() + { + $notEmptyValidator = new NotEmpty(); + $this->assertEquals( + $notEmptyValidator->getMessageTemplates()[NotEmpty::IS_EMPTY], + $this->inputFilter->getNotEmptyValidator()->getMessageTemplates()[NotEmpty::IS_EMPTY] + ); + + $value = 'foo'; + $this->inputFilter->getNotEmptyValidator()->setMessage($value); + $this->assertEquals( + $value, + $this->inputFilter->getNotEmptyValidator()->getMessageTemplates()[NotEmpty::IS_EMPTY] + ); + } } From 90dd83651af43b53ca1aca5a7e70665c86e32201 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Mon, 17 Dec 2018 10:26:37 -0600 Subject: [PATCH 02/16] Simplification of required message code - Combine conditionals, as there is only one combination that has a meaning - Use a ternary to allow removing a transient variable --- src/CollectionInputFilter.php | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/src/CollectionInputFilter.php b/src/CollectionInputFilter.php index e198be7e..005525fc 100644 --- a/src/CollectionInputFilter.php +++ b/src/CollectionInputFilter.php @@ -171,6 +171,11 @@ public function setData($data) } /** + * Retrieve the NotEmpty validator to use for failed "required" validations. + * + * This validator will be used to produce a validation failure message in + * cases where the collection is empty but required. + * * @return NotEmpty */ public function getNotEmptyValidator() @@ -183,8 +188,12 @@ public function getNotEmptyValidator() } /** - * @param NotEmpty $notEmptyValidator + * Set the NotEmpty validator to use for failed "required" validations. * + * This validator will be used to produce a validation failure message in + * cases where the collection is empty but required. + * + * @param NotEmpty $notEmptyValidator * @return $this */ public function setNotEmptyValidator(NotEmpty $notEmptyValidator) @@ -204,11 +213,9 @@ public function isValid($context = null) $inputFilter = $this->getInputFilter(); $valid = true; - if ($this->getCount() < 1) { - if ($this->isRequired) { - $this->collectionMessages[] = $this->prepareRequiredValidationFailureMessage(); - $valid = false; - } + if ($this->getCount() < 1 && $this->isRequired) { + $this->collectionMessages[] = $this->prepareRequiredValidationFailureMessage(); + $valid = false; } if (count($this->data) < $this->getCount()) { @@ -328,21 +335,19 @@ public function getUnknown() } /** - * @return array + * @return array */ protected function prepareRequiredValidationFailureMessage() { $notEmptyValidator = $this->getNotEmptyValidator(); - $templates = $notEmptyValidator->getOption('messageTemplates'); - $message = $templates[NotEmpty::IS_EMPTY]; - $translator = $notEmptyValidator->getTranslator(); - - if ($translator) { - $message = $translator->translate($message, $notEmptyValidator->getTranslatorTextDomain()); - } + $templates = $notEmptyValidator->getOption('messageTemplates'); + $message = $templates[NotEmpty::IS_EMPTY]; + $translator = $notEmptyValidator->getTranslator(); return [ - NotEmpty::IS_EMPTY => $message, + NotEmpty::IS_EMPTY => $translator + ? $translator->translate($message, $notEmptyValidator->getTranslatorTextDomain()) + : $message, ]; } } From 730994a4d7f597ed5c0e3c91ce868a1413d54796 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Mon, 17 Dec 2018 10:53:16 -0600 Subject: [PATCH 03/16] Split tests into discrete behaviors - Lazy-loads a NotEmpty validator if none provided. - Allows composing a NotEmpty validator. - Uses the message from a composed NotEmpty validator when validating a required, but empty, collection. - Factory will use the `required_message` key to seed the `NotEmpty` validator of a collection. --- test/CollectionInputFilterTest.php | 43 +++++++++++++----------------- test/FactoryTest.php | 22 +++++++++++++++ 2 files changed, 41 insertions(+), 24 deletions(-) diff --git a/test/CollectionInputFilterTest.php b/test/CollectionInputFilterTest.php index d06e4d66..e09a6e12 100644 --- a/test/CollectionInputFilterTest.php +++ b/test/CollectionInputFilterTest.php @@ -736,38 +736,33 @@ public function testDuplicatedErrorMessages() ], $inputFilter->getMessages()); } - public function testSetNotEmptyValidator() + public function testLazyLoadsANotEmptyValidatorWhenNoneProvided() { - $notEmptyValidator = new NotEmpty(); - $this->inputFilter->setNotEmptyValidator($notEmptyValidator); - $this->assertEquals( - $notEmptyValidator->getMessageTemplates()[NotEmpty::IS_EMPTY], - $this->inputFilter->getNotEmptyValidator()->getMessageTemplates()[NotEmpty::IS_EMPTY] - ); + $this->assertInstanceOf(NotEmpty::class, $this->inputFilter->getNotEmptyValidator()); + } - $value = 'foo'; + public function testAllowsComposingANotEmptyValidator() + { $notEmptyValidator = new NotEmpty(); - $notEmptyValidator->setMessage($value); $this->inputFilter->setNotEmptyValidator($notEmptyValidator); - $this->assertEquals( - $value, - $this->inputFilter->getNotEmptyValidator()->getMessageTemplates()[NotEmpty::IS_EMPTY] - ); + $this->assertSame($notEmptyValidator, $this->inputFilter->getNotEmptyValidator()); } - public function testGetNotEmptyValidator() + public function testUsesMessageFromComposedNotEmptyValidatorWhenRequiredButCollectionIsEmpty() { + $message = 'this is the validation message'; $notEmptyValidator = new NotEmpty(); - $this->assertEquals( - $notEmptyValidator->getMessageTemplates()[NotEmpty::IS_EMPTY], - $this->inputFilter->getNotEmptyValidator()->getMessageTemplates()[NotEmpty::IS_EMPTY] - ); + $notEmptyValidator->setMessage($message); - $value = 'foo'; - $this->inputFilter->getNotEmptyValidator()->setMessage($value); - $this->assertEquals( - $value, - $this->inputFilter->getNotEmptyValidator()->getMessageTemplates()[NotEmpty::IS_EMPTY] - ); + $this->inputFilter->setIsRequired(true); + $this->inputFilter->setNotEmptyValidator($notEmptyValidator); + + $this->inputFilter->setData([]); + + $this->assertFalse($this->inputFilter->isValid()); + + $this->assertEquals([ + [NotEmpty::IS_EMPTY => $message], + ], $this->inputFilter->getMessages()); } } diff --git a/test/FactoryTest.php b/test/FactoryTest.php index 228cd0f8..610b0592 100644 --- a/test/FactoryTest.php +++ b/test/FactoryTest.php @@ -1038,6 +1038,28 @@ public function testWhenCreateInputPullsInputFromThePluginManagerItMustNotOverwr $this->assertSame($input->reveal(), $factory->createInput($spec)); } + public function testFactoryCanCreateCollectionInputFilterWithRequiredMessage() + { + $factory = $this->createDefaultFactory(); + $message = 'this is the validation message'; + + /** @var CollectionInputFilter $inputFilter */ + $inputFilter = $factory->createInputFilter([ + 'type' => CollectionInputFilter::class, + 'required' => true, + 'required_message' => $message, + 'inputfilter' => new InputFilter(), + 'count' => 3, + ]); + + $this->assertInstanceOf(CollectionInputFilter::class, $inputFilter); + + $notEmptyValidator = $inputFilter->getNotEmptyValidator(); + $messageTemplates = $notEmptyValidator->getMessageTemplates(); + $this->assertArrayHasKey(Validator\NotEmpty::IS_EMPTY, $messageTemplates); + $this->assertSame($message, $messageTemplates[Validator\NotEmpty::IS_EMPTY]); + } + /** * @return Factory */ From 9fd35469faea2ae5b063bdf2f621dbb92059fd5c Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Mon, 17 Dec 2018 10:56:23 -0600 Subject: [PATCH 04/16] Adds CHANGELOG entry for #170 --- CHANGELOG.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9860a368..e8aa54e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,17 @@ All notable changes to this project will be documented in this file, in reverse ### Added -- Nothing. +- [#170](https://github.com/zendframework/zend-inputfilter/pull/170) adds the ability to set a "required" message on a `CollectionInputFilter`. + By default, such instances will lazy-load a `NotEmpty` validator, and use its + messages to report that the collection was empty if it is marked as required. + If you wish to set a different message, you have two options: + + - provide a custom `NotEmpty` validator via the new method + `setNotEmptyValidator()`. + + - if using a factory, provide the key `required_message` as a sibling to + `required`, containing the custom message. This will replace the typical + `IS_EMPTY` message. ### Changed From c7e4615bc03e1ece0c45349e4adb4327d4e6a284 Mon Sep 17 00:00:00 2001 From: Sasha Alex Romanenko Date: Tue, 14 Aug 2018 16:29:52 -0400 Subject: [PATCH 05/16] Copy and commit to PSR file to easier track changes in editor. --- src/PsrFileInput.php | 220 ++++++++++++++++++++++ test/PsrFileInputTest.php | 372 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 592 insertions(+) create mode 100644 src/PsrFileInput.php create mode 100644 test/PsrFileInputTest.php diff --git a/src/PsrFileInput.php b/src/PsrFileInput.php new file mode 100644 index 00000000..5e9cf390 --- /dev/null +++ b/src/PsrFileInput.php @@ -0,0 +1,220 @@ +autoPrependUploadValidator = $value; + return $this; + } + + /** + * @return bool + */ + public function getAutoPrependUploadValidator() + { + return $this->autoPrependUploadValidator; + } + + /** + * @return mixed + */ + public function getValue() + { + $value = $this->value; + if ($this->isValid && is_array($value)) { + // Run filters ~after~ validation, so that is_uploaded_file() + // validation is not affected by filters. + $filter = $this->getFilterChain(); + if (isset($value['tmp_name'])) { + // Single file input + $value = $filter->filter($value); + } else { + // Multi file input (multiple attribute set) + $newValue = []; + foreach ($value as $fileData) { + if (is_array($fileData) && isset($fileData['tmp_name'])) { + $newValue[] = $filter->filter($fileData); + } + } + $value = $newValue; + } + } + + return $value; + } + + /** + * Checks if the raw input value is an empty file input eg: no file was uploaded + * + * @param $rawValue + * @return bool + */ + public function isEmptyFile($rawValue) + { + if (! is_array($rawValue)) { + return true; + } + + if (isset($rawValue['error']) && $rawValue['error'] === UPLOAD_ERR_NO_FILE) { + return true; + } + + if (count($rawValue) === 1 && isset($rawValue[0])) { + return $this->isEmptyFile($rawValue[0]); + } + + return false; + } + + /** + * @param mixed $context Extra "context" to provide the validator + * @return bool + */ + public function isValid($context = null) + { + $rawValue = $this->getRawValue(); + $hasValue = $this->hasValue(); + $empty = $this->isEmptyFile($rawValue); + $required = $this->isRequired(); + $allowEmpty = $this->allowEmpty(); + $continueIfEmpty = $this->continueIfEmpty(); + + if (! $hasValue && ! $required) { + return true; + } + + if (! $hasValue && $required && ! $this->hasFallback()) { + if ($this->errorMessage === null) { + $this->errorMessage = $this->prepareRequiredValidationFailureMessage(); + } + return false; + } + + if ($empty && ! $required && ! $continueIfEmpty) { + return true; + } + + if ($empty && $allowEmpty && ! $continueIfEmpty) { + return true; + } + + $this->injectUploadValidator(); + $validator = $this->getValidatorChain(); + //$value = $this->getValue(); // Do not run the filters yet for File uploads (see getValue()) + + if (! is_array($rawValue)) { + // This can happen in an AJAX POST, where the input comes across as a string + $rawValue = [ + 'tmp_name' => $rawValue, + 'name' => $rawValue, + 'size' => 0, + 'type' => '', + 'error' => UPLOAD_ERR_NO_FILE, + ]; + } + if (is_array($rawValue) && isset($rawValue['tmp_name'])) { + // Single file input + $this->isValid = $validator->isValid($rawValue, $context); + } elseif (is_array($rawValue) && isset($rawValue[0]['tmp_name'])) { + // Multi file input (multiple attribute set) + $this->isValid = true; + foreach ($rawValue as $value) { + if (! $validator->isValid($value, $context)) { + $this->isValid = false; + break; // Do not continue processing files if validation fails + } + } + } + + return $this->isValid; + } + + /** + * @return void + */ + protected function injectUploadValidator() + { + if (! $this->autoPrependUploadValidator) { + return; + } + $chain = $this->getValidatorChain(); + + // Check if Upload validator is already first in chain + $validators = $chain->getValidators(); + if (isset($validators[0]['instance']) + && $validators[0]['instance'] instanceof UploadValidator + ) { + $this->autoPrependUploadValidator = false; + return; + } + + $chain->prependByName('fileuploadfile', [], true); + $this->autoPrependUploadValidator = false; + } + + /** + * @deprecated 2.4.8 See note on parent class. Removal does not affect this class. + * + * No-op, NotEmpty validator does not apply for PsrFileInputs. + * See also: BaseInputFilter::isValid() + * + * @return void + */ + protected function injectNotEmptyValidator() + { + $this->notEmptyValidator = true; + } + + /** + * @param InputInterface $input + * @return PsrFileInput + */ + public function merge(InputInterface $input) + { + parent::merge($input); + if ($input instanceof PsrFileInput) { + $this->setAutoPrependUploadValidator($input->getAutoPrependUploadValidator()); + } + return $this; + } +} diff --git a/test/PsrFileInputTest.php b/test/PsrFileInputTest.php new file mode 100644 index 00000000..64c72338 --- /dev/null +++ b/test/PsrFileInputTest.php @@ -0,0 +1,372 @@ +input = new PsrFileInput('foo'); + // Upload validator does not work in CLI test environment, disable + $this->input->setAutoPrependUploadValidator(false); + } + + public function testRetrievingValueFiltersTheValue() + { + $this->markTestSkipped('Test are not enabled in PsrFileInputTest'); + } + + public function testRetrievingValueFiltersTheValueOnlyAfterValidating() + { + $value = ['tmp_name' => 'bar']; + $this->input->setValue($value); + + $newValue = ['tmp_name' => 'foo']; + $this->input->setFilterChain($this->createFilterChainMock([[$value, $newValue]])); + + $this->assertEquals($value, $this->input->getValue()); + $this->assertTrue( + $this->input->isValid(), + 'isValid() value not match. Detail . ' . json_encode($this->input->getMessages()) + ); + $this->assertEquals($newValue, $this->input->getValue()); + } + + public function testCanFilterArrayOfMultiFileData() + { + $values = [ + ['tmp_name' => 'foo'], + ['tmp_name' => 'bar'], + ['tmp_name' => 'baz'], + ]; + $this->input->setValue($values); + + $newValue = ['tmp_name' => 'new']; + $filteredValue = [$newValue, $newValue, $newValue]; + $this->input->setFilterChain($this->createFilterChainMock([ + [$values[0], $newValue], + [$values[1], $newValue], + [$values[2], $newValue], + ])); + + $this->assertEquals($values, $this->input->getValue()); + $this->assertTrue( + $this->input->isValid(), + 'isValid() value not match. Detail . ' . json_encode($this->input->getMessages()) + ); + $this->assertEquals( + $filteredValue, + $this->input->getValue() + ); + } + + public function testCanRetrieveRawValue() + { + $value = ['tmp_name' => 'bar']; + $this->input->setValue($value); + + $newValue = ['tmp_name' => 'new']; + $this->input->setFilterChain($this->createFilterChainMock([[$value, $newValue]])); + + $this->assertEquals($value, $this->input->getRawValue()); + } + + public function testValidationOperatesOnFilteredValue() + { + $this->markTestSkipped('Test is not enabled in PsrFileInputTest'); + } + + public function testValidationOperatesBeforeFiltering() + { + $badValue = [ + 'tmp_name' => ' ' . __FILE__ . ' ', + 'name' => 'foo', + 'size' => 1, + 'error' => 0, + ]; + $this->input->setValue($badValue); + + $filteredValue = ['tmp_name' => 'new']; + $this->input->setFilterChain($this->createFilterChainMock([[$badValue, $filteredValue]])); + $this->input->setValidatorChain($this->createValidatorChainMock([[$badValue, null, false]])); + + $this->assertFalse($this->input->isValid()); + $this->assertEquals($badValue, $this->input->getValue()); + } + + public function testAutoPrependUploadValidatorIsOnByDefault() + { + $input = new PsrFileInput('foo'); + $this->assertTrue($input->getAutoPrependUploadValidator()); + } + + public function testUploadValidatorIsAddedWhenIsValidIsCalled() + { + $this->input->setAutoPrependUploadValidator(true); + $this->assertTrue($this->input->getAutoPrependUploadValidator()); + $this->assertTrue($this->input->isRequired()); + $this->input->setValue([ + 'tmp_name' => __FILE__, + 'name' => 'foo', + 'size' => 1, + 'error' => 0, + ]); + $validatorChain = $this->input->getValidatorChain(); + $this->assertEquals(0, count($validatorChain->getValidators())); + + $this->assertFalse($this->input->isValid()); + $validators = $validatorChain->getValidators(); + $this->assertEquals(1, count($validators)); + $this->assertInstanceOf(Validator\File\UploadFile::class, $validators[0]['instance']); + } + + public function testUploadValidatorIsNotAddedWhenIsValidIsCalled() + { + $this->assertFalse($this->input->getAutoPrependUploadValidator()); + $this->assertTrue($this->input->isRequired()); + $this->input->setValue(['tmp_name' => 'bar']); + $validatorChain = $this->input->getValidatorChain(); + $this->assertEquals(0, count($validatorChain->getValidators())); + + $this->assertTrue( + $this->input->isValid(), + 'isValid() value not match. Detail . ' . json_encode($this->input->getMessages()) + ); + $this->assertEquals(0, count($validatorChain->getValidators())); + } + + public function testRequiredUploadValidatorValidatorNotAddedWhenOneExists() + { + $this->input->setAutoPrependUploadValidator(true); + $this->assertTrue($this->input->getAutoPrependUploadValidator()); + $this->assertTrue($this->input->isRequired()); + $this->input->setValue(['tmp_name' => 'bar']); + + /** @var Validator\File\UploadFile|MockObject $uploadMock */ + $uploadMock = $this->getMockBuilder(Validator\File\UploadFile::class) + ->setMethods(['isValid']) + ->getMock(); + $uploadMock->expects($this->exactly(1)) + ->method('isValid') + ->will($this->returnValue(true)); + + $validatorChain = $this->input->getValidatorChain(); + $validatorChain->prependValidator($uploadMock); + $this->assertTrue( + $this->input->isValid(), + 'isValid() value not match. Detail . ' . json_encode($this->input->getMessages()) + ); + + $validators = $validatorChain->getValidators(); + $this->assertEquals(1, count($validators)); + $this->assertEquals($uploadMock, $validators[0]['instance']); + } + + public function testValidationsRunWithoutFileArrayDueToAjaxPost() + { + $this->input->setAutoPrependUploadValidator(true); + $this->assertTrue($this->input->getAutoPrependUploadValidator()); + $this->assertTrue($this->input->isRequired()); + $this->input->setValue(''); + + $expectedNormalizedValue = [ + 'tmp_name' => '', + 'name' => '', + 'size' => 0, + 'type' => '', + 'error' => UPLOAD_ERR_NO_FILE, + ]; + $this->input->setValidatorChain($this->createValidatorChainMock([[$expectedNormalizedValue, null, false]])); + $this->assertFalse($this->input->isValid()); + } + + public function testNotEmptyValidatorAddedWhenIsValidIsCalled($value = null) + { + $this->markTestSkipped('Test is not enabled in PsrFileInputTest'); + } + + public function testRequiredNotEmptyValidatorNotAddedWhenOneExists($value = null) + { + $this->markTestSkipped('Test is not enabled in PsrFileInputTest'); + } + + public function testFallbackValueVsIsValidRules( + $required = null, + $fallbackValue = null, + $originalValue = null, + $isValid = null, + $expectedValue = null + ) { + $this->markTestSkipped('Input::setFallbackValue is not implemented on PsrFileInput'); + } + + + public function testFallbackValueVsIsValidRulesWhenValueNotSet( + $required = null, + $fallbackValue = null + ) { + $this->markTestSkipped('Input::setFallbackValue is not implemented on PsrFileInput'); + } + + public function testIsEmptyFileNotArray() + { + $rawValue = 'file'; + $this->assertTrue($this->input->isEmptyFile($rawValue)); + } + + public function testIsEmptyFileUploadNoFile() + { + $rawValue = [ + 'tmp_name' => '', + 'error' => \UPLOAD_ERR_NO_FILE, + ]; + $this->assertTrue($this->input->isEmptyFile($rawValue)); + } + + public function testIsEmptyFileOk() + { + $rawValue = [ + 'tmp_name' => 'name', + 'error' => \UPLOAD_ERR_OK, + ]; + $this->assertFalse($this->input->isEmptyFile($rawValue)); + } + + public function testIsEmptyMultiFileUploadNoFile() + { + $rawValue = [[ + 'tmp_name' => 'foo', + 'error' => \UPLOAD_ERR_NO_FILE, + ]]; + $this->assertTrue($this->input->isEmptyFile($rawValue)); + } + + public function testIsEmptyFileMultiFileOk() + { + $rawValue = [ + [ + 'tmp_name' => 'foo', + 'error' => \UPLOAD_ERR_OK, + ], + [ + 'tmp_name' => 'bar', + 'error' => \UPLOAD_ERR_OK, + ], + ]; + $this->assertFalse($this->input->isEmptyFile($rawValue)); + } + + /** + * Specific PsrFileInput::merge extras + */ + public function testPsrFileInputMerge() + { + $source = new PsrFileInput(); + $source->setAutoPrependUploadValidator(true); + + $target = $this->input; + $target->setAutoPrependUploadValidator(false); + + $return = $target->merge($source); + $this->assertSame($target, $return, 'merge() must return it self'); + + $this->assertEquals( + true, + $target->getAutoPrependUploadValidator(), + 'getAutoPrependUploadValidator() value not match' + ); + } + + public function isRequiredVsAllowEmptyVsContinueIfEmptyVsIsValidProvider() + { + $dataSets = parent::isRequiredVsAllowEmptyVsContinueIfEmptyVsIsValidProvider(); + + // PsrFileInput do not use NotEmpty validator so the only validator present in the chain is the custom one. + unset($dataSets['Required: T; AEmpty: F; CIEmpty: F; Validator: X, Value: Empty / tmp_name']); + unset($dataSets['Required: T; AEmpty: F; CIEmpty: F; Validator: X, Value: Empty / single']); + unset($dataSets['Required: T; AEmpty: F; CIEmpty: F; Validator: X, Value: Empty / multi']); + + return $dataSets; + } + + public function emptyValueProvider() + { + return [ + 'tmp_name' => [ + 'raw' => 'file', + 'filtered' => [ + 'tmp_name' => 'file', + 'name' => 'file', + 'size' => 0, + 'type' => '', + 'error' => UPLOAD_ERR_NO_FILE, + ], + ], + 'single' => [ + 'raw' => [ + 'tmp_name' => '', + 'error' => UPLOAD_ERR_NO_FILE, + ], + 'filtered' => [ + 'tmp_name' => '', + 'error' => UPLOAD_ERR_NO_FILE, + ], + ], + 'multi' => [ + 'raw' => [ + [ + 'tmp_name' => 'foo', + 'error' => UPLOAD_ERR_NO_FILE, + ], + ], + 'filtered' => [ + 'tmp_name' => 'foo', + 'error' => UPLOAD_ERR_NO_FILE, + ], + ], + ]; + } + + public function mixedValueProvider() + { + $fooUploadErrOk = [ + 'tmp_name' => 'foo', + 'error' => UPLOAD_ERR_OK, + ]; + + return [ + 'single' => [ + 'raw' => $fooUploadErrOk, + 'filtered' => $fooUploadErrOk, + ], + 'multi' => [ + 'raw' => [ + $fooUploadErrOk, + ], + 'filtered' => $fooUploadErrOk, + ], + ]; + } + + protected function getDummyValue($raw = true) + { + return ['tmp_name' => 'bar']; + } +} From efc2cfb699f8538fb6589dd3b55b022d4cad9b8f Mon Sep 17 00:00:00 2001 From: Sasha Alex Romanenko Date: Wed, 15 Aug 2018 19:16:21 -0400 Subject: [PATCH 06/16] Add PSR-7 compatible FileInput filter which uses UploadedFileInterface instead of $_FILES. Using own repository for zend-validator for tests to pass, as filter has tight dependency on validator. --- composer.json | 14 +- composer.lock | 349 +++++++++++++++++++++++++++----------- src/PsrFileInput.php | 54 +++--- test/PsrFileInputTest.php | 179 +++++++++---------- 4 files changed, 362 insertions(+), 234 deletions(-) diff --git a/composer.json b/composer.json index 0a78d1cb..655b73b1 100644 --- a/composer.json +++ b/composer.json @@ -28,16 +28,24 @@ "slack": "https://zendframework-slack.herokuapp.com", "forum": "https://discourse.zendframework.com/c/questions/expressive" }, + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/alextech/zend-validator" + } + ], "require": { "php": "^5.6 || ^7.0", + "psr/http-message": "^1.0", "zendframework/zend-filter": "^2.6", + "zendframework/zend-servicemanager": "^2.7.10 || ^3.3.1", "zendframework/zend-stdlib": "^2.7 || ^3.0", - "zendframework/zend-validator": "^2.10.1", - "zendframework/zend-servicemanager": "^2.7.10 || ^3.3.1" + "zendframework/zend-validator": "dev-feature/psr-uploadfile" }, "require-dev": { "phpunit/phpunit": "^5.7.23 || ^6.4.3", - "zendframework/zend-coding-standard": "~1.0.0" + "zendframework/zend-coding-standard": "~1.0.0", + "zendframework/zend-diactoros": "^1.8" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock index 58d3d53d..2aea4c9c 100644 --- a/composer.lock +++ b/composer.lock @@ -1,10 +1,10 @@ { "_readme": [ "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ac89a57a6a22a9ec7d3063ec7eecd574", + "content-hash": "1e0ea7801feacb13899898c866ec40c8", "packages": [ { "name": "container-interop/container-interop", @@ -86,31 +86,84 @@ ], "time": "2017-02-14T16:28:37+00:00" }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "time": "2016-08-06T14:39:51+00:00" + }, { "name": "zendframework/zend-filter", - "version": "2.7.2", + "version": "2.8.0", "source": { "type": "git", "url": "https://github.com/zendframework/zend-filter.git", - "reference": "b8d0ff872f126631bf63a932e33aa2d22d467175" + "reference": "7b997dbe79459f1652deccc8786d7407fb66caa9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-filter/zipball/b8d0ff872f126631bf63a932e33aa2d22d467175", - "reference": "b8d0ff872f126631bf63a932e33aa2d22d467175", + "url": "https://api.github.com/repos/zendframework/zend-filter/zipball/7b997dbe79459f1652deccc8786d7407fb66caa9", + "reference": "7b997dbe79459f1652deccc8786d7407fb66caa9", "shasum": "" }, "require": { - "php": "^5.5 || ^7.0", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "php": "^5.6 || ^7.0", + "zendframework/zend-stdlib": "^2.7.7 || ^3.1" + }, + "conflict": { + "zendframework/zend-validator": "<2.10.1" }, "require-dev": { - "pear/archive_tar": "^1.4", - "phpunit/phpunit": "^6.0.10 || ^5.7.17", + "pear/archive_tar": "^1.4.3", + "phpunit/phpunit": "^5.7.23 || ^6.4.3", "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-crypt": "^2.6 || ^3.0", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3", - "zendframework/zend-uri": "^2.5" + "zendframework/zend-crypt": "^3.2.1", + "zendframework/zend-servicemanager": "^2.7.8 || ^3.3", + "zendframework/zend-uri": "^2.6" }, "suggest": { "zendframework/zend-crypt": "Zend\\Crypt component, for encryption filters", @@ -121,8 +174,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.7-dev", - "dev-develop": "2.8-dev" + "dev-master": "2.8.x-dev", + "dev-develop": "2.9.x-dev" }, "zf": { "component": "Zend\\Filter", @@ -139,25 +192,25 @@ "BSD-3-Clause" ], "description": "provides a set of commonly needed data filters", - "homepage": "https://github.com/zendframework/zend-filter", "keywords": [ + "ZendFramework", "filter", - "zf2" + "zf" ], - "time": "2017-05-17T20:56:17+00:00" + "time": "2018-04-11T16:20:04+00:00" }, { "name": "zendframework/zend-servicemanager", - "version": "3.3.1", + "version": "3.3.2", "source": { "type": "git", "url": "https://github.com/zendframework/zend-servicemanager.git", - "reference": "0fa3d3cf588dde0850fff1efa60d44a7aa3c3ab7" + "reference": "9f35a104b8d4d3b32da5f4a3b6efc0dd62e5af42" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-servicemanager/zipball/0fa3d3cf588dde0850fff1efa60d44a7aa3c3ab7", - "reference": "0fa3d3cf588dde0850fff1efa60d44a7aa3c3ab7", + "url": "https://api.github.com/repos/zendframework/zend-servicemanager/zipball/9f35a104b8d4d3b32da5f4a3b6efc0dd62e5af42", + "reference": "9f35a104b8d4d3b32da5f4a3b6efc0dd62e5af42", "shasum": "" }, "require": { @@ -171,10 +224,10 @@ "psr/container-implementation": "^1.0" }, "require-dev": { - "mikey179/vfsstream": "^1.6", + "mikey179/vfsstream": "^1.6.5", "ocramius/proxy-manager": "^1.0 || ^2.0", - "phpbench/phpbench": "^0.10.0", - "phpunit/phpunit": "^5.7 || ^6.0.6", + "phpbench/phpbench": "^0.13.0", + "phpunit/phpunit": "^5.7.25 || ^6.4.4", "zendframework/zend-coding-standard": "~1.0.0" }, "suggest": { @@ -201,41 +254,46 @@ "license": [ "BSD-3-Clause" ], - "homepage": "https://github.com/zendframework/zend-servicemanager", + "description": "Factory-Driven Dependency Injection Container", "keywords": [ + "PSR-11", + "ZendFramework", + "dependency-injection", + "di", + "dic", "service-manager", "servicemanager", "zf" ], - "time": "2017-11-27T18:11:25+00:00" + "time": "2018-01-29T16:48:37+00:00" }, { "name": "zendframework/zend-stdlib", - "version": "3.1.0", + "version": "3.2.0", "source": { "type": "git", "url": "https://github.com/zendframework/zend-stdlib.git", - "reference": "debedcfc373a293f9250cc9aa03cf121428c8e78" + "reference": "cd164b4a18b5d1aeb69be2c26db035b5ed6925ae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-stdlib/zipball/debedcfc373a293f9250cc9aa03cf121428c8e78", - "reference": "debedcfc373a293f9250cc9aa03cf121428c8e78", + "url": "https://api.github.com/repos/zendframework/zend-stdlib/zipball/cd164b4a18b5d1aeb69be2c26db035b5ed6925ae", + "reference": "cd164b4a18b5d1aeb69be2c26db035b5ed6925ae", "shasum": "" }, "require": { "php": "^5.6 || ^7.0" }, "require-dev": { - "athletic/athletic": "~0.1", - "phpunit/phpunit": "~4.0", - "squizlabs/php_codesniffer": "^2.6.2" + "phpbench/phpbench": "^0.13", + "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2", + "zendframework/zend-coding-standard": "~1.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1-dev", - "dev-develop": "3.2-dev" + "dev-master": "3.2.x-dev", + "dev-develop": "3.3.x-dev" } }, "autoload": { @@ -247,25 +305,26 @@ "license": [ "BSD-3-Clause" ], - "homepage": "https://github.com/zendframework/zend-stdlib", + "description": "SPL extensions, array utilities, error handlers, and more", "keywords": [ + "ZendFramework", "stdlib", - "zf2" + "zf" ], - "time": "2016-09-13T14:38:50+00:00" + "time": "2018-04-30T13:50:40+00:00" }, { "name": "zendframework/zend-validator", - "version": "2.10.1", + "version": "dev-feature/psr-uploadfile", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-validator.git", - "reference": "010084ddbd33299bf51ea6f0e07f8f4e8bd832a8" + "url": "https://github.com/alextech/zend-validator.git", + "reference": "03e17ae6cae48d8955162b257952d6697ff9f848" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-validator/zipball/010084ddbd33299bf51ea6f0e07f8f4e8bd832a8", - "reference": "010084ddbd33299bf51ea6f0e07f8f4e8bd832a8", + "url": "https://api.github.com/repos/alextech/zend-validator/zipball/03e17ae6cae48d8955162b257952d6697ff9f848", + "reference": "03e17ae6cae48d8955162b257952d6697ff9f848", "shasum": "" }, "require": { @@ -275,10 +334,12 @@ }, "require-dev": { "phpunit/phpunit": "^6.0.8 || ^5.7.15", + "psr/http-message": "^1.0", "zendframework/zend-cache": "^2.6.1", "zendframework/zend-coding-standard": "~1.0.0", "zendframework/zend-config": "^2.6", "zendframework/zend-db": "^2.7", + "zendframework/zend-diactoros": "^1.8", "zendframework/zend-filter": "^2.6", "zendframework/zend-http": "^2.5.4", "zendframework/zend-i18n": "^2.6", @@ -288,6 +349,7 @@ "zendframework/zend-uri": "^2.5" }, "suggest": { + "psr/http-message": "Zend\\Filter component, required by PsrFileInput validator", "zendframework/zend-db": "Zend\\Db component, required by the (No)RecordExists validator", "zendframework/zend-filter": "Zend\\Filter component, required by the Digits validator", "zendframework/zend-i18n": "Zend\\I18n component to allow translation of validation error messages", @@ -300,8 +362,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.10-dev", - "dev-develop": "2.11-dev" + "dev-master": "2.10.x-dev", + "dev-develop": "2.11.x-dev" }, "zf": { "component": "Zend\\Validator", @@ -313,7 +375,29 @@ "Zend\\Validator\\": "src/" } }, - "notification-url": "https://packagist.org/downloads/", + "autoload-dev": { + "psr-4": { + "ZendTest\\Validator\\": "test/" + } + }, + "scripts": { + "check": [ + "@cs-check", + "@test" + ], + "cs-check": [ + "phpcs" + ], + "cs-fix": [ + "phpcbf" + ], + "test": [ + "phpunit --colors=always" + ], + "test-coverage": [ + "phpunit --colors=always --coverage-clover clover.xml" + ] + }, "license": [ "BSD-3-Clause" ], @@ -323,7 +407,10 @@ "validator", "zf2" ], - "time": "2017-08-22T14:19:23+00:00" + "support": { + "source": "https://github.com/alextech/zend-validator/tree/feature/psr-uploadfile" + }, + "time": "2018-08-15T22:59:51+00:00" } ], "packages-dev": [ @@ -383,25 +470,28 @@ }, { "name": "myclabs/deep-copy", - "version": "1.7.0", + "version": "1.8.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e" + "reference": "3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", - "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8", + "reference": "3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "php": "^7.1" + }, + "replace": { + "myclabs/deep-copy": "self.version" }, "require-dev": { "doctrine/collections": "^1.0", "doctrine/common": "^2.6", - "phpunit/phpunit": "^4.1" + "phpunit/phpunit": "^7.1" }, "type": "library", "autoload": { @@ -424,7 +514,7 @@ "object", "object graph" ], - "time": "2017-10-19T19:58:43+00:00" + "time": "2018-06-11T23:09:50+00:00" }, { "name": "phar-io/manifest", @@ -584,16 +674,16 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "4.2.0", + "version": "4.3.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "66465776cfc249844bde6d117abff1d22e06c2da" + "reference": "94fd0001232e47129dd3504189fa1c7225010d08" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/66465776cfc249844bde6d117abff1d22e06c2da", - "reference": "66465776cfc249844bde6d117abff1d22e06c2da", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94fd0001232e47129dd3504189fa1c7225010d08", + "reference": "94fd0001232e47129dd3504189fa1c7225010d08", "shasum": "" }, "require": { @@ -631,7 +721,7 @@ } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2017-11-27T17:38:31+00:00" + "time": "2017-11-30T07:14:17+00:00" }, { "name": "phpdocumentor/type-resolver", @@ -682,33 +772,33 @@ }, { "name": "phpspec/prophecy", - "version": "1.7.3", + "version": "1.8.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "e4ed002c67da8eceb0eb8ddb8b3847bb53c5c2bf" + "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/e4ed002c67da8eceb0eb8ddb8b3847bb53c5c2bf", - "reference": "e4ed002c67da8eceb0eb8ddb8b3847bb53c5c2bf", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4ba436b55987b4bf311cb7c6ba82aa528aac0a06", + "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06", "shasum": "" }, "require": { "doctrine/instantiator": "^1.0.2", "php": "^5.3|^7.0", "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", - "sebastian/comparator": "^1.1|^2.0", + "sebastian/comparator": "^1.1|^2.0|^3.0", "sebastian/recursion-context": "^1.0|^2.0|^3.0" }, "require-dev": { "phpspec/phpspec": "^2.5|^3.2", - "phpunit/phpunit": "^4.8.35 || ^5.7" + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.7.x-dev" + "dev-master": "1.8.x-dev" } }, "autoload": { @@ -741,20 +831,20 @@ "spy", "stub" ], - "time": "2017-11-24T13:59:53+00:00" + "time": "2018-08-05T17:53:17+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "5.3.0", + "version": "5.3.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "661f34d0bd3f1a7225ef491a70a020ad23a057a1" + "reference": "c89677919c5dd6d3b3852f230a663118762218ac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/661f34d0bd3f1a7225ef491a70a020ad23a057a1", - "reference": "661f34d0bd3f1a7225ef491a70a020ad23a057a1", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/c89677919c5dd6d3b3852f230a663118762218ac", + "reference": "c89677919c5dd6d3b3852f230a663118762218ac", "shasum": "" }, "require": { @@ -804,7 +894,7 @@ "testing", "xunit" ], - "time": "2017-12-06T09:29:45+00:00" + "time": "2018-04-06T15:36:58+00:00" }, { "name": "phpunit/php-file-iterator", @@ -994,16 +1084,16 @@ }, { "name": "phpunit/phpunit", - "version": "6.5.5", + "version": "6.5.11", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "83d27937a310f2984fd575686138597147bdc7df" + "reference": "7bab54cb366076023bbf457a2a0d513332cd40f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/83d27937a310f2984fd575686138597147bdc7df", - "reference": "83d27937a310f2984fd575686138597147bdc7df", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/7bab54cb366076023bbf457a2a0d513332cd40f2", + "reference": "7bab54cb366076023bbf457a2a0d513332cd40f2", "shasum": "" }, "require": { @@ -1021,7 +1111,7 @@ "phpunit/php-file-iterator": "^1.4.3", "phpunit/php-text-template": "^1.2.1", "phpunit/php-timer": "^1.0.9", - "phpunit/phpunit-mock-objects": "^5.0.5", + "phpunit/phpunit-mock-objects": "^5.0.9", "sebastian/comparator": "^2.1", "sebastian/diff": "^2.0", "sebastian/environment": "^3.1", @@ -1074,20 +1164,20 @@ "testing", "xunit" ], - "time": "2017-12-17T06:31:19+00:00" + "time": "2018-08-07T07:05:35+00:00" }, { "name": "phpunit/phpunit-mock-objects", - "version": "5.0.6", + "version": "5.0.10", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "33fd41a76e746b8fa96d00b49a23dadfa8334cdf" + "reference": "cd1cf05c553ecfec36b170070573e540b67d3f1f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/33fd41a76e746b8fa96d00b49a23dadfa8334cdf", - "reference": "33fd41a76e746b8fa96d00b49a23dadfa8334cdf", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/cd1cf05c553ecfec36b170070573e540b67d3f1f", + "reference": "cd1cf05c553ecfec36b170070573e540b67d3f1f", "shasum": "" }, "require": { @@ -1100,7 +1190,7 @@ "phpunit/phpunit": "<6.0" }, "require-dev": { - "phpunit/phpunit": "^6.5" + "phpunit/phpunit": "^6.5.11" }, "suggest": { "ext-soap": "*" @@ -1133,7 +1223,7 @@ "mock", "xunit" ], - "time": "2018-01-06T05:45:45+00:00" + "time": "2018-08-09T05:50:03+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -1182,21 +1272,21 @@ }, { "name": "sebastian/comparator", - "version": "2.1.2", + "version": "2.1.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "11c07feade1d65453e06df3b3b90171d6d982087" + "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/11c07feade1d65453e06df3b3b90171d6d982087", - "reference": "11c07feade1d65453e06df3b3b90171d6d982087", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/34369daee48eafb2651bea869b4b15d75ccc35f9", + "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9", "shasum": "" }, "require": { "php": "^7.0", - "sebastian/diff": "^2.0", + "sebastian/diff": "^2.0 || ^3.0", "sebastian/exporter": "^3.1" }, "require-dev": { @@ -1242,7 +1332,7 @@ "compare", "equality" ], - "time": "2018-01-12T06:34:42+00:00" + "time": "2018-02-01T13:46:46+00:00" }, { "name": "sebastian/diff", @@ -1814,16 +1904,16 @@ }, { "name": "webmozart/assert", - "version": "1.2.0", + "version": "1.3.0", "source": { "type": "git", "url": "https://github.com/webmozart/assert.git", - "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f" + "reference": "0df1908962e7a3071564e857d86874dad1ef204a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/2db61e59ff05fe5126d152bd0655c9ea113e550f", - "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f", + "url": "https://api.github.com/repos/webmozart/assert/zipball/0df1908962e7a3071564e857d86874dad1ef204a", + "reference": "0df1908962e7a3071564e857d86874dad1ef204a", "shasum": "" }, "require": { @@ -1860,7 +1950,7 @@ "check", "validate" ], - "time": "2016-11-23T20:04:58+00:00" + "time": "2018-01-29T19:49:41+00:00" }, { "name": "zendframework/zend-coding-standard", @@ -1890,11 +1980,76 @@ "zf" ], "time": "2016-11-09T21:30:43+00:00" + }, + { + "name": "zendframework/zend-diactoros", + "version": "1.8.5", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-diactoros.git", + "reference": "3e4edb822c942f37ade0d09579cfbab11e2fee87" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-diactoros/zipball/3e4edb822c942f37ade0d09579cfbab11e2fee87", + "reference": "3e4edb822c942f37ade0d09579cfbab11e2fee87", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0", + "psr/http-message": "^1.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-dom": "*", + "ext-libxml": "*", + "phpunit/phpunit": "^5.7.16 || ^6.0.8 || ^7.2.7", + "zendframework/zend-coding-standard": "~1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8.x-dev", + "dev-develop": "1.9.x-dev", + "dev-release-2.0": "2.0.x-dev" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php" + ], + "psr-4": { + "Zend\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://github.com/zendframework/zend-diactoros", + "keywords": [ + "http", + "psr", + "psr-7" + ], + "time": "2018-08-10T14:16:32+00:00" } ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": { + "zendframework/zend-validator": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { diff --git a/src/PsrFileInput.php b/src/PsrFileInput.php index 5e9cf390..09ab7be1 100644 --- a/src/PsrFileInput.php +++ b/src/PsrFileInput.php @@ -9,6 +9,8 @@ namespace Zend\InputFilter; +use Psr\Http\Message\UploadedFileInterface; +use Zend\Diactoros\UploadedFile; use Zend\Validator\File\UploadFile as UploadValidator; /** @@ -61,23 +63,23 @@ public function getAutoPrependUploadValidator() public function getValue() { $value = $this->value; - if ($this->isValid && is_array($value)) { + if ($this->isValid && $value instanceof UploadedFileInterface) { + // Single file input + // Run filters ~after~ validation, so that is_uploaded_file() // validation is not affected by filters. $filter = $this->getFilterChain(); - if (isset($value['tmp_name'])) { - // Single file input - $value = $filter->filter($value); - } else { - // Multi file input (multiple attribute set) - $newValue = []; - foreach ($value as $fileData) { - if (is_array($fileData) && isset($fileData['tmp_name'])) { - $newValue[] = $filter->filter($fileData); - } - } - $value = $newValue; + + $value = $filter->filter($value); + } elseif ($this->isValid && \is_array($value)) { + // Multi file input (multiple attribute set) + $filter = $this->getFilterChain(); + + $newValue = []; + foreach ($value as $fileData) { + $newValue[] = $filter->filter($fileData); } + $value = $newValue; } return $value; @@ -86,23 +88,19 @@ public function getValue() /** * Checks if the raw input value is an empty file input eg: no file was uploaded * - * @param $rawValue + * @param UploadedFile|array $rawValue * @return bool */ public function isEmptyFile($rawValue) { - if (! is_array($rawValue)) { - return true; + if (\is_array($rawValue) && $rawValue[0] instanceof UploadedFile) { + return $this->isEmptyFile($rawValue[0]); } - if (isset($rawValue['error']) && $rawValue['error'] === UPLOAD_ERR_NO_FILE) { + if ($rawValue instanceof UploadedFile && $rawValue->getError() === UPLOAD_ERR_NO_FILE) { return true; } - if (count($rawValue) === 1 && isset($rawValue[0])) { - return $this->isEmptyFile($rawValue[0]); - } - return false; } @@ -142,20 +140,10 @@ public function isValid($context = null) $validator = $this->getValidatorChain(); //$value = $this->getValue(); // Do not run the filters yet for File uploads (see getValue()) - if (! is_array($rawValue)) { - // This can happen in an AJAX POST, where the input comes across as a string - $rawValue = [ - 'tmp_name' => $rawValue, - 'name' => $rawValue, - 'size' => 0, - 'type' => '', - 'error' => UPLOAD_ERR_NO_FILE, - ]; - } - if (is_array($rawValue) && isset($rawValue['tmp_name'])) { + if ($rawValue instanceof UploadedFileInterface) { // Single file input $this->isValid = $validator->isValid($rawValue, $context); - } elseif (is_array($rawValue) && isset($rawValue[0]['tmp_name'])) { + } elseif (\is_array($rawValue) && $rawValue[0] instanceof UploadedFileInterface) { // Multi file input (multiple attribute set) $this->isValid = true; foreach ($rawValue as $value) { diff --git a/test/PsrFileInputTest.php b/test/PsrFileInputTest.php index 64c72338..db249c69 100644 --- a/test/PsrFileInputTest.php +++ b/test/PsrFileInputTest.php @@ -10,11 +10,12 @@ namespace ZendTest\InputFilter; use PHPUnit_Framework_MockObject_MockObject as MockObject; +use Zend\Diactoros\UploadedFile; use Zend\InputFilter\PsrFileInput; use Zend\Validator; /** - * @covers Zend\InputFilter\PsrFileInput + * @covers \Zend\InputFilter\PsrFileInput */ class PsrFileInputTest extends InputTest { @@ -35,10 +36,10 @@ public function testRetrievingValueFiltersTheValue() public function testRetrievingValueFiltersTheValueOnlyAfterValidating() { - $value = ['tmp_name' => 'bar']; + $value = new UploadedFile('bar', 1, 0); $this->input->setValue($value); - $newValue = ['tmp_name' => 'foo']; + $newValue = new UploadedFile('foo', 1, 0); $this->input->setFilterChain($this->createFilterChainMock([[$value, $newValue]])); $this->assertEquals($value, $this->input->getValue()); @@ -52,13 +53,13 @@ public function testRetrievingValueFiltersTheValueOnlyAfterValidating() public function testCanFilterArrayOfMultiFileData() { $values = [ - ['tmp_name' => 'foo'], - ['tmp_name' => 'bar'], - ['tmp_name' => 'baz'], + new UploadedFile('foo', 1, 0), + new UploadedFile('bar', 1, 0), + new UploadedFile('baz', 1, 0), ]; $this->input->setValue($values); - $newValue = ['tmp_name' => 'new']; + $newValue = new UploadedFile('new', 1, 0); $filteredValue = [$newValue, $newValue, $newValue]; $this->input->setFilterChain($this->createFilterChainMock([ [$values[0], $newValue], @@ -79,10 +80,10 @@ public function testCanFilterArrayOfMultiFileData() public function testCanRetrieveRawValue() { - $value = ['tmp_name' => 'bar']; + $value = new UploadedFile('bar', 1, 0); $this->input->setValue($value); - $newValue = ['tmp_name' => 'new']; + $newValue = new UploadedFile('new', 1, 0); $this->input->setFilterChain($this->createFilterChainMock([[$value, $newValue]])); $this->assertEquals($value, $this->input->getRawValue()); @@ -95,15 +96,15 @@ public function testValidationOperatesOnFilteredValue() public function testValidationOperatesBeforeFiltering() { - $badValue = [ - 'tmp_name' => ' ' . __FILE__ . ' ', - 'name' => 'foo', - 'size' => 1, - 'error' => 0, - ]; + $badValue = new UploadedFile( + ' ' . __FILE__ . ' ', + 1, + 0, + 'foo' + ); $this->input->setValue($badValue); - $filteredValue = ['tmp_name' => 'new']; + $filteredValue = new UploadedFile('new', 1, 0); $this->input->setFilterChain($this->createFilterChainMock([[$badValue, $filteredValue]])); $this->input->setValidatorChain($this->createValidatorChainMock([[$badValue, null, false]])); @@ -122,18 +123,18 @@ public function testUploadValidatorIsAddedWhenIsValidIsCalled() $this->input->setAutoPrependUploadValidator(true); $this->assertTrue($this->input->getAutoPrependUploadValidator()); $this->assertTrue($this->input->isRequired()); - $this->input->setValue([ - 'tmp_name' => __FILE__, - 'name' => 'foo', - 'size' => 1, - 'error' => 0, - ]); + $this->input->setValue(new UploadedFile( + __FILE__, + 1, + 0, + 'foo' + )); $validatorChain = $this->input->getValidatorChain(); - $this->assertEquals(0, count($validatorChain->getValidators())); + $this->assertCount(0, $validatorChain->getValidators()); $this->assertFalse($this->input->isValid()); $validators = $validatorChain->getValidators(); - $this->assertEquals(1, count($validators)); + $this->assertCount(1, $validators); $this->assertInstanceOf(Validator\File\UploadFile::class, $validators[0]['instance']); } @@ -141,7 +142,7 @@ public function testUploadValidatorIsNotAddedWhenIsValidIsCalled() { $this->assertFalse($this->input->getAutoPrependUploadValidator()); $this->assertTrue($this->input->isRequired()); - $this->input->setValue(['tmp_name' => 'bar']); + $this->input->setValue(new UploadedFile('bar', 1, 0)); $validatorChain = $this->input->getValidatorChain(); $this->assertEquals(0, count($validatorChain->getValidators())); @@ -157,7 +158,7 @@ public function testRequiredUploadValidatorValidatorNotAddedWhenOneExists() $this->input->setAutoPrependUploadValidator(true); $this->assertTrue($this->input->getAutoPrependUploadValidator()); $this->assertTrue($this->input->isRequired()); - $this->input->setValue(['tmp_name' => 'bar']); + $this->input->setValue(new UploadedFile('bar', 1, 0)); /** @var Validator\File\UploadFile|MockObject $uploadMock */ $uploadMock = $this->getMockBuilder(Validator\File\UploadFile::class) @@ -179,24 +180,6 @@ public function testRequiredUploadValidatorValidatorNotAddedWhenOneExists() $this->assertEquals($uploadMock, $validators[0]['instance']); } - public function testValidationsRunWithoutFileArrayDueToAjaxPost() - { - $this->input->setAutoPrependUploadValidator(true); - $this->assertTrue($this->input->getAutoPrependUploadValidator()); - $this->assertTrue($this->input->isRequired()); - $this->input->setValue(''); - - $expectedNormalizedValue = [ - 'tmp_name' => '', - 'name' => '', - 'size' => 0, - 'type' => '', - 'error' => UPLOAD_ERR_NO_FILE, - ]; - $this->input->setValidatorChain($this->createValidatorChainMock([[$expectedNormalizedValue, null, false]])); - $this->assertFalse($this->input->isValid()); - } - public function testNotEmptyValidatorAddedWhenIsValidIsCalled($value = null) { $this->markTestSkipped('Test is not enabled in PsrFileInputTest'); @@ -225,50 +208,49 @@ public function testFallbackValueVsIsValidRulesWhenValueNotSet( $this->markTestSkipped('Input::setFallbackValue is not implemented on PsrFileInput'); } - public function testIsEmptyFileNotArray() - { - $rawValue = 'file'; - $this->assertTrue($this->input->isEmptyFile($rawValue)); - } - public function testIsEmptyFileUploadNoFile() { - $rawValue = [ - 'tmp_name' => '', - 'error' => \UPLOAD_ERR_NO_FILE, - ]; + $rawValue = new UploadedFile( + '', + 0, + UPLOAD_ERR_NO_FILE + ); $this->assertTrue($this->input->isEmptyFile($rawValue)); } public function testIsEmptyFileOk() { - $rawValue = [ - 'tmp_name' => 'name', - 'error' => \UPLOAD_ERR_OK, - ]; + $rawValue = new UploadedFile( + 'name', + 1, + UPLOAD_ERR_OK + ); $this->assertFalse($this->input->isEmptyFile($rawValue)); } public function testIsEmptyMultiFileUploadNoFile() { - $rawValue = [[ - 'tmp_name' => 'foo', - 'error' => \UPLOAD_ERR_NO_FILE, - ]]; + $rawValue = [new UploadedFile( + 'foo', + 0, + UPLOAD_ERR_NO_FILE + )]; $this->assertTrue($this->input->isEmptyFile($rawValue)); } public function testIsEmptyFileMultiFileOk() { $rawValue = [ - [ - 'tmp_name' => 'foo', - 'error' => \UPLOAD_ERR_OK, - ], - [ - 'tmp_name' => 'bar', - 'error' => \UPLOAD_ERR_OK, - ], + new UploadedFile( + 'foo', + 1, + UPLOAD_ERR_OK + ), + new UploadedFile( + 'bar', + 1, + UPLOAD_ERR_OK + ), ]; $this->assertFalse($this->input->isEmptyFile($rawValue)); } @@ -309,47 +291,42 @@ public function isRequiredVsAllowEmptyVsContinueIfEmptyVsIsValidProvider() public function emptyValueProvider() { return [ - 'tmp_name' => [ - 'raw' => 'file', - 'filtered' => [ - 'tmp_name' => 'file', - 'name' => 'file', - 'size' => 0, - 'type' => '', - 'error' => UPLOAD_ERR_NO_FILE, - ], - ], 'single' => [ - 'raw' => [ - 'tmp_name' => '', - 'error' => UPLOAD_ERR_NO_FILE, - ], - 'filtered' => [ - 'tmp_name' => '', - 'error' => UPLOAD_ERR_NO_FILE, - ], + 'raw' => new UploadedFile( + '', + 0, + UPLOAD_ERR_NO_FILE + ), + 'filtered' => new UploadedFile( + '', + 0, + UPLOAD_ERR_NO_FILE + ), ], 'multi' => [ 'raw' => [ - [ - 'tmp_name' => 'foo', - 'error' => UPLOAD_ERR_NO_FILE, - ], - ], - 'filtered' => [ - 'tmp_name' => 'foo', - 'error' => UPLOAD_ERR_NO_FILE, + new UploadedFile( + 'foo', + 0, + UPLOAD_ERR_NO_FILE + ), ], + 'filtered' => new UploadedFile( + 'foo', + 0, + UPLOAD_ERR_NO_FILE + ), ], ]; } public function mixedValueProvider() { - $fooUploadErrOk = [ - 'tmp_name' => 'foo', - 'error' => UPLOAD_ERR_OK, - ]; + $fooUploadErrOk = new UploadedFile( + 'foo', + 1, + UPLOAD_ERR_OK + ); return [ 'single' => [ @@ -367,6 +344,6 @@ public function mixedValueProvider() protected function getDummyValue($raw = true) { - return ['tmp_name' => 'bar']; + return new UploadedFile('bar', 0, 0); } } From 58fd44c39c1e1cd718531d464b71b058ee1c25d0 Mon Sep 17 00:00:00 2001 From: Sasha Alex Romanenko Date: Wed, 15 Aug 2018 19:59:11 -0400 Subject: [PATCH 07/16] Update doc comment to match PSR names. --- src/PsrFileInput.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PsrFileInput.php b/src/PsrFileInput.php index 09ab7be1..8e08df61 100644 --- a/src/PsrFileInput.php +++ b/src/PsrFileInput.php @@ -14,11 +14,11 @@ use Zend\Validator\File\UploadFile as UploadValidator; /** - * FileInput is a special Input type for handling uploaded files. + * PsrFileInput is a special Input type for handling uploaded files through PSR-7 middlware. * * It differs from Input in a few ways: * - * 1. It expects the raw value to be in the $_FILES array format. + * 1. It expects the raw value to be instance of UploadedFileInterface object type. * * 2. The validators are run **before** the filters (the opposite behavior of Input). * This is so is_uploaded_file() validation can be run prior to any filters that From dca3bab1f180c7b5816c9f2dbf3e42207be18c96 Mon Sep 17 00:00:00 2001 From: Sasha Alex Romanenko Date: Thu, 16 Aug 2018 14:20:40 -0400 Subject: [PATCH 08/16] Current implementation of decorators have state, so cannot simply create a bridge to appropriate PSR or HTTP filter implementation. Change to decorator style and move detection of value type to public facing FileInput. --- src/FileInput.php | 147 +++++-------- src/FileInputDecoratorInterface.php | 44 ++++ src/HttpServerFileInputDecorator.php | 150 +++++++++++++ src/PsrFileInput.php | 208 ------------------ src/PsrFileInputDecorator.php | 132 +++++++++++ ...p => HttpServerFileInputDecoratorTest.php} | 7 +- ...Test.php => PsrFileInputDecoratorTest.php} | 17 +- 7 files changed, 399 insertions(+), 306 deletions(-) create mode 100644 src/FileInputDecoratorInterface.php create mode 100644 src/HttpServerFileInputDecorator.php delete mode 100644 src/PsrFileInput.php create mode 100644 src/PsrFileInputDecorator.php rename test/{FileInputTest.php => HttpServerFileInputDecoratorTest.php} (98%) rename test/{PsrFileInputTest.php => PsrFileInputDecoratorTest.php} (96%) diff --git a/src/FileInput.php b/src/FileInput.php index 08d6a7f3..705d9de9 100644 --- a/src/FileInput.php +++ b/src/FileInput.php @@ -9,7 +9,8 @@ namespace Zend\InputFilter; -use Zend\Validator\File\UploadFile as UploadValidator; +use Psr\Http\Message\UploadedFileInterface; +use Zend\Diactoros\UploadedFile; /** * FileInput is a special Input type for handling uploaded files. @@ -37,8 +38,45 @@ class FileInput extends Input */ protected $autoPrependUploadValidator = true; + /** @var FileInputDecoratorInterface */ + private $filterImpl; + + /** + * @param array|UploadedFile $value + * + * @return Input + */ + public function setValue($value) + { + if(\is_array($value)) { + if (isset($value[0]) && $value[0] instanceof UploadedFileInterface) { + $this->filterImpl = new PsrFileInputDecorator($this); + } else { + $this->filterImpl = new HttpServerFileInputDecorator($this); + } + } elseif ($value instanceof UploadedFileInterface) { + $this->filterImpl = new PsrFileInputDecorator($this); + } else { + // ajax case + $this->filterImpl = new HttpServerFileInputDecorator($this); + } + + + parent::setValue($value); + + return $this; + } + + public function resetValue() + { + $this->filterImpl = null; + return parent::resetValue(); + } + + /** * @param bool $value Enable/Disable automatically prepending an Upload validator + * * @return FileInput */ public function setAutoPrependUploadValidator($value) @@ -60,27 +98,10 @@ public function getAutoPrependUploadValidator() */ public function getValue() { - $value = $this->value; - if ($this->isValid && is_array($value)) { - // Run filters ~after~ validation, so that is_uploaded_file() - // validation is not affected by filters. - $filter = $this->getFilterChain(); - if (isset($value['tmp_name'])) { - // Single file input - $value = $filter->filter($value); - } else { - // Multi file input (multiple attribute set) - $newValue = []; - foreach ($value as $fileData) { - if (is_array($fileData) && isset($fileData['tmp_name'])) { - $newValue[] = $filter->filter($fileData); - } - } - $value = $newValue; - } + if ($this->filterImpl === null) { + return $this->value; } - - return $value; + return $this->filterImpl->getValue(); } /** @@ -91,19 +112,19 @@ public function getValue() */ public function isEmptyFile($rawValue) { - if (! is_array($rawValue)) { - return true; - } + if (\is_array($rawValue)) { + if (isset($rawValue[0]) && $rawValue[0] instanceof UploadedFileInterface) { + return PsrFileInputDecorator::isEmptyFileDecorator($rawValue); + } - if (isset($rawValue['error']) && $rawValue['error'] === UPLOAD_ERR_NO_FILE) { - return true; + return HttpServerFileInputDecorator::isEmptyFileDecorator($rawValue); } - if (count($rawValue) === 1 && isset($rawValue[0])) { - return $this->isEmptyFile($rawValue[0]); + if($rawValue instanceof UploadedFileInterface) { + return PsrFileInputDecorator::isEmptyFileDecorator($rawValue); } - return false; + return true; } /** @@ -138,58 +159,21 @@ public function isValid($context = null) return true; } - $this->injectUploadValidator(); - $validator = $this->getValidatorChain(); - //$value = $this->getValue(); // Do not run the filters yet for File uploads (see getValue()) - - if (! is_array($rawValue)) { - // This can happen in an AJAX POST, where the input comes across as a string - $rawValue = [ - 'tmp_name' => $rawValue, - 'name' => $rawValue, - 'size' => 0, - 'type' => '', - 'error' => UPLOAD_ERR_NO_FILE, - ]; - } - if (is_array($rawValue) && isset($rawValue['tmp_name'])) { - // Single file input - $this->isValid = $validator->isValid($rawValue, $context); - } elseif (is_array($rawValue) && isset($rawValue[0]['tmp_name'])) { - // Multi file input (multiple attribute set) - $this->isValid = true; - foreach ($rawValue as $value) { - if (! $validator->isValid($value, $context)) { - $this->isValid = false; - break; // Do not continue processing files if validation fails - } - } - } - - return $this->isValid; + return $this->filterImpl->isValid($context); } /** - * @return void + * @param InputInterface $input + * + * @return FileInput */ - protected function injectUploadValidator() + public function merge(InputInterface $input) { - if (! $this->autoPrependUploadValidator) { - return; - } - $chain = $this->getValidatorChain(); - - // Check if Upload validator is already first in chain - $validators = $chain->getValidators(); - if (isset($validators[0]['instance']) - && $validators[0]['instance'] instanceof UploadValidator - ) { - $this->autoPrependUploadValidator = false; - return; + parent::merge($input); + if ($input instanceof FileInput) { + $this->setAutoPrependUploadValidator($input->getAutoPrependUploadValidator()); } - - $chain->prependByName('fileuploadfile', [], true); - $this->autoPrependUploadValidator = false; + return $this; } /** @@ -204,17 +188,4 @@ protected function injectNotEmptyValidator() { $this->notEmptyValidator = true; } - - /** - * @param InputInterface $input - * @return FileInput - */ - public function merge(InputInterface $input) - { - parent::merge($input); - if ($input instanceof FileInput) { - $this->setAutoPrependUploadValidator($input->getAutoPrependUploadValidator()); - } - return $this; - } } diff --git a/src/FileInputDecoratorInterface.php b/src/FileInputDecoratorInterface.php new file mode 100644 index 00000000..944a1aab --- /dev/null +++ b/src/FileInputDecoratorInterface.php @@ -0,0 +1,44 @@ +subject = $subject; + } + + /** + * @return mixed + */ + public function getValue() + { + $value = $this->subject->value; + if ($this->subject->isValid && is_array($value)) { + // Run filters ~after~ validation, so that is_uploaded_file() + // validation is not affected by filters. + $filter = $this->subject->getFilterChain(); + if (isset($value['tmp_name'])) { + // Single file input + $value = $filter->filter($value); + } else { + // Multi file input (multiple attribute set) + $newValue = []; + foreach ($value as $fileData) { + if (is_array($fileData) && isset($fileData['tmp_name'])) { + $newValue[] = $filter->filter($fileData); + } + } + $value = $newValue; + } + } + + return $value; + } + + /** + * Checks if the raw input value is an empty file input eg: no file was uploaded + * + * @param $rawValue + * @return bool + */ + public static function isEmptyFileDecorator($rawValue) + { + if (! is_array($rawValue)) { + return true; + } + + if (isset($rawValue['error']) && $rawValue['error'] === UPLOAD_ERR_NO_FILE) { + return true; + } + + if (count($rawValue) === 1 && isset($rawValue[0])) { + return self::isEmptyFileDecorator($rawValue[0]); + } + + return false; + } + + /** + * @param mixed $context Extra "context" to provide the validator + * @return bool + */ + public function isValid($context = null) + { + $rawValue = $this->subject->getRawValue(); + $validator = $this->subject->getValidatorChain(); + $this->injectUploadValidator(); + + //$value = $this->getValue(); // Do not run the filters yet for File uploads (see getValue()) + + if (! is_array($rawValue)) { + // This can happen in an AJAX POST, where the input comes across as a string + $rawValue = [ + 'tmp_name' => $rawValue, + 'name' => $rawValue, + 'size' => 0, + 'type' => '', + 'error' => UPLOAD_ERR_NO_FILE, + ]; + } + if (is_array($rawValue) && isset($rawValue['tmp_name'])) { + // Single file input + $this->subject->isValid = $validator->isValid($rawValue, $context); + } elseif (is_array($rawValue) && isset($rawValue[0]['tmp_name'])) { + // Multi file input (multiple attribute set) + $this->subject->isValid = true; + foreach ($rawValue as $value) { + if (! $validator->isValid($value, $context)) { + $this->subject->isValid = false; + break; // Do not continue processing files if validation fails + } + } + } + + return $this->subject->isValid; + } + + /** + * @return void + */ + protected function injectUploadValidator() + { + if (! $this->subject->autoPrependUploadValidator) { + return; + } + $chain = $this->subject->getValidatorChain(); + + // Check if Upload validator is already first in chain + $validators = $chain->getValidators(); + if (isset($validators[0]['instance']) + && $validators[0]['instance'] instanceof UploadValidator + ) { + $this->subject->autoPrependUploadValidator = false; + return; + } + + $chain->prependByName('fileuploadfile', [], true); + $this->subject->autoPrependUploadValidator = false; + } +} diff --git a/src/PsrFileInput.php b/src/PsrFileInput.php deleted file mode 100644 index 8e08df61..00000000 --- a/src/PsrFileInput.php +++ /dev/null @@ -1,208 +0,0 @@ -autoPrependUploadValidator = $value; - return $this; - } - - /** - * @return bool - */ - public function getAutoPrependUploadValidator() - { - return $this->autoPrependUploadValidator; - } - - /** - * @return mixed - */ - public function getValue() - { - $value = $this->value; - if ($this->isValid && $value instanceof UploadedFileInterface) { - // Single file input - - // Run filters ~after~ validation, so that is_uploaded_file() - // validation is not affected by filters. - $filter = $this->getFilterChain(); - - $value = $filter->filter($value); - } elseif ($this->isValid && \is_array($value)) { - // Multi file input (multiple attribute set) - $filter = $this->getFilterChain(); - - $newValue = []; - foreach ($value as $fileData) { - $newValue[] = $filter->filter($fileData); - } - $value = $newValue; - } - - return $value; - } - - /** - * Checks if the raw input value is an empty file input eg: no file was uploaded - * - * @param UploadedFile|array $rawValue - * @return bool - */ - public function isEmptyFile($rawValue) - { - if (\is_array($rawValue) && $rawValue[0] instanceof UploadedFile) { - return $this->isEmptyFile($rawValue[0]); - } - - if ($rawValue instanceof UploadedFile && $rawValue->getError() === UPLOAD_ERR_NO_FILE) { - return true; - } - - return false; - } - - /** - * @param mixed $context Extra "context" to provide the validator - * @return bool - */ - public function isValid($context = null) - { - $rawValue = $this->getRawValue(); - $hasValue = $this->hasValue(); - $empty = $this->isEmptyFile($rawValue); - $required = $this->isRequired(); - $allowEmpty = $this->allowEmpty(); - $continueIfEmpty = $this->continueIfEmpty(); - - if (! $hasValue && ! $required) { - return true; - } - - if (! $hasValue && $required && ! $this->hasFallback()) { - if ($this->errorMessage === null) { - $this->errorMessage = $this->prepareRequiredValidationFailureMessage(); - } - return false; - } - - if ($empty && ! $required && ! $continueIfEmpty) { - return true; - } - - if ($empty && $allowEmpty && ! $continueIfEmpty) { - return true; - } - - $this->injectUploadValidator(); - $validator = $this->getValidatorChain(); - //$value = $this->getValue(); // Do not run the filters yet for File uploads (see getValue()) - - if ($rawValue instanceof UploadedFileInterface) { - // Single file input - $this->isValid = $validator->isValid($rawValue, $context); - } elseif (\is_array($rawValue) && $rawValue[0] instanceof UploadedFileInterface) { - // Multi file input (multiple attribute set) - $this->isValid = true; - foreach ($rawValue as $value) { - if (! $validator->isValid($value, $context)) { - $this->isValid = false; - break; // Do not continue processing files if validation fails - } - } - } - - return $this->isValid; - } - - /** - * @return void - */ - protected function injectUploadValidator() - { - if (! $this->autoPrependUploadValidator) { - return; - } - $chain = $this->getValidatorChain(); - - // Check if Upload validator is already first in chain - $validators = $chain->getValidators(); - if (isset($validators[0]['instance']) - && $validators[0]['instance'] instanceof UploadValidator - ) { - $this->autoPrependUploadValidator = false; - return; - } - - $chain->prependByName('fileuploadfile', [], true); - $this->autoPrependUploadValidator = false; - } - - /** - * @deprecated 2.4.8 See note on parent class. Removal does not affect this class. - * - * No-op, NotEmpty validator does not apply for PsrFileInputs. - * See also: BaseInputFilter::isValid() - * - * @return void - */ - protected function injectNotEmptyValidator() - { - $this->notEmptyValidator = true; - } - - /** - * @param InputInterface $input - * @return PsrFileInput - */ - public function merge(InputInterface $input) - { - parent::merge($input); - if ($input instanceof PsrFileInput) { - $this->setAutoPrependUploadValidator($input->getAutoPrependUploadValidator()); - } - return $this; - } -} diff --git a/src/PsrFileInputDecorator.php b/src/PsrFileInputDecorator.php new file mode 100644 index 00000000..72a0827f --- /dev/null +++ b/src/PsrFileInputDecorator.php @@ -0,0 +1,132 @@ +subject = $subject; + } + + /** + * @return mixed + */ + public function getValue() + { + $value = $this->subject->value; + if ($this->subject->isValid && $value instanceof UploadedFileInterface) { + // Single file input + + // Run filters ~after~ validation, so that is_uploaded_file() + // validation is not affected by filters. + $filter = $this->subject->getFilterChain(); + + $value = $filter->filter($value); + } elseif ($this->subject->isValid && \is_array($value)) { + // Multi file input (multiple attribute set) + $filter = $this->subject->getFilterChain(); + + $newValue = []; + foreach ($value as $fileData) { + $newValue[] = $filter->filter($fileData); + } + $value = $newValue; + } + + return $value; + } + + /** + * Checks if the raw input value is an empty file input eg: no file was uploaded + * + * @param UploadedFileInterface|array $rawValue + * @return bool + */ + public static function isEmptyFileDecorator($rawValue) + { + if (\is_array($rawValue)) { + return self::isEmptyFileDecorator($rawValue[0]); + } + + return $rawValue->getError() === UPLOAD_ERR_NO_FILE; + + } + + /** + * @param mixed $context Extra "context" to provide the validator + * @return bool + */ + public function isValid($context = null) + { + $rawValue = $this->subject->getRawValue(); + $validator = $this->subject->getValidatorChain(); + $this->injectUploadValidator(); + + //$value = $this->getValue(); // Do not run the filters yet for File uploads (see getValue()) + + if ($rawValue instanceof UploadedFileInterface) { + // Single file input + $this->subject->isValid = $validator->isValid($rawValue, $context); + } elseif (\is_array($rawValue)) { + // Multi file input (multiple attribute set) + $this->subject->isValid = true; + foreach ($rawValue as $value) { + if (! $validator->isValid($value, $context)) { + $this->subject->isValid = false; + break; // Do not continue processing files if validation fails + } + } + } + + return $this->subject->isValid; + } + + /** + * @return void + */ + protected function injectUploadValidator() + { + if (! $this->subject->autoPrependUploadValidator) { + return; + } + $chain = $this->subject->getValidatorChain(); + + // Check if Upload validator is already first in chain + $validators = $chain->getValidators(); + if (isset($validators[0]['instance'])) { + $this->subject->autoPrependUploadValidator = false; + return; + } + + $chain->prependByName('fileuploadfile', [], true); + $this->subject->autoPrependUploadValidator = false; + } +} diff --git a/test/FileInputTest.php b/test/HttpServerFileInputDecoratorTest.php similarity index 98% rename from test/FileInputTest.php rename to test/HttpServerFileInputDecoratorTest.php index be0d14ad..35a3cb10 100644 --- a/test/FileInputTest.php +++ b/test/HttpServerFileInputDecoratorTest.php @@ -11,14 +11,15 @@ use PHPUnit_Framework_MockObject_MockObject as MockObject; use Zend\InputFilter\FileInput; +use Zend\InputFilter\HttpServerFileInputDecorator; use Zend\Validator; /** - * @covers Zend\InputFilter\FileInput + * @covers \Zend\InputFilter\FileInput */ -class FileInputTest extends InputTest +class HttpServerFileInputDecoratorTest extends InputTest { - /** @var FileInput */ + /** @var HttpServerFileInputDecorator */ protected $input; public function setUp() diff --git a/test/PsrFileInputTest.php b/test/PsrFileInputDecoratorTest.php similarity index 96% rename from test/PsrFileInputTest.php rename to test/PsrFileInputDecoratorTest.php index db249c69..3a98e113 100644 --- a/test/PsrFileInputTest.php +++ b/test/PsrFileInputDecoratorTest.php @@ -11,20 +11,23 @@ use PHPUnit_Framework_MockObject_MockObject as MockObject; use Zend\Diactoros\UploadedFile; -use Zend\InputFilter\PsrFileInput; +use Zend\InputFilter\FileInput; +use Zend\InputFilter\PsrFileInputDecorator; use Zend\Validator; /** - * @covers \Zend\InputFilter\PsrFileInput + * @covers \Zend\InputFilter\PsrFileInputDecorator + * @covers \Zend\InputFilter\FileInput */ -class PsrFileInputTest extends InputTest +class PsrFileInputDecoratorTest extends InputTest { - /** @var PsrFileInput */ + /** @var PsrFileInputDecorator */ protected $input; public function setUp() { - $this->input = new PsrFileInput('foo'); + + $this->input = new FileInput('foo'); // Upload validator does not work in CLI test environment, disable $this->input->setAutoPrependUploadValidator(false); } @@ -114,7 +117,7 @@ public function testValidationOperatesBeforeFiltering() public function testAutoPrependUploadValidatorIsOnByDefault() { - $input = new PsrFileInput('foo'); + $input = new FileInput('foo'); $this->assertTrue($input->getAutoPrependUploadValidator()); } @@ -260,7 +263,7 @@ public function testIsEmptyFileMultiFileOk() */ public function testPsrFileInputMerge() { - $source = new PsrFileInput(); + $source = new FileInput(); $source->setAutoPrependUploadValidator(true); $target = $this->input; From f9ce7b94dadd8b442e2eead0f719bdf2c8ea1678 Mon Sep 17 00:00:00 2001 From: Sasha Alex Romanenko Date: Thu, 16 Aug 2018 14:31:58 -0400 Subject: [PATCH 09/16] No need to check for UploadedFileInterface again in Psr decorator as the FileInput entry point does this already. --- src/PsrFileInputDecorator.php | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/PsrFileInputDecorator.php b/src/PsrFileInputDecorator.php index 72a0827f..042cf247 100644 --- a/src/PsrFileInputDecorator.php +++ b/src/PsrFileInputDecorator.php @@ -37,20 +37,19 @@ public function __construct(FileInput $subject) } /** - * @return mixed + * @return UploadedFileInterface|array */ public function getValue() { $value = $this->subject->value; - if ($this->subject->isValid && $value instanceof UploadedFileInterface) { - // Single file input - // Run filters ~after~ validation, so that is_uploaded_file() - // validation is not affected by filters. - $filter = $this->subject->getFilterChain(); + // Run filters ~after~ validation, so that is_uploaded_file() + // validation is not affected by filters. + if (! $this->subject->isValid) { + return $value; + } - $value = $filter->filter($value); - } elseif ($this->subject->isValid && \is_array($value)) { + if (\is_array($value)) { // Multi file input (multiple attribute set) $filter = $this->subject->getFilterChain(); @@ -59,6 +58,11 @@ public function getValue() $newValue[] = $filter->filter($fileData); } $value = $newValue; + } else { + // Single file input + + $filter = $this->subject->getFilterChain(); + $value = $filter->filter($value); } return $value; @@ -92,10 +96,7 @@ public function isValid($context = null) //$value = $this->getValue(); // Do not run the filters yet for File uploads (see getValue()) - if ($rawValue instanceof UploadedFileInterface) { - // Single file input - $this->subject->isValid = $validator->isValid($rawValue, $context); - } elseif (\is_array($rawValue)) { + if (\is_array($rawValue)) { // Multi file input (multiple attribute set) $this->subject->isValid = true; foreach ($rawValue as $value) { @@ -104,6 +105,9 @@ public function isValid($context = null) break; // Do not continue processing files if validation fails } } + } else { + // Single file input + $this->subject->isValid = $validator->isValid($rawValue, $context); } return $this->subject->isValid; From ddb7d06fb1d38783b1ff0d5505edd9d5a3ee34a2 Mon Sep 17 00:00:00 2001 From: Sasha Alex Romanenko Date: Thu, 16 Aug 2018 17:22:15 -0400 Subject: [PATCH 10/16] temporary allow using psr upload fork of z\filter repository --- composer.json | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 655b73b1..e214db71 100644 --- a/composer.json +++ b/composer.json @@ -31,13 +31,19 @@ "repositories": [ { "type": "vcs", - "url": "https://github.com/alextech/zend-validator" + "url": "https://github.com/alextech/zend-validator", + "no-api": true + }, + { + "type": "vcs", + "url": "https://github.com/alextech/zend-filter", + "no-api": true } ], "require": { "php": "^5.6 || ^7.0", "psr/http-message": "^1.0", - "zendframework/zend-filter": "^2.6", + "zendframework/zend-filter": "dev-feature/psr-uploadfile", "zendframework/zend-servicemanager": "^2.7.10 || ^3.3.1", "zendframework/zend-stdlib": "^2.7 || ^3.0", "zendframework/zend-validator": "dev-feature/psr-uploadfile" From d36fae2dbe87543b0ce50f88b3d2b7be0a0b68b0 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Mon, 17 Dec 2018 11:15:18 -0600 Subject: [PATCH 11/16] Updates to dependencies - PSR-7 becomes optional; only required if validating PSR-7 `UploadedFileInterface` instances. - Diactoros is no longer required for testing; we can mock PSR-7 interface. --- composer.json | 24 ++--- composer.lock | 261 +++++++++++++++++--------------------------------- 2 files changed, 93 insertions(+), 192 deletions(-) diff --git a/composer.json b/composer.json index e214db71..b5cda0fa 100644 --- a/composer.json +++ b/composer.json @@ -28,30 +28,20 @@ "slack": "https://zendframework-slack.herokuapp.com", "forum": "https://discourse.zendframework.com/c/questions/expressive" }, - "repositories": [ - { - "type": "vcs", - "url": "https://github.com/alextech/zend-validator", - "no-api": true - }, - { - "type": "vcs", - "url": "https://github.com/alextech/zend-filter", - "no-api": true - } - ], "require": { "php": "^5.6 || ^7.0", - "psr/http-message": "^1.0", - "zendframework/zend-filter": "dev-feature/psr-uploadfile", + "zendframework/zend-filter": "^2.9.1", "zendframework/zend-servicemanager": "^2.7.10 || ^3.3.1", "zendframework/zend-stdlib": "^2.7 || ^3.0", - "zendframework/zend-validator": "dev-feature/psr-uploadfile" + "zendframework/zend-validator": "^2.11" }, "require-dev": { "phpunit/phpunit": "^5.7.23 || ^6.4.3", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-diactoros": "^1.8" + "psr/http-message": "^1.0", + "zendframework/zend-coding-standard": "~1.0.0" + }, + "suggest": { + "psr/http-message-implementation": "PSR-7 is required if you wish to validate PSR-7 UploadedFileInterface payloads" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock index 2aea4c9c..bb2c3b52 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "1e0ea7801feacb13899898c866ec40c8", + "content-hash": "d00b91180d6996b4eb38a9a7a271b9a0", "packages": [ { "name": "container-interop/container-interop", @@ -86,68 +86,18 @@ ], "time": "2017-02-14T16:28:37+00:00" }, - { - "name": "psr/http-message", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "time": "2016-08-06T14:39:51+00:00" - }, { "name": "zendframework/zend-filter", - "version": "2.8.0", + "version": "2.9.1", "source": { "type": "git", "url": "https://github.com/zendframework/zend-filter.git", - "reference": "7b997dbe79459f1652deccc8786d7407fb66caa9" + "reference": "1c3e6d02f9cd5f6c929c9859498f5efbe216e86f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-filter/zipball/7b997dbe79459f1652deccc8786d7407fb66caa9", - "reference": "7b997dbe79459f1652deccc8786d7407fb66caa9", + "url": "https://api.github.com/repos/zendframework/zend-filter/zipball/1c3e6d02f9cd5f6c929c9859498f5efbe216e86f", + "reference": "1c3e6d02f9cd5f6c929c9859498f5efbe216e86f", "shasum": "" }, "require": { @@ -160,12 +110,14 @@ "require-dev": { "pear/archive_tar": "^1.4.3", "phpunit/phpunit": "^5.7.23 || ^6.4.3", + "psr/http-factory": "^1.0", "zendframework/zend-coding-standard": "~1.0.0", "zendframework/zend-crypt": "^3.2.1", "zendframework/zend-servicemanager": "^2.7.8 || ^3.3", "zendframework/zend-uri": "^2.6" }, "suggest": { + "psr/http-factory-implementation": "psr/http-factory-implementation, for creating file upload instances when consuming PSR-7 in file upload filters", "zendframework/zend-crypt": "Zend\\Crypt component, for encryption filters", "zendframework/zend-i18n": "Zend\\I18n component for filters depending on i18n functionality", "zendframework/zend-servicemanager": "Zend\\ServiceManager component, for using the filter chain functionality", @@ -174,8 +126,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.8.x-dev", - "dev-develop": "2.9.x-dev" + "dev-master": "2.9.x-dev", + "dev-develop": "2.10.x-dev" }, "zf": { "component": "Zend\\Filter", @@ -197,7 +149,7 @@ "filter", "zf" ], - "time": "2018-04-11T16:20:04+00:00" + "time": "2018-12-17T16:00:04+00:00" }, { "name": "zendframework/zend-servicemanager", @@ -269,16 +221,16 @@ }, { "name": "zendframework/zend-stdlib", - "version": "3.2.0", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/zendframework/zend-stdlib.git", - "reference": "cd164b4a18b5d1aeb69be2c26db035b5ed6925ae" + "reference": "66536006722aff9e62d1b331025089b7ec71c065" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-stdlib/zipball/cd164b4a18b5d1aeb69be2c26db035b5ed6925ae", - "reference": "cd164b4a18b5d1aeb69be2c26db035b5ed6925ae", + "url": "https://api.github.com/repos/zendframework/zend-stdlib/zipball/66536006722aff9e62d1b331025089b7ec71c065", + "reference": "66536006722aff9e62d1b331025089b7ec71c065", "shasum": "" }, "require": { @@ -311,20 +263,20 @@ "stdlib", "zf" ], - "time": "2018-04-30T13:50:40+00:00" + "time": "2018-08-28T21:34:05+00:00" }, { "name": "zendframework/zend-validator", - "version": "dev-feature/psr-uploadfile", + "version": "2.11.0", "source": { "type": "git", - "url": "https://github.com/alextech/zend-validator.git", - "reference": "03e17ae6cae48d8955162b257952d6697ff9f848" + "url": "https://github.com/zendframework/zend-validator.git", + "reference": "f0789b4c4c099afdd2ecc58cc209a26c64bd4f17" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/alextech/zend-validator/zipball/03e17ae6cae48d8955162b257952d6697ff9f848", - "reference": "03e17ae6cae48d8955162b257952d6697ff9f848", + "url": "https://api.github.com/repos/zendframework/zend-validator/zipball/f0789b4c4c099afdd2ecc58cc209a26c64bd4f17", + "reference": "f0789b4c4c099afdd2ecc58cc209a26c64bd4f17", "shasum": "" }, "require": { @@ -339,7 +291,6 @@ "zendframework/zend-coding-standard": "~1.0.0", "zendframework/zend-config": "^2.6", "zendframework/zend-db": "^2.7", - "zendframework/zend-diactoros": "^1.8", "zendframework/zend-filter": "^2.6", "zendframework/zend-http": "^2.5.4", "zendframework/zend-i18n": "^2.6", @@ -349,7 +300,7 @@ "zendframework/zend-uri": "^2.5" }, "suggest": { - "psr/http-message": "Zend\\Filter component, required by PsrFileInput validator", + "psr/http-message": "psr/http-message, required when validating PSR-7 UploadedFileInterface instances via the Upload and UploadFile validators", "zendframework/zend-db": "Zend\\Db component, required by the (No)RecordExists validator", "zendframework/zend-filter": "Zend\\Filter component, required by the Digits validator", "zendframework/zend-i18n": "Zend\\I18n component to allow translation of validation error messages", @@ -362,8 +313,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.10.x-dev", - "dev-develop": "2.11.x-dev" + "dev-master": "2.11.x-dev", + "dev-develop": "2.12.x-dev" }, "zf": { "component": "Zend\\Validator", @@ -375,29 +326,7 @@ "Zend\\Validator\\": "src/" } }, - "autoload-dev": { - "psr-4": { - "ZendTest\\Validator\\": "test/" - } - }, - "scripts": { - "check": [ - "@cs-check", - "@test" - ], - "cs-check": [ - "phpcs" - ], - "cs-fix": [ - "phpcbf" - ], - "test": [ - "phpunit --colors=always" - ], - "test-coverage": [ - "phpunit --colors=always --coverage-clover clover.xml" - ] - }, + "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], @@ -407,10 +336,7 @@ "validator", "zf2" ], - "support": { - "source": "https://github.com/alextech/zend-validator/tree/feature/psr-uploadfile" - }, - "time": "2018-08-15T22:59:51+00:00" + "time": "2018-12-13T21:23:15+00:00" } ], "packages-dev": [ @@ -1084,16 +1010,16 @@ }, { "name": "phpunit/phpunit", - "version": "6.5.11", + "version": "6.5.13", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "7bab54cb366076023bbf457a2a0d513332cd40f2" + "reference": "0973426fb012359b2f18d3bd1e90ef1172839693" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/7bab54cb366076023bbf457a2a0d513332cd40f2", - "reference": "7bab54cb366076023bbf457a2a0d513332cd40f2", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0973426fb012359b2f18d3bd1e90ef1172839693", + "reference": "0973426fb012359b2f18d3bd1e90ef1172839693", "shasum": "" }, "require": { @@ -1164,7 +1090,7 @@ "testing", "xunit" ], - "time": "2018-08-07T07:05:35+00:00" + "time": "2018-09-08T15:10:43+00:00" }, { "name": "phpunit/phpunit-mock-objects", @@ -1225,6 +1151,56 @@ ], "time": "2018-08-09T05:50:03+00:00" }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "time": "2016-08-06T14:39:51+00:00" + }, { "name": "sebastian/code-unit-reverse-lookup", "version": "1.0.1", @@ -1786,16 +1762,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "2.9.1", + "version": "2.9.2", "source": { "type": "git", "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "dcbed1074f8244661eecddfc2a675430d8d33f62" + "reference": "2acf168de78487db620ab4bc524135a13cfe6745" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/dcbed1074f8244661eecddfc2a675430d8d33f62", - "reference": "dcbed1074f8244661eecddfc2a675430d8d33f62", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/2acf168de78487db620ab4bc524135a13cfe6745", + "reference": "2acf168de78487db620ab4bc524135a13cfe6745", "shasum": "" }, "require": { @@ -1860,7 +1836,7 @@ "phpcs", "standards" ], - "time": "2017-05-22T02:43:20+00:00" + "time": "2018-11-07T22:31:41+00:00" }, { "name": "theseer/tokenizer", @@ -1980,76 +1956,11 @@ "zf" ], "time": "2016-11-09T21:30:43+00:00" - }, - { - "name": "zendframework/zend-diactoros", - "version": "1.8.5", - "source": { - "type": "git", - "url": "https://github.com/zendframework/zend-diactoros.git", - "reference": "3e4edb822c942f37ade0d09579cfbab11e2fee87" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-diactoros/zipball/3e4edb822c942f37ade0d09579cfbab11e2fee87", - "reference": "3e4edb822c942f37ade0d09579cfbab11e2fee87", - "shasum": "" - }, - "require": { - "php": "^5.6 || ^7.0", - "psr/http-message": "^1.0" - }, - "provide": { - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "ext-dom": "*", - "ext-libxml": "*", - "phpunit/phpunit": "^5.7.16 || ^6.0.8 || ^7.2.7", - "zendframework/zend-coding-standard": "~1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.8.x-dev", - "dev-develop": "1.9.x-dev", - "dev-release-2.0": "2.0.x-dev" - } - }, - "autoload": { - "files": [ - "src/functions/create_uploaded_file.php", - "src/functions/marshal_headers_from_sapi.php", - "src/functions/marshal_method_from_sapi.php", - "src/functions/marshal_protocol_version_from_sapi.php", - "src/functions/marshal_uri_from_sapi.php", - "src/functions/normalize_server.php", - "src/functions/normalize_uploaded_files.php", - "src/functions/parse_cookie_header.php" - ], - "psr-4": { - "Zend\\Diactoros\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-2-Clause" - ], - "description": "PSR HTTP Message implementations", - "homepage": "https://github.com/zendframework/zend-diactoros", - "keywords": [ - "http", - "psr", - "psr-7" - ], - "time": "2018-08-10T14:16:32+00:00" } ], "aliases": [], "minimum-stability": "stable", - "stability-flags": { - "zendframework/zend-validator": 20 - }, + "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { From 4e283a3da36a8b8f705fde59e031fe86f630e54c Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Mon, 17 Dec 2018 11:55:35 -0600 Subject: [PATCH 12/16] Moves new file input decorators to subnamespace This patch moves the new file input decorators to the namespace `Zend\InputFilter\FileInput`, and performs a number of minor refactors on them to improve readability. Tests are refactored slightly to use generators where appropriate, and to mock PSR-7 interfaces instead of using Diactoros. --- src/FileInput.php | 86 +++-- src/FileInput/FileInputDecoratorInterface.php | 37 ++ .../HttpServerFileInputDecorator.php | 117 +++--- src/{ => FileInput}/PsrFileInputDecorator.php | 105 +++--- src/FileInputDecoratorInterface.php | 44 --- .../HttpServerFileInputDecoratorTest.php | 16 +- test/FileInput/PsrFileInputDecoratorTest.php | 351 +++++++++++++++++ test/InputTest.php | 17 +- test/PsrFileInputDecoratorTest.php | 352 ------------------ 9 files changed, 569 insertions(+), 556 deletions(-) create mode 100644 src/FileInput/FileInputDecoratorInterface.php rename src/{ => FileInput}/HttpServerFileInputDecorator.php (61%) rename src/{ => FileInput}/PsrFileInputDecorator.php (58%) delete mode 100644 src/FileInputDecoratorInterface.php rename test/{ => FileInput}/HttpServerFileInputDecoratorTest.php (95%) create mode 100644 test/FileInput/PsrFileInputDecoratorTest.php delete mode 100644 test/PsrFileInputDecoratorTest.php diff --git a/src/FileInput.php b/src/FileInput.php index 705d9de9..e982185f 100644 --- a/src/FileInput.php +++ b/src/FileInput.php @@ -1,27 +1,27 @@ filterImpl = new PsrFileInputDecorator($this); - } else { - $this->filterImpl = new HttpServerFileInputDecorator($this); - } - } elseif ($value instanceof UploadedFileInterface) { - $this->filterImpl = new PsrFileInputDecorator($this); - } else { - // ajax case - $this->filterImpl = new HttpServerFileInputDecorator($this); - } - - + $this->implementation = $this->createDecoratorImplementation($value); parent::setValue($value); - return $this; } public function resetValue() { - $this->filterImpl = null; + $this->implementation = null; return parent::resetValue(); } - /** * @param bool $value Enable/Disable automatically prepending an Upload validator * @@ -98,10 +83,10 @@ public function getAutoPrependUploadValidator() */ public function getValue() { - if ($this->filterImpl === null) { + if ($this->implementation === null) { return $this->value; } - return $this->filterImpl->getValue(); + return $this->implementation->getValue(); } /** @@ -112,16 +97,16 @@ public function getValue() */ public function isEmptyFile($rawValue) { - if (\is_array($rawValue)) { + if ($rawValue instanceof UploadedFileInterface) { + return FileInput\PsrFileInputDecorator::isEmptyFileDecorator($rawValue); + } + + if (is_array($rawValue)) { if (isset($rawValue[0]) && $rawValue[0] instanceof UploadedFileInterface) { - return PsrFileInputDecorator::isEmptyFileDecorator($rawValue); + return FileInput\PsrFileInputDecorator::isEmptyFileDecorator($rawValue); } - return HttpServerFileInputDecorator::isEmptyFileDecorator($rawValue); - } - - if($rawValue instanceof UploadedFileInterface) { - return PsrFileInputDecorator::isEmptyFileDecorator($rawValue); + return FileInput\HttpServerFileInputDecorator::isEmptyFileDecorator($rawValue); } return true; @@ -159,7 +144,7 @@ public function isValid($context = null) return true; } - return $this->filterImpl->isValid($context); + return $this->implementation->isValid($context); } /** @@ -188,4 +173,29 @@ protected function injectNotEmptyValidator() { $this->notEmptyValidator = true; } + + /** + * @param mixed $value + * @return FileInput\FileInputDecoratorInterface + */ + private function createDecoratorImplementation($value) + { + // Single PSR-7 instance + if ($value instanceof UploadedFileInterface) { + return new FileInput\PsrFileInputDecorator($this); + } + + if (is_array($value)) { + if (isset($value[0]) && $value[0] instanceof UploadedFileInterface) { + // Array of PSR-7 instances + return new FileInput\PsrFileInputDecorator($this); + } + + // Single or multiple SAPI file upload arrays + return new FileInput\HttpServerFileInputDecorator($this); + } + + // AJAX/XHR/Fetch case + return new FileInput\HttpServerFileInputDecorator($this); + } } diff --git a/src/FileInput/FileInputDecoratorInterface.php b/src/FileInput/FileInputDecoratorInterface.php new file mode 100644 index 00000000..3cda94e1 --- /dev/null +++ b/src/FileInput/FileInputDecoratorInterface.php @@ -0,0 +1,37 @@ +subject = $subject; - } - - /** - * @return mixed - */ - public function getValue() - { - $value = $this->subject->value; - if ($this->subject->isValid && is_array($value)) { - // Run filters ~after~ validation, so that is_uploaded_file() - // validation is not affected by filters. - $filter = $this->subject->getFilterChain(); - if (isset($value['tmp_name'])) { - // Single file input - $value = $filter->filter($value); - } else { - // Multi file input (multiple attribute set) - $newValue = []; - foreach ($value as $fileData) { - if (is_array($fileData) && isset($fileData['tmp_name'])) { - $newValue[] = $filter->filter($fileData); - } - } - $value = $newValue; - } - } - - return $value; - } - /** * Checks if the raw input value is an empty file input eg: no file was uploaded * @@ -86,17 +56,50 @@ public static function isEmptyFileDecorator($rawValue) return false; } + public function __construct(FileInput $subject) + { + $this->subject = $subject; + } + + /** + * @return mixed + */ + public function getValue() + { + $value = $this->subject->value; + + if (! $this->subject->isValid || ! is_array($value)) { + return $value; + } + + // Run filters ~after~ validation, so that is_uploaded_file() + // validation is not affected by filters. + $filter = $this->subject->getFilterChain(); + if (isset($value['tmp_name'])) { + // Single file input + $value = $filter->filter($value); + return $value; + } + + // Multi file input (multiple attribute set) + $newValue = []; + foreach ($value as $fileData) { + if (is_array($fileData) && isset($fileData['tmp_name'])) { + $newValue[] = $filter->filter($fileData); + } + } + + return $newValue; + } + /** * @param mixed $context Extra "context" to provide the validator * @return bool */ public function isValid($context = null) { - $rawValue = $this->subject->getRawValue(); - $validator = $this->subject->getValidatorChain(); - $this->injectUploadValidator(); - - //$value = $this->getValue(); // Do not run the filters yet for File uploads (see getValue()) + $rawValue = $this->subject->getRawValue(); + $validator = $this->injectUploadValidator($this->subject->getValidatorChain()); if (! is_array($rawValue)) { // This can happen in an AJAX POST, where the input comes across as a string @@ -108,32 +111,38 @@ public function isValid($context = null) 'error' => UPLOAD_ERR_NO_FILE, ]; } + if (is_array($rawValue) && isset($rawValue['tmp_name'])) { // Single file input $this->subject->isValid = $validator->isValid($rawValue, $context); - } elseif (is_array($rawValue) && isset($rawValue[0]['tmp_name'])) { + return $this->subject->isValid; + } + + if (is_array($rawValue) && isset($rawValue[0]['tmp_name'])) { // Multi file input (multiple attribute set) $this->subject->isValid = true; + foreach ($rawValue as $value) { if (! $validator->isValid($value, $context)) { $this->subject->isValid = false; - break; // Do not continue processing files if validation fails + return false; // Do not continue processing files if validation fails } } + + return true; // We return early from the loop if validation fails } return $this->subject->isValid; } /** - * @return void + * @return ValidatorChain */ - protected function injectUploadValidator() + protected function injectUploadValidator(ValidatorChain $chain) { if (! $this->subject->autoPrependUploadValidator) { - return; + return $chain; } - $chain = $this->subject->getValidatorChain(); // Check if Upload validator is already first in chain $validators = $chain->getValidators(); @@ -141,10 +150,12 @@ protected function injectUploadValidator() && $validators[0]['instance'] instanceof UploadValidator ) { $this->subject->autoPrependUploadValidator = false; - return; + return $chain; } - $chain->prependByName('fileuploadfile', [], true); + $chain->prependByName(UploadValidator::class, [], true); $this->subject->autoPrependUploadValidator = false; + + return $chain; } } diff --git a/src/PsrFileInputDecorator.php b/src/FileInput/PsrFileInputDecorator.php similarity index 58% rename from src/PsrFileInputDecorator.php rename to src/FileInput/PsrFileInputDecorator.php index 042cf247..4045f505 100644 --- a/src/PsrFileInputDecorator.php +++ b/src/FileInput/PsrFileInputDecorator.php @@ -1,43 +1,60 @@ getError() === UPLOAD_ERR_NO_FILE; + } + public function __construct(FileInput $subject) { $this->subject = $subject; } /** - * @return UploadedFileInterface|array + * @return UploadedFileInterface|UploadedFileInterface[] */ public function getValue() { @@ -49,39 +66,19 @@ public function getValue() return $value; } - if (\is_array($value)) { - // Multi file input (multiple attribute set) - $filter = $this->subject->getFilterChain(); + $filter = $this->subject->getFilterChain(); + if (is_array($value)) { + // Multi file input (multiple attribute set) $newValue = []; foreach ($value as $fileData) { $newValue[] = $filter->filter($fileData); } - $value = $newValue; - } else { - // Single file input - - $filter = $this->subject->getFilterChain(); - $value = $filter->filter($value); - } - - return $value; - } - - /** - * Checks if the raw input value is an empty file input eg: no file was uploaded - * - * @param UploadedFileInterface|array $rawValue - * @return bool - */ - public static function isEmptyFileDecorator($rawValue) - { - if (\is_array($rawValue)) { - return self::isEmptyFileDecorator($rawValue[0]); + return $newValue; } - return $rawValue->getError() === UPLOAD_ERR_NO_FILE; - + // Single file input + return $filter->filter($value); } /** @@ -90,47 +87,47 @@ public static function isEmptyFileDecorator($rawValue) */ public function isValid($context = null) { - $rawValue = $this->subject->getRawValue(); - $validator = $this->subject->getValidatorChain(); - $this->injectUploadValidator(); + $rawValue = $this->subject->getRawValue(); + $validator = $this->injectUploadValidator($this->subject->getValidatorChain()); - //$value = $this->getValue(); // Do not run the filters yet for File uploads (see getValue()) - - if (\is_array($rawValue)) { + if (is_array($rawValue)) { // Multi file input (multiple attribute set) $this->subject->isValid = true; foreach ($rawValue as $value) { if (! $validator->isValid($value, $context)) { $this->subject->isValid = false; - break; // Do not continue processing files if validation fails + return false; // Do not continue processing files if validation fails } } - } else { - // Single file input - $this->subject->isValid = $validator->isValid($rawValue, $context); + return true; // We return early from the loop if validation fails } + // Single file input + $this->subject->isValid = $validator->isValid($rawValue, $context); return $this->subject->isValid; } /** - * @return void + * @return ValidatorChain */ - protected function injectUploadValidator() + protected function injectUploadValidator(ValidatorChain $chain) { if (! $this->subject->autoPrependUploadValidator) { - return; + return $chain; } - $chain = $this->subject->getValidatorChain(); // Check if Upload validator is already first in chain $validators = $chain->getValidators(); - if (isset($validators[0]['instance'])) { + if (isset($validators[0]['instance']) + && $validators[0]['instance'] instanceof UploadValidator + ) { $this->subject->autoPrependUploadValidator = false; - return; + return $chain; } - $chain->prependByName('fileuploadfile', [], true); + $chain->prependByName(UploadValidator::class, [], true); $this->subject->autoPrependUploadValidator = false; + + return $chain; } } diff --git a/src/FileInputDecoratorInterface.php b/src/FileInputDecoratorInterface.php deleted file mode 100644 index 944a1aab..00000000 --- a/src/FileInputDecoratorInterface.php +++ /dev/null @@ -1,44 +0,0 @@ -assertTrue($this->input->isRequired()); $this->input->setValue(['tmp_name' => 'bar']); - /** @var Validator\File\UploadFile|MockObject $uploadMock */ $uploadMock = $this->getMockBuilder(Validator\File\UploadFile::class) ->setMethods(['isValid']) ->getMock(); diff --git a/test/FileInput/PsrFileInputDecoratorTest.php b/test/FileInput/PsrFileInputDecoratorTest.php new file mode 100644 index 00000000..6eea2867 --- /dev/null +++ b/test/FileInput/PsrFileInputDecoratorTest.php @@ -0,0 +1,351 @@ +input = new FileInput('foo'); + // Upload validator does not work in CLI test environment, disable + $this->input->setAutoPrependUploadValidator(false); + } + + public function testRetrievingValueFiltersTheValue() + { + $this->markTestSkipped('Test is not enabled in PsrFileInputTest'); + } + + public function testRetrievingValueFiltersTheValueOnlyAfterValidating() + { + $upload = $this->prophesize(UploadedFileInterface::class); + $upload->getError()->willReturn(UPLOAD_ERR_OK); + + $this->input->setValue($upload->reveal()); + + $filteredUpload = $this->prophesize(UploadedFileInterface::class); + + $this->input->setFilterChain($this->createFilterChainMock([[ + $upload->reveal(), + $filteredUpload->reveal(), + ]])); + + $this->assertEquals($upload->reveal(), $this->input->getValue()); + $this->assertTrue( + $this->input->isValid(), + 'isValid() value not match. Detail . ' . json_encode($this->input->getMessages()) + ); + $this->assertEquals($filteredUpload->reveal(), $this->input->getValue()); + } + + public function testCanFilterArrayOfMultiFileData() + { + $values = []; + for ($i = 0; $i < 3; $i += 1) { + $upload = $this->prophesize(UploadedFileInterface::class); + $upload->getError()->willReturn(UPLOAD_ERR_OK); + $values[] = $upload->reveal(); + } + + $this->input->setValue($values); + + $filteredValues = []; + for ($i = 0; $i < 3; $i += 1) { + $upload = $this->prophesize(UploadedFileInterface::class); + $filteredValues[] = $upload->reveal(); + } + + $this->input->setFilterChain($this->createFilterChainMock([ + [$values[0], $filteredValues[0]], + [$values[1], $filteredValues[1]], + [$values[2], $filteredValues[2]], + ])); + + $this->assertEquals($values, $this->input->getValue()); + $this->assertTrue( + $this->input->isValid(), + 'isValid() value not match. Detail . ' . json_encode($this->input->getMessages()) + ); + $this->assertEquals( + $filteredValues, + $this->input->getValue() + ); + } + + public function testCanRetrieveRawValue() + { + $value = $this->prophesize(UploadedFileInterface::class); + $value->getError()->shouldNotBeCalled(); + + $this->input->setValue($value->reveal()); + + $filteredValue = $this->prophesize(UploadedFileInterface::class)->reveal(); + $this->input->setFilterChain($this->createFilterChainMock([[$value->reveal(), $filteredValue]])); + + $this->assertEquals($value->reveal(), $this->input->getRawValue()); + } + + public function testValidationOperatesOnFilteredValue() + { + $this->markTestSkipped('Test is not enabled in PsrFileInputTest'); + } + + public function testValidationOperatesBeforeFiltering() + { + $badValue = $this->prophesize(UploadedFileInterface::class); + $badValue->getError()->willReturn(UPLOAD_ERR_NO_FILE); + $filteredValue = $this->prophesize(UploadedFileInterface::class)->reveal(); + + $this->input->setValue($badValue->reveal()); + + $this->input->setFilterChain($this->createFilterChainMock([[$badValue->reveal(), $filteredValue]])); + $this->input->setValidatorChain($this->createValidatorChainMock([[$badValue->reveal(), null, false]])); + + $this->assertFalse($this->input->isValid()); + $this->assertEquals($badValue->reveal(), $this->input->getValue()); + } + + public function testAutoPrependUploadValidatorIsOnByDefault() + { + $input = new FileInput('foo'); + $this->assertTrue($input->getAutoPrependUploadValidator()); + } + + public function testUploadValidatorIsAddedDuringIsValidWhenAutoPrependUploadValidatorIsEnabled() + { + $this->input->setAutoPrependUploadValidator(true); + $this->assertTrue($this->input->getAutoPrependUploadValidator()); + $this->assertTrue($this->input->isRequired()); + + $uploadedFile = $this->prophesize(UploadedFileInterface::class); + $uploadedFile->getError()->willReturn(UPLOAD_ERR_CANT_WRITE); + + $this->input->setValue($uploadedFile->reveal()); + + $validatorChain = $this->input->getValidatorChain(); + $this->assertCount(0, $validatorChain->getValidators()); + + $this->assertFalse($this->input->isValid()); + $validators = $validatorChain->getValidators(); + $this->assertCount(1, $validators); + $this->assertInstanceOf(Validator\File\UploadFile::class, $validators[0]['instance']); + } + + public function testUploadValidatorIsNotAddedByDefaultDuringIsValidWhenAutoPrependUploadValidatorIsDisabled() + { + $this->assertFalse($this->input->getAutoPrependUploadValidator()); + $this->assertTrue($this->input->isRequired()); + + $uploadedFile = $this->prophesize(UploadedFileInterface::class); + $uploadedFile->getError()->willReturn(UPLOAD_ERR_OK); + + $this->input->setValue($uploadedFile->reveal()); + $validatorChain = $this->input->getValidatorChain(); + $this->assertEquals(0, count($validatorChain->getValidators())); + + $this->assertTrue( + $this->input->isValid(), + 'isValid() value not match. Detail . ' . json_encode($this->input->getMessages()) + ); + $this->assertEquals(0, count($validatorChain->getValidators())); + } + + public function testRequiredUploadValidatorValidatorNotAddedWhenOneExists() + { + $this->input->setAutoPrependUploadValidator(true); + $this->assertTrue($this->input->getAutoPrependUploadValidator()); + $this->assertTrue($this->input->isRequired()); + + $upload = $this->prophesize(UploadedFileInterface::class); + $upload->getError()->willReturn(UPLOAD_ERR_OK); + + $this->input->setValue($upload->reveal()); + + $validator = $this->prophesize(Validator\File\UploadFile::class); + $validator + ->isValid(Argument::that([$upload, 'reveal']), null) + ->willReturn(true) + ->shouldBeCalledTimes(1); + + $validatorChain = $this->input->getValidatorChain(); + $validatorChain->prependValidator($validator->reveal()); + $this->assertTrue( + $this->input->isValid(), + 'isValid() value not match. Detail . ' . json_encode($this->input->getMessages()) + ); + + $validators = $validatorChain->getValidators(); + $this->assertEquals(1, count($validators)); + $this->assertEquals($validator->reveal(), $validators[0]['instance']); + } + + public function testNotEmptyValidatorAddedWhenIsValidIsCalled($value = null) + { + $this->markTestSkipped('Test is not enabled in PsrFileInputTest'); + } + + public function testRequiredNotEmptyValidatorNotAddedWhenOneExists($value = null) + { + $this->markTestSkipped('Test is not enabled in PsrFileInputTest'); + } + + public function testFallbackValueVsIsValidRules( + $required = null, + $fallbackValue = null, + $originalValue = null, + $isValid = null, + $expectedValue = null + ) { + $this->markTestSkipped('Input::setFallbackValue is not implemented on PsrFileInput'); + } + + + public function testFallbackValueVsIsValidRulesWhenValueNotSet( + $required = null, + $fallbackValue = null + ) { + $this->markTestSkipped('Input::setFallbackValue is not implemented on PsrFileInput'); + } + + public function testIsEmptyFileUploadNoFile() + { + $upload = $this->prophesize(UploadedFileInterface::class); + $upload->getError()->willReturn(UPLOAD_ERR_NO_FILE); + $this->assertTrue($this->input->isEmptyFile($upload->reveal())); + } + + public function testIsEmptyFileOk() + { + $upload = $this->prophesize(UploadedFileInterface::class); + $upload->getError()->willReturn(UPLOAD_ERR_OK); + $this->assertFalse($this->input->isEmptyFile($upload->reveal())); + } + + public function testIsEmptyMultiFileUploadNoFile() + { + $upload = $this->prophesize(UploadedFileInterface::class); + $upload->getError()->willReturn(UPLOAD_ERR_NO_FILE); + + $rawValue = [$upload->reveal()]; + + $this->assertTrue($this->input->isEmptyFile($rawValue)); + } + + public function testIsEmptyFileMultiFileOk() + { + $rawValue = []; + for ($i = 0; $i < 2; $i += 1) { + $upload = $this->prophesize(UploadedFileInterface::class); + $upload->getError()->willReturn(UPLOAD_ERR_OK); + $rawValue[] = $upload->reveal(); + } + + $this->assertFalse($this->input->isEmptyFile($rawValue)); + } + + /** + * Specific PsrFileInput::merge extras + */ + public function testPsrFileInputMerge() + { + $source = new FileInput(); + $source->setAutoPrependUploadValidator(true); + + $target = $this->input; + $target->setAutoPrependUploadValidator(false); + + $return = $target->merge($source); + $this->assertSame($target, $return, 'merge() must return it self'); + + $this->assertEquals( + true, + $target->getAutoPrependUploadValidator(), + 'getAutoPrependUploadValidator() value not match' + ); + } + + public function isRequiredVsAllowEmptyVsContinueIfEmptyVsIsValidProvider() + { + $generator = parent::isRequiredVsAllowEmptyVsContinueIfEmptyVsIsValidProvider(); + if (! is_array($generator)) { + $generator = clone $generator; + $generator->rewind(); + } + + $toSkip = [ + 'Required: T; AEmpty: F; CIEmpty: F; Validator: X, Value: Empty / tmp_name', + 'Required: T; AEmpty: F; CIEmpty: F; Validator: X, Value: Empty / single', + 'Required: T; AEmpty: F; CIEmpty: F; Validator: X, Value: Empty / multi', + ]; + + foreach ($generator as $name => $data) { + if (in_array($name, $toSkip, true)) { + continue; + } + yield $name => $data; + } + } + + public function emptyValueProvider() + { + foreach (['single', 'multi'] as $type) { + $raw = $this->prophesize(UploadedFileInterface::class); + $raw->getError()->willReturn(UPLOAD_ERR_NO_FILE); + + $filtered = $this->prophesize(UploadedFileInterface::class); + $filtered->getError()->willReturn(UPLOAD_ERR_NO_FILE); + + yield $type => [ + 'raw' => $type === 'multi' + ? [$raw->reveal()] + : $raw->reveal(), + 'filtered' => $raw->reveal(), + ]; + } + } + + public function mixedValueProvider() + { + $fooUploadErrOk = $this->prophesize(UploadedFileInterface::class); + $fooUploadErrOk->getError()->willReturn(UPLOAD_ERR_OK); + + return [ + 'single' => [ + 'raw' => $fooUploadErrOk->reveal(), + 'filtered' => $fooUploadErrOk->reveal(), + ], + 'multi' => [ + 'raw' => [ + $fooUploadErrOk->reveal(), + ], + 'filtered' => $fooUploadErrOk->reveal(), + ], + ]; + } + + protected function getDummyValue($raw = true) + { + $upload = $this->prophesize(UploadedFileInterface::class); + $upload->getError()->willReturn(UPLOAD_ERR_OK); + return $upload->reveal(); + } +} diff --git a/test/InputTest.php b/test/InputTest.php index 16ed313b..5760593c 100644 --- a/test/InputTest.php +++ b/test/InputTest.php @@ -1,14 +1,13 @@ emptyValueProvider(); $mixedValues = $this->mixedValueProvider(); + $emptyValues = $emptyValues instanceof Iterator ? iterator_to_array($emptyValues) : $emptyValues; + $mixedValues = $mixedValues instanceof Iterator ? iterator_to_array($mixedValues) : $mixedValues; + $values = array_merge($emptyValues, $mixedValues); return $values; @@ -672,7 +674,10 @@ public function setValueProvider() public function isRequiredVsAllowEmptyVsContinueIfEmptyVsIsValidProvider() { $allValues = $this->setValueProvider(); + $emptyValues = $this->emptyValueProvider(); + $emptyValues = $emptyValues instanceof Iterator ? iterator_to_array($emptyValues) : $emptyValues; + $nonEmptyValues = array_diff_key($allValues, $emptyValues); $isRequired = true; @@ -694,7 +699,7 @@ public function isRequiredVsAllowEmptyVsContinueIfEmptyVsIsValidProvider() }; // @codingStandardsIgnoreStart - $dataTemplates=[ + $dataTemplates = [ // Description => [$isRequired, $allowEmpty, $continueIfEmpty, $validator, [$values], $expectedIsValid, $expectedMessages] 'Required: T; AEmpty: T; CIEmpty: T; Validator: T' => [ $isRequired, $aEmpty, $cIEmpty, $validatorValid , $allValues , $isValid, []], 'Required: T; AEmpty: T; CIEmpty: T; Validator: F' => [ $isRequired, $aEmpty, $cIEmpty, $validatorInvalid, $allValues , !$isValid, $validatorMsg], diff --git a/test/PsrFileInputDecoratorTest.php b/test/PsrFileInputDecoratorTest.php deleted file mode 100644 index 3a98e113..00000000 --- a/test/PsrFileInputDecoratorTest.php +++ /dev/null @@ -1,352 +0,0 @@ -input = new FileInput('foo'); - // Upload validator does not work in CLI test environment, disable - $this->input->setAutoPrependUploadValidator(false); - } - - public function testRetrievingValueFiltersTheValue() - { - $this->markTestSkipped('Test are not enabled in PsrFileInputTest'); - } - - public function testRetrievingValueFiltersTheValueOnlyAfterValidating() - { - $value = new UploadedFile('bar', 1, 0); - $this->input->setValue($value); - - $newValue = new UploadedFile('foo', 1, 0); - $this->input->setFilterChain($this->createFilterChainMock([[$value, $newValue]])); - - $this->assertEquals($value, $this->input->getValue()); - $this->assertTrue( - $this->input->isValid(), - 'isValid() value not match. Detail . ' . json_encode($this->input->getMessages()) - ); - $this->assertEquals($newValue, $this->input->getValue()); - } - - public function testCanFilterArrayOfMultiFileData() - { - $values = [ - new UploadedFile('foo', 1, 0), - new UploadedFile('bar', 1, 0), - new UploadedFile('baz', 1, 0), - ]; - $this->input->setValue($values); - - $newValue = new UploadedFile('new', 1, 0); - $filteredValue = [$newValue, $newValue, $newValue]; - $this->input->setFilterChain($this->createFilterChainMock([ - [$values[0], $newValue], - [$values[1], $newValue], - [$values[2], $newValue], - ])); - - $this->assertEquals($values, $this->input->getValue()); - $this->assertTrue( - $this->input->isValid(), - 'isValid() value not match. Detail . ' . json_encode($this->input->getMessages()) - ); - $this->assertEquals( - $filteredValue, - $this->input->getValue() - ); - } - - public function testCanRetrieveRawValue() - { - $value = new UploadedFile('bar', 1, 0); - $this->input->setValue($value); - - $newValue = new UploadedFile('new', 1, 0); - $this->input->setFilterChain($this->createFilterChainMock([[$value, $newValue]])); - - $this->assertEquals($value, $this->input->getRawValue()); - } - - public function testValidationOperatesOnFilteredValue() - { - $this->markTestSkipped('Test is not enabled in PsrFileInputTest'); - } - - public function testValidationOperatesBeforeFiltering() - { - $badValue = new UploadedFile( - ' ' . __FILE__ . ' ', - 1, - 0, - 'foo' - ); - $this->input->setValue($badValue); - - $filteredValue = new UploadedFile('new', 1, 0); - $this->input->setFilterChain($this->createFilterChainMock([[$badValue, $filteredValue]])); - $this->input->setValidatorChain($this->createValidatorChainMock([[$badValue, null, false]])); - - $this->assertFalse($this->input->isValid()); - $this->assertEquals($badValue, $this->input->getValue()); - } - - public function testAutoPrependUploadValidatorIsOnByDefault() - { - $input = new FileInput('foo'); - $this->assertTrue($input->getAutoPrependUploadValidator()); - } - - public function testUploadValidatorIsAddedWhenIsValidIsCalled() - { - $this->input->setAutoPrependUploadValidator(true); - $this->assertTrue($this->input->getAutoPrependUploadValidator()); - $this->assertTrue($this->input->isRequired()); - $this->input->setValue(new UploadedFile( - __FILE__, - 1, - 0, - 'foo' - )); - $validatorChain = $this->input->getValidatorChain(); - $this->assertCount(0, $validatorChain->getValidators()); - - $this->assertFalse($this->input->isValid()); - $validators = $validatorChain->getValidators(); - $this->assertCount(1, $validators); - $this->assertInstanceOf(Validator\File\UploadFile::class, $validators[0]['instance']); - } - - public function testUploadValidatorIsNotAddedWhenIsValidIsCalled() - { - $this->assertFalse($this->input->getAutoPrependUploadValidator()); - $this->assertTrue($this->input->isRequired()); - $this->input->setValue(new UploadedFile('bar', 1, 0)); - $validatorChain = $this->input->getValidatorChain(); - $this->assertEquals(0, count($validatorChain->getValidators())); - - $this->assertTrue( - $this->input->isValid(), - 'isValid() value not match. Detail . ' . json_encode($this->input->getMessages()) - ); - $this->assertEquals(0, count($validatorChain->getValidators())); - } - - public function testRequiredUploadValidatorValidatorNotAddedWhenOneExists() - { - $this->input->setAutoPrependUploadValidator(true); - $this->assertTrue($this->input->getAutoPrependUploadValidator()); - $this->assertTrue($this->input->isRequired()); - $this->input->setValue(new UploadedFile('bar', 1, 0)); - - /** @var Validator\File\UploadFile|MockObject $uploadMock */ - $uploadMock = $this->getMockBuilder(Validator\File\UploadFile::class) - ->setMethods(['isValid']) - ->getMock(); - $uploadMock->expects($this->exactly(1)) - ->method('isValid') - ->will($this->returnValue(true)); - - $validatorChain = $this->input->getValidatorChain(); - $validatorChain->prependValidator($uploadMock); - $this->assertTrue( - $this->input->isValid(), - 'isValid() value not match. Detail . ' . json_encode($this->input->getMessages()) - ); - - $validators = $validatorChain->getValidators(); - $this->assertEquals(1, count($validators)); - $this->assertEquals($uploadMock, $validators[0]['instance']); - } - - public function testNotEmptyValidatorAddedWhenIsValidIsCalled($value = null) - { - $this->markTestSkipped('Test is not enabled in PsrFileInputTest'); - } - - public function testRequiredNotEmptyValidatorNotAddedWhenOneExists($value = null) - { - $this->markTestSkipped('Test is not enabled in PsrFileInputTest'); - } - - public function testFallbackValueVsIsValidRules( - $required = null, - $fallbackValue = null, - $originalValue = null, - $isValid = null, - $expectedValue = null - ) { - $this->markTestSkipped('Input::setFallbackValue is not implemented on PsrFileInput'); - } - - - public function testFallbackValueVsIsValidRulesWhenValueNotSet( - $required = null, - $fallbackValue = null - ) { - $this->markTestSkipped('Input::setFallbackValue is not implemented on PsrFileInput'); - } - - public function testIsEmptyFileUploadNoFile() - { - $rawValue = new UploadedFile( - '', - 0, - UPLOAD_ERR_NO_FILE - ); - $this->assertTrue($this->input->isEmptyFile($rawValue)); - } - - public function testIsEmptyFileOk() - { - $rawValue = new UploadedFile( - 'name', - 1, - UPLOAD_ERR_OK - ); - $this->assertFalse($this->input->isEmptyFile($rawValue)); - } - - public function testIsEmptyMultiFileUploadNoFile() - { - $rawValue = [new UploadedFile( - 'foo', - 0, - UPLOAD_ERR_NO_FILE - )]; - $this->assertTrue($this->input->isEmptyFile($rawValue)); - } - - public function testIsEmptyFileMultiFileOk() - { - $rawValue = [ - new UploadedFile( - 'foo', - 1, - UPLOAD_ERR_OK - ), - new UploadedFile( - 'bar', - 1, - UPLOAD_ERR_OK - ), - ]; - $this->assertFalse($this->input->isEmptyFile($rawValue)); - } - - /** - * Specific PsrFileInput::merge extras - */ - public function testPsrFileInputMerge() - { - $source = new FileInput(); - $source->setAutoPrependUploadValidator(true); - - $target = $this->input; - $target->setAutoPrependUploadValidator(false); - - $return = $target->merge($source); - $this->assertSame($target, $return, 'merge() must return it self'); - - $this->assertEquals( - true, - $target->getAutoPrependUploadValidator(), - 'getAutoPrependUploadValidator() value not match' - ); - } - - public function isRequiredVsAllowEmptyVsContinueIfEmptyVsIsValidProvider() - { - $dataSets = parent::isRequiredVsAllowEmptyVsContinueIfEmptyVsIsValidProvider(); - - // PsrFileInput do not use NotEmpty validator so the only validator present in the chain is the custom one. - unset($dataSets['Required: T; AEmpty: F; CIEmpty: F; Validator: X, Value: Empty / tmp_name']); - unset($dataSets['Required: T; AEmpty: F; CIEmpty: F; Validator: X, Value: Empty / single']); - unset($dataSets['Required: T; AEmpty: F; CIEmpty: F; Validator: X, Value: Empty / multi']); - - return $dataSets; - } - - public function emptyValueProvider() - { - return [ - 'single' => [ - 'raw' => new UploadedFile( - '', - 0, - UPLOAD_ERR_NO_FILE - ), - 'filtered' => new UploadedFile( - '', - 0, - UPLOAD_ERR_NO_FILE - ), - ], - 'multi' => [ - 'raw' => [ - new UploadedFile( - 'foo', - 0, - UPLOAD_ERR_NO_FILE - ), - ], - 'filtered' => new UploadedFile( - 'foo', - 0, - UPLOAD_ERR_NO_FILE - ), - ], - ]; - } - - public function mixedValueProvider() - { - $fooUploadErrOk = new UploadedFile( - 'foo', - 1, - UPLOAD_ERR_OK - ); - - return [ - 'single' => [ - 'raw' => $fooUploadErrOk, - 'filtered' => $fooUploadErrOk, - ], - 'multi' => [ - 'raw' => [ - $fooUploadErrOk, - ], - 'filtered' => $fooUploadErrOk, - ], - ]; - } - - protected function getDummyValue($raw = true) - { - return new UploadedFile('bar', 0, 0); - } -} From 8c9e795a97bc07ad0c42f097b8d17177d59ee3db Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Mon, 17 Dec 2018 15:09:51 -0600 Subject: [PATCH 13/16] Documents PSR-7 compatibility Adds a section to the file input chapter covering PSR-7, and showing how to get the merged form and uploaded file data to pass to the input filter. --- docs/book/file-input.md | 103 +++++++++++++++++++++++++++++++++------- 1 file changed, 87 insertions(+), 16 deletions(-) diff --git a/docs/book/file-input.md b/docs/book/file-input.md index e04bd025..425f837b 100644 --- a/docs/book/file-input.md +++ b/docs/book/file-input.md @@ -10,9 +10,14 @@ While `FileInput` uses the same interface as `Input`, it differs in a few ways: chapter for details on how to accomplish this. [Diactoros](https://docs.zendframework.com/zend-diactoros/) and [zend-http](https://docs.zendframework.com/zend-http/) can do this for you. + + Alternately, you may provide an array of PSR-7 uploaded file instances. + 2. The validators are run **before** the filters (which is the opposite behavior - of `Input`). This is so that any `is_uploaded_file()` validation can be run - prior to any filters that may rename/move/modify the file. + of `Input`). This is so that any validation can be run + prior to any filters that may rename/move/modify the file; we should not do + those operations if the file upload was invalid! + 3. Instead of adding a `NotEmpty` validator, it will (by default) automatically add a `Zend\Validator\File\UploadFile` validator. @@ -34,29 +39,95 @@ use Zend\Validator; // Description text input $description = new Input('description'); // Standard Input type -$description->getFilterChain() // Filters are run first w/ Input - ->attach(new Filter\StringTrim()); -$description->getValidatorChain() // Validators are run second w/ Input - ->attach(new Validator\StringLength(['max' => 140])); + +// Filters are run first w/ Input +$description + ->getFilterChain() + ->attach(new Filter\StringTrim()); + +// Validators are run second w/ Input +$description + ->getValidatorChain() + ->attach(new Validator\StringLength(['max' => 140])); // File upload input -$file = new FileInput('file'); // Special File Input type -$file->getValidatorChain() // Validators are run first w/ FileInput - ->attach(new Validator\File\UploadFile()); -$file->getFilterChain() // Filters are run second w/ FileInput - ->attach(new Filter\File\RenameUpload([ +$file = new FileInput('file'); // Special File Input type + +// Validators are run first w/ FileInput +$file + ->getValidatorChain() + ->attach(new Validator\File\UploadFile()); + +// Filters are run second w/ FileInput +$file + ->getFilterChain() + ->attach(new Filter\File\RenameUpload([ 'target' => './data/tmpuploads/file', 'randomize' => true, - ])); + ])); // Merge $_POST and $_FILES data together $request = new Request(); -$postData = array_merge_recursive($request->getPost()->toArray(), $request->getFiles()->toArray()); +$postData = array_merge_recursive( + $request->getPost()->toArray(), + $request->getFiles()->toArray() +); + +$inputFilter = new InputFilter(); +$inputFilter + ->add($description) + ->add($file) + ->setData($postData); + +if ($inputFilter->isValid()) { // FileInput validators are run, but not the filters... + echo "The form is valid\n"; + $data = $inputFilter->getValues(); // This is when the FileInput filters are run. +} else { + echo "The form is not valid\n"; + foreach ($inputFilter->getInvalidInput() as $error) { + print_r ($error->getMessages()); + } +} +``` + +## PSR-7 Support + +- Since 2.9.0 + +You may also pass an array of uploaded files from a [PSR-7 ServerRequestInterface](http://www.php-fig.org/psr/psr-7/#serverrequestinterface). + +```php +use Psr\Http\Message\ServerRequestInterface; +use Zend\Filter; +use Zend\InputFilter\InputFilter; +use Zend\InputFilter\FileInput; +use Zend\Validator; + +// File upload input +$file = new FileInput('file'); +$file + ->getValidatorChain() + ->attach(new Validator\File\UploadFile()); +$file + ->getFilterChain() + ->attach(new Filter\File\RenameUpload([ + 'target' => './data/tmpuploads/file', + 'randomize' => true, + ])); + +// Merge form and uploaded file data together +// Unlike the previous example, we get the form data from `getParsedBody()`, and +// the uploaded file data from `getUploadedFiles()`. +// @var ServerRequestInterface $request +$postData = array_merge_recursive( + $request->getParsedBody(), + $request->getUploadedFiles() +); $inputFilter = new InputFilter(); -$inputFilter->add($description) - ->add($file) - ->setData($postData); +$inputFilter + ->add($file) + ->setData($postData); if ($inputFilter->isValid()) { // FileInput validators are run, but not the filters... echo "The form is valid\n"; From 3b32a72157ce23d2e835e2e57b1fbbc5b7140014 Mon Sep 17 00:00:00 2001 From: Sasha Alex Romanenko Date: Thu, 16 Aug 2018 17:59:36 -0400 Subject: [PATCH 14/16] Changelog --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e8aa54e1..07f0116a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ All notable changes to this project will be documented in this file, in reverse ### Added +- [#172](https://github.com/zendframework/zend-inputfilter/pull/172) adds support for PSR-7 `UploadedFileInterface` to `Zend\InputFilter\FileInput`. + It adds a new interface, `Zend\InputFilter\FileInput\FileInputDecoratorInterface`, + which defines methods required for validating and filtering file uploads. It + also provides two implementations of it, one for standard SAPI file uploads, + and the other for PSR-7 uploads. The `FileInput` class does detection on the + value being tested and decorates itself using the appropriate decorator, which + then performs the work of validating and filtering the upload or uploads. + - [#170](https://github.com/zendframework/zend-inputfilter/pull/170) adds the ability to set a "required" message on a `CollectionInputFilter`. By default, such instances will lazy-load a `NotEmpty` validator, and use its messages to report that the collection was empty if it is marked as required. From f34ad9087fe334b0f90e2ed9cf2804cfbf1129b4 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Mon, 17 Dec 2018 15:29:33 -0600 Subject: [PATCH 15/16] Bumps branch aliases - dev-master => 2.9.x-dev - dev-develop => 2.10.x-dev --- composer.json | 4 ++-- composer.lock | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index b5cda0fa..d895fafd 100644 --- a/composer.json +++ b/composer.json @@ -12,8 +12,8 @@ }, "extra": { "branch-alias": { - "dev-master": "2.8.x-dev", - "dev-develop": "2.9.x-dev" + "dev-master": "2.9.x-dev", + "dev-develop": "2.10.x-dev" }, "zf": { "component": "Zend\\InputFilter", diff --git a/composer.lock b/composer.lock index bb2c3b52..001e4d10 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "d00b91180d6996b4eb38a9a7a271b9a0", + "content-hash": "b7ceabdec2f140507b9c15f618abc0d9", "packages": [ { "name": "container-interop/container-interop", From 9b3d3436945632e963753f5b7381d79b750c8950 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Mon, 17 Dec 2018 15:30:00 -0600 Subject: [PATCH 16/16] 2.9.0 readiness --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07f0116a..cf459ad3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ All notable changes to this project will be documented in this file, in reverse chronological order by release. -## 2.9.0 - TBD +## 2.9.0 - 2018-12-17 ### Added