1 <?php
2 3 4 5 6 7 8 9
10 class ConfirmedPasswordField extends FormField {
11
12 13 14 15 16
17 public $minLength = null;
18
19 20 21 22 23
24 public $maxLength = null;
25
26 27 28 29 30 31
32 public $requireStrongPassword = false;
33
34 35 36 37 38
39 public $canBeEmpty = false;
40
41 42 43 44 45 46 47 48 49 50 51
52 protected $showOnClick = false;
53
54 55 56 57 58 59
60 public $showOnClickTitle;
61
62 63 64 65 66 67 68 69
70 function __construct($name, $title = null, $value = "", $form = null, $showOnClick = false, $titleConfirmField = null) {
71
72 $this->children = new FieldSet(
73 new PasswordField(
74 "{$name}[_Password]",
75 (isset($title)) ? $title : _t('Member.PASSWORD')),
76 new PasswordField(
77 "{$name}[_ConfirmPassword]",
78 (isset($titleConfirmField)) ? $titleConfirmField : _t('Member.CONFIRMPASSWORD', 'Confirm Password')
79 )
80 );
81
82
83 if($showOnClick) {
84 $this->children->push(new HiddenField("{$name}[_PasswordFieldVisible]"));
85 }
86 $this->showOnClick = $showOnClick;
87
88
89 $title = false;
90
91 parent::__construct($name, $title, null, $form);
92 $this->setValue($value);
93 }
94
95 function Field() {
96 Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/prototype/prototype.js');
97 Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/behaviour/behaviour.js');
98 Requirements::javascript(SAPPHIRE_DIR . '/javascript/prototype_improvements.js');
99 Requirements::javascript(SAPPHIRE_DIR . '/javascript/ConfirmedPasswordField.js');
100
101 $content = '';
102
103 if($this->showOnClick) {
104 if($this->showOnClickTitle) {
105 $title = $this->showOnClickTitle;
106 } else {
107 $title = _t(
108 'ConfirmedPasswordField.SHOWONCLICKTITLE',
109 'Change Password',
110 PR_MEDIUM,
111 'Label of the link which triggers display of the "change password" formfields'
112 );
113 }
114
115 $content .= "<div class=\"showOnClick\">\n";
116 $content .= "<a href=\"#\"" . $this->getTabIndexHTML() . ">{$title}</a>\n";
117 $content .= "<div class=\"showOnClickContainer\">";
118 }
119
120 foreach($this->children as $field) {
121 $field->setDisabled($this->isDisabled());
122 $field->setReadonly($this->isReadonly());
123 $content .= $field->FieldHolder();
124 }
125
126 if($this->showOnClick) {
127 $content .= "</div>\n";
128 $content .= "</div>\n";
129 }
130
131 return $content;
132 }
133
134 135 136 137 138
139 function setCanBeEmpty($value) {
140 $this->canBeEmpty = (bool)$value;
141 }
142
143 144 145 146 147 148 149
150 public function setShowOnClickTitle($title) {
151 $this->showOnClickTitle = $title;
152 }
153
154 155 156
157 public function getShowOnClickTitle() {
158 return $this->showOnClickTitle;
159 }
160
161 function setRightTitle($title) {
162 foreach($this->children as $field) {
163 $field->setRightTitle($title);
164 }
165 }
166
167 168 169
170 function setChildrenTitles($titles) {
171 if(is_array($titles)&&count($titles)==2){
172 foreach($this->children as $field) {
173 if(isset($titles[0])){
174 $field->setTitle($titles[0]);
175 array_shift($titles);
176 }
177 }
178 }
179 }
180
181 182 183
184 function setValue($value) {
185 if(is_array($value)) {
186 if($value['_Password'] || (!$value['_Password'] && !$this->canBeEmpty)) {
187 $this->value = $value['_Password'];
188 }
189 if(isset($value['_PasswordFieldVisible'])){
190 $this->children->fieldByName($this->Name() . '[_PasswordFieldVisible]')->setValue($value['_PasswordFieldVisible']);
191 }
192 } else {
193 if($value || (!$value && !$this->canBeEmpty)) {
194 $this->value = $value;
195 }
196 }
197 $this->children->fieldByName($this->Name() . '[_Password]')->setValue($this->value);
198 $this->children->fieldByName($this->Name() . '[_ConfirmPassword]')->setValue($this->value);
199 }
200
201 function jsValidation() {
202 $formID = $this->form->FormName();
203 $jsTests = '';
204
205 $jsTests .= "
206 // if fields are hidden, reset values and don't validate
207 var containers = $$('.showOnClickContainer', $('#'+fieldName));
208 if(containers.length && !Element.visible(containers[0])) {
209 passEl.value = null;
210 confEl.value = null;
211 return true;
212 }
213 ";
214
215 $error1 = _t('ConfirmedPasswordField.HAVETOMATCH', 'Passwords have to match.');
216 $jsTests .= "
217 if(passEl.value != confEl.value) {
218 validationError(confEl, \"$error1\", \"error\");
219 return false;
220 }
221 ";
222
223 $error2 = _t('ConfirmedPasswordField.NOEMPTY', 'Passwords can\'t be empty.');
224 if(!$this->canBeEmpty) {
225 $jsTests .= "
226 if(!passEl.value || !confEl.value) {
227 validationError(confEl, \"$error2\", \"error\");
228 return false;
229 }
230 ";
231 }
232
233 if(($this->minLength || $this->maxLength)) {
234 if($this->minLength && $this->maxLength) {
235 $limit = "{{$this->minLength},{$this->maxLength}}";
236 $errorMsg = sprintf(_t('ConfirmedPasswordField.BETWEEN', 'Passwords must be %s to %s characters long.'), $this->minLength, $this->maxLength);
237 } elseif($this->minLength) {
238 $limit = "{{$this->minLength}}.*";
239 $errorMsg = sprintf(_t('ConfirmedPasswordField.ATLEAST', 'Passwords must be at least %s characters long.'), $this->minLength);
240 } elseif($this->maxLength) {
241 $limit = "{0,{$this->maxLength}}";
242 $errorMsg = sprintf(_t('ConfirmedPasswordField.MAXIMUM', 'Passwords must be at most %s characters long.'), $this->maxLength);
243 }
244 $limitRegex = '/^.' . $limit . '$/';
245 $jsTests .= "
246 if(passEl.value && !passEl.value.match({$limitRegex})) {
247 validationError(confEl, \"{$errorMsg}\", \"error\");
248 return false;
249 }
250 ";
251 }
252
253 $error3 = _t('ConfirmedPasswordField.LEASTONE', 'Passwords must have at least one digit and one alphanumeric character.');
254 if($this->requireStrongPassword) {
255 $jsTests .= "
256 if(!passEl.value.match(/^(([a-zA-Z]+\d+)|(\d+[a-zA-Z]+))[a-zA-Z0-9]*$/)) {
257 validationError(
258 confEl,
259 \"$error3\",
260 \"error\"
261 );
262 return false;
263 }
264 ";
265 }
266
267 $jsFunc =<<<JS
268 Behaviour.register({
269 "#$formID": {
270 validateConfirmedPassword: function(fieldName) {
271 var passEl = _CURRENT_FORM.elements['Password[_Password]'];
272 var confEl = _CURRENT_FORM.elements['Password[_ConfirmPassword]'];
273 $jsTests
274 return true;
275 }
276 }
277 });
278 JS;
279 Requirements :: customScript($jsFunc, 'func_validateConfirmedPassword_' . $formID);
280
281
282 return <<<JS
283 if(typeof fromAnOnBlur != 'undefined'){
284 if(fromAnOnBlur.name == '$this->name')
285 $('$formID').validateConfirmedPassword('$this->name');
286 }else{
287 $('$formID').validateConfirmedPassword('$this->name');
288 }
289 JS;
290 }
291
292 293 294 295 296 297 298
299 function isSaveable() {
300 $isVisible = $this->children->fieldByName($this->Name() . '[_PasswordFieldVisible]');
301 return (!$this->showOnClick || ($this->showOnClick && $isVisible && $isVisible->Value()));
302 }
303
304 function validate() {
305 $validator = $this->form->getValidator();
306 $name = $this->name;
307
308
309 if(!$this->isSaveable()) return true;
310
311 $passwordField = $this->children->fieldByName($name.'[_Password]');
312 $passwordConfirmField = $this->children->fieldByName($name.'[_ConfirmPassword]');
313 $passwordField->setValue($_POST[$name]['_Password']);
314 $passwordConfirmField->setValue($_POST[$name]['_ConfirmPassword']);
315
316 $value = $passwordField->Value();
317
318
319 if($value != $passwordConfirmField->Value()) {
320 $validator->validationError($name, _t('Form.VALIDATIONPASSWORDSDONTMATCH',"Passwords don't match"), "validation", false);
321 return false;
322 }
323
324 if(!$this->canBeEmpty) {
325
326 if(!$value || !$passwordConfirmField->Value()) {
327 $validator->validationError($name, _t('Form.VALIDATIONPASSWORDSNOTEMPTY', "Passwords can't be empty"), "validation", false);
328 return false;
329 }
330 }
331
332
333 if(($this->minLength || $this->maxLength)) {
334 if($this->minLength && $this->maxLength) {
335 $limit = "{{$this->minLength},{$this->maxLength}}";
336 $errorMsg = sprintf(_t('ConfirmedPasswordField.BETWEEN', 'Passwords must be %s to %s characters long.'), $this->minLength, $this->maxLength);
337 } elseif($this->minLength) {
338 $limit = "{{$this->minLength}}.*";
339 $errorMsg = sprintf(_t('ConfirmedPasswordField.ATLEAST', 'Passwords must be at least %s characters long.'), $this->minLength);
340 } elseif($this->maxLength) {
341 $limit = "{0,{$this->maxLength}}";
342 $errorMsg = sprintf(_t('ConfirmedPasswordField.MAXIMUM', 'Passwords must be at most %s characters long.'), $this->maxLength);
343 }
344 $limitRegex = '/^.' . $limit . '$/';
345 if(!empty($value) && !preg_match($limitRegex,$value)) {
346 $validator->validationError('Password', $errorMsg,
347 "validation",
348 false
349 );
350 }
351 }
352
353 if($this->requireStrongPassword) {
354 if(!preg_match('/^(([a-zA-Z]+\d+)|(\d+[a-zA-Z]+))[a-zA-Z0-9]*$/',$value)) {
355 $validator->validationError(
356 'Password',
357 _t('Form.VALIDATIONSTRONGPASSWORD', "Passwords must have at least one digit and one alphanumeric character."),
358 "validation",
359 false
360 );
361 return false;
362 }
363 }
364 return true;
365 }
366
367 368 369 370 371 372 373
374 function saveInto(DataObject $record) {
375 if(!$this->isSaveable()) return false;
376
377 if(!($this->canBeEmpty && !$this->value)) {
378 parent::saveInto($record);
379 }
380 }
381
382 383 384
385 function performReadonlyTransformation() {
386 $stars = '*****';
387
388 $field = new ReadonlyField($this->name, $this->title ? $this->title : _t('Member.PASSWORD'), $stars);
389 $field->setForm($this->form);
390 return $field;
391 }
392 }
393
[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the
Silverstripe Forums to ask development related questions.
-