FIX Missing syntax opening in readme, and tweaks for pre-emptive userforms compatibility

This commit is contained in:
Robbie Averill 2017-08-28 13:44:53 +12:00
parent a1ea0df540
commit 680411e2ac
4 changed files with 239 additions and 244 deletions

View File

@ -101,6 +101,7 @@ be set as the spam protector. The `getFormField()` method returns the
`FormField` to be inserted into the `Form`. The `FormField` returned should be `FormField` to be inserted into the `Form`. The `FormField` returned should be
in charge of the validation process. in charge of the validation process.
```php
<?php <?php
use CaptchaField; use CaptchaField;

View File

@ -2,17 +2,25 @@
namespace SilverStripe\SpamProtection; namespace SilverStripe\SpamProtection;
use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Convert;
use SilverStripe\Forms\DropdownField; use SilverStripe\Forms\DropdownField;
use SilverStripe\Forms\FieldGroup; use SilverStripe\Forms\FieldGroup;
use SilverStripe\Forms\FieldList; use SilverStripe\Forms\FieldList;
use SilverStripe\SpamProtection\Extension\FormSpamProtectionExtension; use SilverStripe\Forms\FormField;
use SilverStripe\ORM\UnsavedRelationList; use SilverStripe\ORM\UnsavedRelationList;
use SilverStripe\SpamProtection\Extension\FormSpamProtectionExtension;
use SilverStripe\UserForms\Model\EditableFormField;
use SilverStripe\UserForms\Model\EditableFormField\EditableEmailField;
use SilverStripe\UserForms\Model\EditableFormField\EditableNumericField;
use SilverStripe\UserForms\Model\EditableFormField\EditableTextField;
// @todo /**
use EditableEmailField; * @todo The userforms namespaces may still change, as the branch is not merged in yet
use EditableFormField; */
use EditableNumericField; if (!class_exists(EditableFormFields::class)) {
use EditableTextField; return;
}
/** /**
* Editable Spam Protecter Field. Used with the User Defined Forms module (if * Editable Spam Protecter Field. Used with the User Defined Forms module (if
@ -20,257 +28,246 @@ use EditableTextField;
* *
* @package spamprotection * @package spamprotection
*/ */
// @todo update namespaced for userforms when it is 4.0 compatible class EditableSpamProtectionField extends EditableFormField
if (class_exists('EditableFormField')) { {
class EditableSpamProtectionField extends EditableFormField private static $singular_name = 'Spam Protection Field';
private static $plural_name = 'Spam Protection Fields';
private static $table_name = 'EditableSpamProtectionField';
/**
* Fields to include spam detection for
*
* @var array
* @config
*/
private static $check_fields = array(
EditableEmailField::class,
EditableTextField::class,
EditableNumericField::class
);
private static $db = array(
'SpamFieldSettings' => 'Text'
);
/**
* @var FormField
*/
protected $formField = null;
public function getFormField()
{ {
private static $singular_name = 'Spam Protection Field'; if ($this->formField) {
return $this->formField;
private static $plural_name = 'Spam Protection Fields';
private static $table_name = 'EditableSpamProtectionField';
/**
* Fields to include spam detection for
*
* @var array
* @config
*/
private static $check_fields = array(
EditableEmailField::class,
EditableTextField::class,
EditableNumericField::class
);
private static $db = array(
'SpamFieldSettings' => 'Text'
);
/**
* @var FormField
*/
protected $formField = null;
public function getFormField()
{
if ($this->formField) {
return $this->formField;
}
// Get protector
$protector = FormSpamProtectionExtension::get_protector();
if (!$protector) {
return false;
}
// Extract saved field mappings and update this field.
$fieldMapping = array();
foreach ($this->getCandidateFields() as $otherField) {
$mapSetting = "Map-{$otherField->Name}";
$spamField = $this->spamMapValue($mapSetting);
$fieldMapping[$otherField->Name] = $spamField;
}
$protector->setFieldMapping($fieldMapping);
// Generate field
return $protector->getFormField($this->Name, $this->Title, null);
} }
/** // Get protector
* @param FormField $field $protector = FormSpamProtectionExtension::get_protector();
* @return self if (!$protector) {
*/ return false;
public function setFormField(FormField $field)
{
$this->formField = $field;
return $this;
} }
/** // Extract saved field mappings and update this field.
* Gets the list of all candidate spam detectable fields on this field's form $fieldMapping = array();
* foreach ($this->getCandidateFields() as $otherField) {
* @return DataList $mapSetting = "Map-{$otherField->Name}";
*/ $spamField = $this->spamMapValue($mapSetting);
protected function getCandidateFields() $fieldMapping[$otherField->Name] = $spamField;
{ }
$protector->setFieldMapping($fieldMapping);
// Get list of all configured classes available for spam detection // Generate field
$types = self::config()->check_fields; return $protector->getFormField($this->Name, $this->Title, null);
$typesInherit = array(); }
foreach ($types as $type) {
$subTypes = ClassInfo::subclassesFor($type);
$typesInherit = array_merge($typesInherit, $subTypes);
}
// Get all candidates of the above types /**
return $this * @param FormField $field
->Parent() * @return self
->Fields() */
->filter('ClassName', $typesInherit) public function setFormField(FormField $field)
->exclude('Title', ''); // Ignore this field and those without titles {
$this->formField = $field;
return $this;
}
/**
* Gets the list of all candidate spam detectable fields on this field's form
*
* @return DataList
*/
protected function getCandidateFields()
{
// Get list of all configured classes available for spam detection
$types = $this->config()->get('check_fields');
$typesInherit = array();
foreach ($types as $type) {
$subTypes = ClassInfo::subclassesFor($type);
$typesInherit = array_merge($typesInherit, $subTypes);
} }
/** // Get all candidates of the above types
* This method is in place for userforms 2.x return $this
* ->Parent()
* @deprecated 3.0 Please use {@link getCMSFields()} instead ->Fields()
*/ ->filter('ClassName', $typesInherit)
public function getFieldConfiguration() ->exclude('Title', ''); // Ignore this field and those without titles
{ }
return $this->getCMSFields();
/**
* Write the spam field mapping values to a serialised DB field
*
* {@inheritDoc}
*/
public function onBeforeWrite()
{
$fieldMap = Convert::json2array($this->SpamFieldSettings);
if (empty($fieldMap)) {
$fieldMap = array();
} }
/** foreach ($this->record as $key => $value) {
* Write the spam field mapping values to a serialised DB field if (substr($key, 0, 8) === 'spammap-') {
* $fieldMap[substr($key, 8)] = $value;
* {@inheritDoc}
*/
public function onBeforeWrite()
{
$fieldMap = Convert::json2array($this->SpamFieldSettings);
if (empty($fieldMap)) {
$fieldMap = array();
} }
foreach ($this->record as $key => $value) {
if (substr($key, 0, 8) === 'spammap-') {
$fieldMap[substr($key, 8)] = $value;
}
}
$this->setField('SpamFieldSettings', Convert::raw2json($fieldMap));
return parent::onBeforeWrite();
} }
$this->setField('SpamFieldSettings', Convert::raw2json($fieldMap));
/** return parent::onBeforeWrite();
* Used in userforms 3.x and above }
*
* {@inheritDoc}
*/
public function getCMSFields()
{
/** @var FieldList $fields */
$fields = parent::getCMSFields();
// Get protector /**
$protector = FormSpamProtectionExtension::get_protector(); * Used in userforms 3.x and above
if (!$protector) { *
return $fields; * {@inheritDoc}
} */
public function getCMSFields()
if ($this->Parent()->Fields() instanceof UnsavedRelationList) { {
return $fields; /** @var FieldList $fields */
} $fields = parent::getCMSFields();
// Each other text field in this group can be assigned a field mapping
$mapGroup = FieldGroup::create()
->setTitle(_t(__CLASS__.'.SPAMFIELDMAPPING', 'Spam Field Mapping'))
->setName('SpamFieldMapping')
->setDescription(_t(
__CLASS__.'.SPAMFIELDMAPPINGDESCRIPTION',
'Select the form fields that correspond to any relevant spam protection identifiers'
));
// Generate field specific settings
$mappableFields = Config::inst()->get(FormSpamProtectionExtension::class, 'mappable_fields');
$mappableFieldsMerged = array_combine($mappableFields, $mappableFields);
foreach ($this->getCandidateFields() as $otherField) {
$mapSetting = "Map-{$otherField->Name}";
$fieldOption = DropdownField::create(
'spammap-' . $mapSetting,
$otherField->Title,
$mappableFieldsMerged,
$this->spamMapValue($mapSetting)
)->setEmptyString('');
$mapGroup->push($fieldOption);
}
$fields->addFieldToTab('Root.Main', $mapGroup);
// Get protector
$protector = FormSpamProtectionExtension::get_protector();
if (!$protector) {
var_dump('a');
return $fields; return $fields;
} }
/** if ($this->Parent()->Fields() instanceof UnsavedRelationList) {
* Try to retrieve a value for the given spam field map name from the serialised data var_dump('b');
* return $fields;
* @param string $mapSetting
* @return string
*/
public function spamMapValue($mapSetting)
{
$map = Convert::json2array($this->SpamFieldSettings);
if (empty($map)) {
$map = array();
}
if (array_key_exists($mapSetting, $map)) {
return $map[$mapSetting];
}
return '';
} }
/** // Each other text field in this group can be assigned a field mapping
* Using custom validateField method $mapGroup = FieldGroup::create()
* as Spam Protection Field implementations may have their own error messages ->setTitle(_t(__CLASS__.'.SPAMFIELDMAPPING', 'Spam Field Mapping'))
* and may not be based on the field being required, e.g. Honeypot Field ->setName('SpamFieldMapping')
* ->setDescription(_t(
* @param array $data __CLASS__.'.SPAMFIELDMAPPINGDESCRIPTION',
* @param Form $form 'Select the form fields that correspond to any relevant spam protection identifiers'
* @return void ));
*/
public function validateField($data, $form)
{
$formField = $this->getFormField();
$formField->setForm($form);
if (isset($data[$this->Name])) { // Generate field specific settings
$formField->setValue($data[$this->Name]); $mappableFields = FormSpamProtectionExtension::config()->get('mappable_fields');
} $mappableFieldsMerged = array_combine($mappableFields, $mappableFields);
foreach ($this->getCandidateFields() as $otherField) {
$mapSetting = "Map-{$otherField->Name}";
$fieldOption = DropdownField::create(
'spammap-' . $mapSetting,
$otherField->Title,
$mappableFieldsMerged,
$this->spamMapValue($mapSetting)
)->setEmptyString('');
$mapGroup->push($fieldOption);
}
$fields->addFieldToTab('Root.Main', $mapGroup);
$validator = $form->getValidator(); return $fields;
if (!$formField->validate($validator)) { }
$errors = $validator->getErrors();
$foundError = false;
// field validate implementation may not add error to validator /**
if (count($errors) > 0) { * Try to retrieve a value for the given spam field map name from the serialised data
// check if error already added from fields' validate method *
foreach ($errors as $error) { * @param string $mapSetting
if ($error['fieldName'] == $this->Name) { * @return string
$foundError = $error; */
break; public function spamMapValue($mapSetting)
} {
$map = Convert::json2array($this->SpamFieldSettings);
if (empty($map)) {
$map = array();
}
if (array_key_exists($mapSetting, $map)) {
return $map[$mapSetting];
}
return '';
}
/**
* 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();
$formField->setForm($form);
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) { if ($foundError !== false) {
// use error messaging already set from validate method // use error messaging already set from validate method
$form->sessionMessage($foundError['message'], $foundError['messageType']); $form->sessionMessage($foundError['message'], $foundError['messageType']);
} else { } else {
// fallback to custom message set in CMS or default message if none set // fallback to custom message set in CMS or default message if none set
$form->sessionError($this->getErrorMessage()->HTML()); $form->sessionError($this->getErrorMessage()->HTML());
}
} }
} }
}
public function getFieldValidationOptions() public function getFieldValidationOptions()
{ {
return new FieldList(); return FieldList::create();
} }
public function getRequired() public function getRequired()
{ {
return false; return false;
} }
public function getIcon() public function getIcon()
{ {
return 'spamprotection/images/' . strtolower($this->class) . '.png'; return 'spamprotection/images/' . strtolower($this->class) . '.png';
} }
public function showInReports() public function showInReports()
{ {
return false; return false;
}
} }
} }

View File

@ -3,6 +3,7 @@
namespace SilverStripe\SpamProtection\Extension; namespace SilverStripe\SpamProtection\Extension;
use SilverStripe\Core\Config\Config; use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Config\Configurable;
use SilverStripe\Core\Extension; use SilverStripe\Core\Extension;
use SilverStripe\Core\Injector\Injector; use SilverStripe\Core\Injector\Injector;
@ -15,6 +16,8 @@ use SilverStripe\Core\Injector\Injector;
class FormSpamProtectionExtension extends Extension class FormSpamProtectionExtension extends Extension
{ {
use Configurable;
/** /**
* @config * @config
* *

View File

@ -2,14 +2,16 @@
namespace SilverStripe\SpamProtection\Tests; namespace SilverStripe\SpamProtection\Tests;
use UserDefinedForm; use SilverStripe\UserForms\Model\UserDefinedForm;
use SilverStripe\Core\Config\Config; use SilverStripe\Core\Config\Config;
use SilverStripe\Dev\SapphireTest; use SilverStripe\Dev\SapphireTest;
use SilverStripe\Forms\FieldGroup;
use SilverStripe\Forms\Form; use SilverStripe\Forms\Form;
use SilverStripe\Forms\RequiredFields; use SilverStripe\Forms\RequiredFields;
use SilverStripe\Forms\TextField;
use SilverStripe\SpamProtection\EditableSpamProtectionField; use SilverStripe\SpamProtection\EditableSpamProtectionField;
use SilverStripe\SpamProtection\Extension\FormSpamProtectionExtension; use SilverStripe\SpamProtection\Extension\FormSpamProtectionExtension;
use SilverStripe\SpamProtection\Tests\EditableSpamProtectionFieldTest\Protector; use SilverStripe\SpamProtection\Tests\Stub\Protector;
class EditableSpamProtectionFieldTest extends SapphireTest class EditableSpamProtectionFieldTest extends SapphireTest
{ {
@ -19,7 +21,7 @@ class EditableSpamProtectionFieldTest extends SapphireTest
{ {
parent::setUp(); parent::setUp();
if (!class_exists('EditableSpamProtectionField')) { if (!class_exists(EditableSpamProtectionField::class)) {
$this->markTestSkipped('"userforms" module not installed'); $this->markTestSkipped('"userforms" module not installed');
} }
@ -64,12 +66,7 @@ class EditableSpamProtectionFieldTest extends SapphireTest
$formMock $formMock
->expects($this->once()) ->expects($this->once())
->method('sessionMessage') ->method('sessionMessage')
->with( ->with($this->stringContains('some field message'), $this->anything());
$this->anything(),
$this->stringContains('some field message'),
$this->anything(),
$this->anything()
);
$formFieldMock->validateField(array('MyField' => null), $formMock); $formFieldMock->validateField(array('MyField' => null), $formMock);
} }
@ -89,13 +86,8 @@ class EditableSpamProtectionFieldTest extends SapphireTest
$formMock $formMock
->expects($this->once()) ->expects($this->once())
->method('sessionMessage') ->method('sessionError')
->with( ->with($this->stringContains('default error message'));
$this->anything(),
$this->stringContains('default error message'),
$this->anything(),
$this->anything()
);
$formFieldMock->validateField(array('MyField' => null), $formMock); $formFieldMock->validateField(array('MyField' => null), $formMock);
} }
@ -105,7 +97,7 @@ class EditableSpamProtectionFieldTest extends SapphireTest
$field = $this->getEditableFormFieldMock(); $field = $this->getEditableFormFieldMock();
$fields = $field->getCMSFields(); $fields = $field->getCMSFields();
$this->assertInstanceOf('FieldGroup', $fields->fieldByName('Root.Main.SpamFieldMapping')); $this->assertInstanceOf(FieldGroup::class, $fields->fieldByName('Root.Main.SpamFieldMapping'));
} }
public function testSpamMapSettingsAreSerialised() public function testSpamMapSettingsAreSerialised()
@ -121,9 +113,11 @@ class EditableSpamProtectionFieldTest extends SapphireTest
protected function getFormMock() protected function getFormMock()
{ {
$formMock = $this->getMockBuilder(Form::class, array('sessionMessage')) $formMock = $this->getMockBuilder(Form::class)
->setMethods(['sessionMessage', 'sessionError', 'getValidator'])
->disableOriginalConstructor() ->disableOriginalConstructor()
->getMock(); ->getMock();
$formMock $formMock
->expects($this->any()) ->expects($this->any())
->method('getValidator') ->method('getValidator')
@ -137,7 +131,7 @@ class EditableSpamProtectionFieldTest extends SapphireTest
$page = new UserDefinedForm(); $page = new UserDefinedForm();
$page->write(); $page->write();
$formFieldMock = $this->getMockBuilder('TextField') $formFieldMock = $this->getMockBuilder(TextField::class)
->disableOriginalConstructor() ->disableOriginalConstructor()
->getMock(); ->getMock();