1
|
|
|
<?php |
|
|
|
|
2
|
|
|
|
3
|
|
|
namespace SilverStripe\SpamProtection; |
4
|
|
|
|
5
|
|
|
use SilverStripe\Core\ClassInfo; |
6
|
|
|
use SilverStripe\Core\Convert; |
7
|
|
|
use SilverStripe\Forms\DropdownField; |
8
|
|
|
use SilverStripe\Forms\FieldGroup; |
9
|
|
|
use SilverStripe\Forms\FieldList; |
10
|
|
|
use SilverStripe\Forms\FormField; |
11
|
|
|
use SilverStripe\ORM\UnsavedRelationList; |
12
|
|
|
use SilverStripe\SpamProtection\Extension\FormSpamProtectionExtension; |
13
|
|
|
use SilverStripe\UserForms\Model\EditableFormField; |
14
|
|
|
use SilverStripe\UserForms\Model\EditableFormField\EditableEmailField; |
15
|
|
|
use SilverStripe\UserForms\Model\EditableFormField\EditableNumericField; |
16
|
|
|
use SilverStripe\UserForms\Model\EditableFormField\EditableTextField; |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* @todo The userforms namespaces may still change, as the branch is not merged in yet |
20
|
|
|
*/ |
21
|
|
|
if (!class_exists(EditableFormField::class)) { |
22
|
|
|
return; |
23
|
|
|
} |
24
|
|
|
|
25
|
|
|
/** |
26
|
|
|
* Editable Spam Protecter Field. Used with the User Defined Forms module (if |
27
|
|
|
* installed) to allow the user to have captcha fields with their custom forms |
28
|
|
|
* |
29
|
|
|
* @package spamprotection |
30
|
|
|
*/ |
31
|
|
|
class EditableSpamProtectionField extends EditableFormField |
32
|
|
|
{ |
33
|
|
|
private static $singular_name = 'Spam Protection Field'; |
|
|
|
|
34
|
|
|
|
35
|
|
|
private static $plural_name = 'Spam Protection Fields'; |
|
|
|
|
36
|
|
|
|
37
|
|
|
private static $table_name = 'EditableSpamProtectionField'; |
|
|
|
|
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* Fields to include spam detection for |
41
|
|
|
* |
42
|
|
|
* @var array |
43
|
|
|
* @config |
44
|
|
|
*/ |
45
|
|
|
private static $check_fields = array( |
|
|
|
|
46
|
|
|
EditableEmailField::class, |
47
|
|
|
EditableTextField::class, |
48
|
|
|
EditableNumericField::class |
49
|
|
|
); |
50
|
|
|
|
51
|
|
|
private static $db = array( |
|
|
|
|
52
|
|
|
'SpamFieldSettings' => 'Text' |
53
|
|
|
); |
54
|
|
|
|
55
|
|
|
/** |
56
|
|
|
* @var FormField |
57
|
|
|
*/ |
58
|
|
|
protected $formField = null; |
59
|
|
|
|
60
|
|
|
public function getFormField() |
61
|
|
|
{ |
62
|
|
|
if ($this->formField) { |
63
|
|
|
return $this->formField; |
64
|
|
|
} |
65
|
|
|
|
66
|
|
|
// Get protector |
67
|
|
|
$protector = FormSpamProtectionExtension::get_protector(); |
68
|
|
|
if (!$protector) { |
69
|
|
|
return false; |
70
|
|
|
} |
71
|
|
|
|
72
|
|
|
// Extract saved field mappings and update this field. |
73
|
|
|
$fieldMapping = array(); |
74
|
|
|
foreach ($this->getCandidateFields() as $otherField) { |
75
|
|
|
$mapSetting = "Map-{$otherField->Name}"; |
76
|
|
|
$spamField = $this->spamMapValue($mapSetting); |
77
|
|
|
$fieldMapping[$otherField->Name] = $spamField; |
78
|
|
|
} |
79
|
|
|
$protector->setFieldMapping($fieldMapping); |
80
|
|
|
|
81
|
|
|
// Generate field |
82
|
|
|
return $protector->getFormField($this->Name, $this->Title, null); |
83
|
|
|
} |
84
|
|
|
|
85
|
|
|
/** |
86
|
|
|
* @param FormField $field |
87
|
|
|
* @return self |
88
|
|
|
*/ |
89
|
|
|
public function setFormField(FormField $field) |
90
|
|
|
{ |
91
|
|
|
$this->formField = $field; |
92
|
|
|
|
93
|
|
|
return $this; |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
/** |
97
|
|
|
* Gets the list of all candidate spam detectable fields on this field's form |
98
|
|
|
* |
99
|
|
|
* @return DataList |
100
|
|
|
*/ |
101
|
|
|
protected function getCandidateFields() |
102
|
|
|
{ |
103
|
|
|
|
104
|
|
|
// Get list of all configured classes available for spam detection |
105
|
|
|
$types = $this->config()->get('check_fields'); |
106
|
|
|
$typesInherit = array(); |
107
|
|
|
foreach ($types as $type) { |
108
|
|
|
$subTypes = ClassInfo::subclassesFor($type); |
109
|
|
|
$typesInherit = array_merge($typesInherit, $subTypes); |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
// Get all candidates of the above types |
113
|
|
|
return $this |
114
|
|
|
->Parent() |
115
|
|
|
->Fields() |
116
|
|
|
->filter('ClassName', $typesInherit) |
117
|
|
|
->exclude('Title', ''); // Ignore this field and those without titles |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
/** |
121
|
|
|
* Write the spam field mapping values to a serialised DB field |
122
|
|
|
* |
123
|
|
|
* {@inheritDoc} |
124
|
|
|
*/ |
125
|
|
|
public function onBeforeWrite() |
126
|
|
|
{ |
127
|
|
|
$fieldMap = Convert::json2array($this->SpamFieldSettings); |
128
|
|
|
if (empty($fieldMap)) { |
129
|
|
|
$fieldMap = array(); |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
foreach ($this->record as $key => $value) { |
133
|
|
|
if (substr($key, 0, 8) === 'spammap-') { |
134
|
|
|
$fieldMap[substr($key, 8)] = $value; |
135
|
|
|
} |
136
|
|
|
} |
137
|
|
|
$this->setField('SpamFieldSettings', Convert::raw2json($fieldMap)); |
138
|
|
|
|
139
|
|
|
return parent::onBeforeWrite(); |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
/** |
143
|
|
|
* Used in userforms 3.x and above |
144
|
|
|
* |
145
|
|
|
* {@inheritDoc} |
146
|
|
|
*/ |
147
|
|
|
public function getCMSFields() |
148
|
|
|
{ |
149
|
|
|
/** @var FieldList $fields */ |
150
|
|
|
$fields = parent::getCMSFields(); |
151
|
|
|
|
152
|
|
|
// Get protector |
153
|
|
|
$protector = FormSpamProtectionExtension::get_protector(); |
154
|
|
|
if (!$protector) { |
155
|
|
|
var_dump('a'); |
|
|
|
|
156
|
|
|
return $fields; |
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
if ($this->Parent()->Fields() instanceof UnsavedRelationList) { |
160
|
|
|
var_dump('b'); |
161
|
|
|
return $fields; |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
// Each other text field in this group can be assigned a field mapping |
165
|
|
|
$mapGroup = FieldGroup::create() |
166
|
|
|
->setTitle(_t(__CLASS__.'.SPAMFIELDMAPPING', 'Spam Field Mapping')) |
167
|
|
|
->setName('SpamFieldMapping') |
168
|
|
|
->setDescription(_t( |
169
|
|
|
__CLASS__.'.SPAMFIELDMAPPINGDESCRIPTION', |
170
|
|
|
'Select the form fields that correspond to any relevant spam protection identifiers' |
171
|
|
|
)); |
172
|
|
|
|
173
|
|
|
// Generate field specific settings |
174
|
|
|
$mappableFields = FormSpamProtectionExtension::config()->get('mappable_fields'); |
175
|
|
|
$mappableFieldsMerged = array_combine($mappableFields, $mappableFields); |
176
|
|
|
foreach ($this->getCandidateFields() as $otherField) { |
177
|
|
|
$mapSetting = "Map-{$otherField->Name}"; |
178
|
|
|
$fieldOption = DropdownField::create( |
179
|
|
|
'spammap-' . $mapSetting, |
180
|
|
|
$otherField->Title, |
181
|
|
|
$mappableFieldsMerged, |
182
|
|
|
$this->spamMapValue($mapSetting) |
183
|
|
|
)->setEmptyString(''); |
184
|
|
|
$mapGroup->push($fieldOption); |
185
|
|
|
} |
186
|
|
|
$fields->addFieldToTab('Root.Main', $mapGroup); |
187
|
|
|
|
188
|
|
|
return $fields; |
189
|
|
|
} |
190
|
|
|
|
191
|
|
|
/** |
192
|
|
|
* Try to retrieve a value for the given spam field map name from the serialised data |
193
|
|
|
* |
194
|
|
|
* @param string $mapSetting |
195
|
|
|
* @return string |
196
|
|
|
*/ |
197
|
|
|
public function spamMapValue($mapSetting) |
198
|
|
|
{ |
199
|
|
|
$map = Convert::json2array($this->SpamFieldSettings); |
200
|
|
|
if (empty($map)) { |
201
|
|
|
$map = array(); |
202
|
|
|
} |
203
|
|
|
|
204
|
|
|
if (array_key_exists($mapSetting, $map)) { |
205
|
|
|
return $map[$mapSetting]; |
206
|
|
|
} |
207
|
|
|
return ''; |
208
|
|
|
} |
209
|
|
|
|
210
|
|
|
/** |
211
|
|
|
* Using custom validateField method |
212
|
|
|
* as Spam Protection Field implementations may have their own error messages |
213
|
|
|
* and may not be based on the field being required, e.g. Honeypot Field |
214
|
|
|
* |
215
|
|
|
* @param array $data |
216
|
|
|
* @param Form $form |
217
|
|
|
* @return void |
218
|
|
|
*/ |
219
|
|
|
public function validateField($data, $form) |
220
|
|
|
{ |
221
|
|
|
$formField = $this->getFormField(); |
222
|
|
|
$formField->setForm($form); |
223
|
|
|
|
224
|
|
|
if (isset($data[$this->Name])) { |
225
|
|
|
$formField->setValue($data[$this->Name]); |
226
|
|
|
} |
227
|
|
|
|
228
|
|
|
$validator = $form->getValidator(); |
229
|
|
|
if (!$formField->validate($validator)) { |
230
|
|
|
$errors = $validator->getErrors(); |
231
|
|
|
$foundError = false; |
232
|
|
|
|
233
|
|
|
// field validate implementation may not add error to validator |
234
|
|
|
if (count($errors) > 0) { |
235
|
|
|
// check if error already added from fields' validate method |
236
|
|
|
foreach ($errors as $error) { |
237
|
|
|
if ($error['fieldName'] == $this->Name) { |
238
|
|
|
$foundError = $error; |
239
|
|
|
break; |
240
|
|
|
} |
241
|
|
|
} |
242
|
|
|
} |
243
|
|
|
|
244
|
|
|
if ($foundError !== false) { |
245
|
|
|
// use error messaging already set from validate method |
246
|
|
|
$form->sessionMessage($foundError['message'], $foundError['messageType']); |
247
|
|
|
} else { |
248
|
|
|
// fallback to custom message set in CMS or default message if none set |
249
|
|
|
$form->sessionError($this->getErrorMessage()->HTML()); |
250
|
|
|
} |
251
|
|
|
} |
252
|
|
|
} |
253
|
|
|
|
254
|
|
|
public function getFieldValidationOptions() |
255
|
|
|
{ |
256
|
|
|
return FieldList::create(); |
257
|
|
|
} |
258
|
|
|
|
259
|
|
|
public function getRequired() |
260
|
|
|
{ |
261
|
|
|
return false; |
262
|
|
|
} |
263
|
|
|
|
264
|
|
|
public function getIcon() |
265
|
|
|
{ |
266
|
|
|
return 'spamprotection/images/' . strtolower($this->class) . '.png'; |
267
|
|
|
} |
268
|
|
|
|
269
|
|
|
public function showInReports() |
270
|
|
|
{ |
271
|
|
|
return false; |
272
|
|
|
} |
273
|
|
|
} |
274
|
|
|
|
The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.
The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.
To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.