diff --git a/.travis.yml b/.travis.yml index 0ac3886..0229e29 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,6 +31,7 @@ before_script: - php ~/travis-support/travis_setup.php --source `pwd` --target ~/builds/ss - cd ~/builds/ss - composer install + - composer require silverstripe/userforms script: - vendor/bin/phpunit spamprotection/tests diff --git a/code/EditableSpamProtectionField.php b/code/EditableSpamProtectionField.php index 131de2a..6d9f64c 100644 --- a/code/EditableSpamProtectionField.php +++ b/code/EditableSpamProtectionField.php @@ -24,8 +24,17 @@ if (class_exists('EditableFormField')) { 'EditableNumericField' ); + /** + * @var FormField + */ + protected $formField = null; + public function getFormField() { + if ($this->formField) { + return $this->formField; + } + // Get protector $protector = FormSpamProtectionExtension::get_protector(); if (!$protector) { @@ -45,6 +54,17 @@ if (class_exists('EditableFormField')) { return $protector->getFormField($this->Name, $this->Title, null); } + /** + * @param FormField $field + * @return self + */ + public function setFormField(FormField $field) + { + $this->formField = $field; + + return $this; + } + /** * Gets the list of all candidate spam detectable fields on this field's form * @@ -110,11 +130,46 @@ if (class_exists('EditableFormField')) { return $fields; } - public function validateField($data, $form) + /** + * Using custom validateField method + * as Spam Protection Field implementations may have their own error messages + * and may not be based on the field being required, e.g. Honeypot Field + * + * @param array $data + * @param Form $form + * @return void + */ + public function validateField($data, $form) { $formField = $this->getFormField(); - if (!$formField->validate($form->getValidator())) { - $form->addErrorMessage($this->Name, $this->getErrorMessage()->HTML(), 'error', false); + + if (isset($data[$this->Name])) { + $formField->setValue($data[$this->Name]); + } + + $validator = $form->getValidator(); + if (!$formField->validate($validator)) { + $errors = $validator->getErrors(); + $foundError = false; + + // field validate implementation may not add error to validator + if (count($errors) > 0) { + // check if error already added from fields' validate method + foreach ($errors as $error) { + if ($error['fieldName'] == $this->Name) { + $foundError = $error; + break; + } + } + } + + if ($foundError !== false) { + // use error messaging already set from validate method + $form->addErrorMessage($this->Name, $foundError['message'], $foundError['messageType'], false); + } else { + // fallback to custom message set in CMS or default message if none set + $form->addErrorMessage($this->Name, $this->getErrorMessage()->HTML(), 'error', false); + } } } diff --git a/tests/EditableSpamProtectionFieldTest.php b/tests/EditableSpamProtectionFieldTest.php new file mode 100644 index 0000000..fb227ed --- /dev/null +++ b/tests/EditableSpamProtectionFieldTest.php @@ -0,0 +1,135 @@ +update( + 'FormSpamProtectionExtension', 'default_spam_protector', + 'EditableSpamProtectionFieldTest_Protector' + ); + } + + public function testValidateFieldDoesntAddErrorOnSuccess() + { + if (!class_exists('EditableSpamProtectionField')) { + $this->markTestSkipped('"userforms" module not installed'); + } + + $formMock = $this->getFormMock(); + $formFieldMock = $this->getEditableFormFieldMock(); + + $formFieldMock + ->getFormField() // mock + ->expects($this->once()) + ->method('validate') + ->will($this->returnValue(true)); + + $formMock + ->expects($this->never()) + ->method('addErrorMessage'); + + $formFieldMock->validateField(array('MyField' => null), $formMock); + } + + public function testValidateFieldAddsErrorFromField() + { + if (!class_exists('EditableSpamProtectionField')) { + $this->markTestSkipped('"userforms" module not installed'); + } + + $formMock = $this->getFormMock(); + $formFieldMock = $this->getEditableFormFieldMock(); + + $formFieldMock + ->getFormField() // mock + ->expects($this->once()) + ->method('validate') + ->will($this->returnValue(false)); + + $formMock->getValidator()->validationError('MyField', 'some field message', 'required'); + + $formMock + ->expects($this->once()) + ->method('addErrorMessage') + ->with($this->anything(), $this->stringContains('some field message'), $this->anything(), $this->anything());; + + $formFieldMock->validateField(array('MyField' => null), $formMock); + } + + public function testValidateFieldAddsDefaultError() + { + if (!class_exists('EditableSpamProtectionField')) { + $this->markTestSkipped('"userforms" module not installed'); + } + + $formMock = $this->getFormMock(); + $formFieldMock = $this->getEditableFormFieldMock(); + + $formFieldMock + ->getFormField() // mock + ->expects($this->once()) + ->method('validate') + ->will($this->returnValue(false)); + + // field doesn't set any validation errors here + + $formMock + ->expects($this->once()) + ->method('addErrorMessage') + ->with($this->anything(), $this->stringContains('default error message'), $this->anything(), $this->anything()); + + $formFieldMock->validateField(array('MyField' => null), $formMock); + } + + protected function getFormMock() + { + $formMock = $this->getMockBuilder('Form', array('addErrorMessage')) + ->disableOriginalConstructor() + ->getMock(); + $formMock + ->expects($this->any()) + ->method('getValidator') + ->will($this->returnValue(new RequiredFields())); + + return $formMock; + } + + protected function getEditableFormFieldMock() + { + $page = new UserDefinedForm(); + $page->write(); + + $formFieldMock = $this->getMockBuilder('TextField') + ->disableOriginalConstructor() + ->getMock(); + + $editableFormFieldMock = new EditableSpamProtectionField(array( + 'ParentID' => $page->ID, + 'Name' => 'MyField', + 'CustomErrorMessage' => 'default error message' + )); + $editableFormFieldMock->write(); + $editableFormFieldMock->setFormField($formFieldMock); + + return $editableFormFieldMock; + } + +} + +class EditableSpamProtectionFieldTest_Protector implements SpamProtector, TestOnly +{ + public function getFormField($name = null, $title = null, $value = null) + { + return new TextField($name, 'Foo', $value); + } + + public function setFieldMapping($fieldMapping) + { + } +}