diff --git a/admin/code/AdminRootController.php b/admin/code/AdminRootController.php
index 765500c03..77a394ce6 100644
--- a/admin/code/AdminRootController.php
+++ b/admin/code/AdminRootController.php
@@ -1,5 +1,9 @@
*
- * @package cms
- * @subpackage batchaction
+ * @package framework
+ * @subpackage admin
*/
abstract class CMSBatchAction extends Object {
diff --git a/admin/code/CMSBatchActionHandler.php b/admin/code/CMSBatchActionHandler.php
index d3229e694..a8854e662 100644
--- a/admin/code/CMSBatchActionHandler.php
+++ b/admin/code/CMSBatchActionHandler.php
@@ -3,8 +3,8 @@
/**
* Special request handler for admin/batchaction
*
- * @package cms
- * @subpackage batchaction
+ * @package framework
+ * @subpackage admin
*/
class CMSBatchActionHandler extends RequestHandler {
diff --git a/admin/code/CMSForm.php b/admin/code/CMSForm.php
index a03d51178..39641a95c 100644
--- a/admin/code/CMSForm.php
+++ b/admin/code/CMSForm.php
@@ -1,6 +1,11 @@
setCurrentPageID(Member::currentUserID());
$form = parent::getEditForm($id, $fields);
- if($form instanceof SS_HTTPResponse) return $form;
+ if($form instanceof SS_HTTPResponse) {
+ return $form;
+ }
+
$form->Fields()->removeByName('LastVisited');
$form->Fields()->push(new HiddenField('ID', null, Member::currentUserID()));
$form->Actions()->push(
@@ -32,11 +40,21 @@ class CMSProfileController extends LeftAndMain {
->setAttribute('data-icon', 'accept')
->setUseButtonTag(true)
);
+
$form->Actions()->removeByName('action_delete');
- $form->setValidator(new Member_Validator());
$form->setTemplate('Form');
$form->setAttribute('data-pjax-fragment', null);
- if($form->Fields()->hasTabset()) $form->Fields()->findOrMakeTab('Root')->setTemplate('CMSTabSet');
+
+ if($member = Member::currentUser()) {
+ $form->setValidator($member->getValidator());
+ } else {
+ $form->setValidator(Injector::inst()->get('Member')->getValidator());
+ }
+
+ if($form->Fields()->hasTabset()) {
+ $form->Fields()->findOrMakeTab('Root')->setTemplate('CMSTabSet');
+ }
+
$form->addExtraClass('member-profile-form root-form cms-edit-form cms-panel-padded center');
return $form;
diff --git a/admin/code/GroupImportForm.php b/admin/code/GroupImportForm.php
index e4271f002..a544e8373 100644
--- a/admin/code/GroupImportForm.php
+++ b/admin/code/GroupImportForm.php
@@ -1,10 +1,11 @@
getRecord($id);
- if($record && !$record->canView()) return Security::permissionFailure($this);
+
+ if($record && !$record->canView()) {
+ return Security::permissionFailure($this);
+ }
$memberList = GridField::create(
'Members',
@@ -70,7 +75,16 @@ class SecurityAdmin extends LeftAndMain implements PermissionProvider {
->addComponent(new GridFieldButtonRow('after'))
->addComponent(new GridFieldExportButton('buttons-after-left'))
)->addExtraClass("members_grid");
- $memberListConfig->getComponentByType('GridFieldDetailForm')->setValidator(new Member_Validator());
+
+ if($record && method_exists($record, 'getValidator')) {
+ $validator = $record->getValidator();
+ } else {
+ $validator = Injector::inst()->get('Member')->getValidator();
+ }
+
+ $memberListConfig
+ ->getComponentByType('GridFieldDetailForm')
+ ->setValidator($validator);
$groupList = GridField::create(
'Groups',
diff --git a/forms/RequiredFields.php b/forms/RequiredFields.php
index 1e3adbd08..960a2bcde 100644
--- a/forms/RequiredFields.php
+++ b/forms/RequiredFields.php
@@ -1,10 +1,12 @@
required = array();
+
return $this;
}
@@ -50,7 +55,9 @@ class RequiredFields extends Validator {
* Debug helper
*/
public function debug() {
- if(!is_array($this->required)) return false;
+ if(!is_array($this->required)) {
+ return false;
+ }
$result = "
";
foreach( $this->required as $name ){
@@ -62,25 +69,40 @@ class RequiredFields extends Validator {
}
/**
- * Allows validation of fields via specification of a php function for validation which is executed after
- * the form is submitted
- */
+ * Allows validation of fields via specification of a php function for
+ * validation which is executed after the form is submitted.
+ *
+ * @param array $data
+ *
+ * @return boolean
+ */
public function php($data) {
$valid = true;
-
$fields = $this->form->Fields();
+
foreach($fields as $field) {
$valid = ($field->validate($this) && $valid);
}
+
if($this->required) {
foreach($this->required as $fieldName) {
- if(!$fieldName) continue;
+ if(!$fieldName) {
+ continue;
+ }
- $formField = $fields->dataFieldByName($fieldName);
+ if($fieldName instanceof FormField) {
+ $formField = $fieldName;
+ $fieldName = $fieldName->getName();
+ }
+ else {
+ $formField = $fields->dataFieldByName($fieldName);
+ }
$error = true;
+
// submitted data for file upload fields come back as an array
$value = isset($data[$fieldName]) ? $data[$fieldName] : null;
+
if(is_array($value)) {
if($formField instanceof FileField && isset($value['error']) && $value['error']) {
$error = true;
@@ -106,11 +128,13 @@ class RequiredFields extends Validator {
if($msg = $formField->getCustomValidationMessage()) {
$errorMessage = $msg;
}
+
$this->validationError(
$fieldName,
$errorMessage,
"required"
);
+
$valid = false;
}
}
@@ -120,40 +144,66 @@ class RequiredFields extends Validator {
}
/**
- * Add's a single required field to requiredfields stack
+ * Adds a single required field to required fields stack.
+ *
+ * @param string $field
+ *
+ * @return RequiredFields
*/
- public function addRequiredField( $field ) {
+ public function addRequiredField($field) {
$this->required[$field] = $field;
- return $this;
- }
- public function removeRequiredField($field) {
- unset($this->required[$field]);
return $this;
}
/**
- * allows you too add more required fields to this object after construction.
+ * Removes a required field
+ *
+ * @param string $field
+ *
+ * @return RequiredFields
*/
- public function appendRequiredFields($requiredFields){
- $this->required = $this->required + ArrayLib::valuekey($requiredFields->getRequired());
+ public function removeRequiredField($field) {
+ unset($this->required[$field]);
+
+ return $this;
+ }
+
+ /**
+ * Add {@link RequiredField} objects together
+ *
+ * @param RequiredFields
+ *
+ * @return RequiredFields
+ */
+ public function appendRequiredFields($requiredFields) {
+ $this->required = $this->required + ArrayLib::valuekey(
+ $requiredFields->getRequired()
+ );
+
return $this;
}
/**
* Returns true if the named field is "required".
- * Used by FormField to return a value for FormField::Required(), to do things like show *s on the form template.
+ *
+ * Used by {@link FormField} to return a value for FormField::Required(),
+ * to do things like show *s on the form template.
+ *
+ * @param string $fieldName
+ *
+ * @return boolean
*/
public function fieldIsRequired($fieldName) {
return isset($this->required[$fieldName]);
}
/**
- * getter function for append
+ * Return the required fields
+ *
+ * @return array
*/
- public function getRequired(){
+ public function getRequired() {
return array_values($this->required);
}
}
-
-
diff --git a/security/Member.php b/security/Member.php
index a4f212a9f..8dac6ff40 100644
--- a/security/Member.php
+++ b/security/Member.php
@@ -611,8 +611,21 @@ class Member extends DataObject implements TemplateGlobalProvider {
return $fields;
}
+ /**
+ * Returns the {@link RequiredFields} instance for the Member object. This
+ * Validator is used when saving a {@link CMSProfileController} or added to
+ * any form responsible for saving a users data.
+ *
+ * To customize the required fields, add a {@link DataExtension} to member
+ * calling the `updateValidator()` method.
+ *
+ * @return Member_Validator
+ */
public function getValidator() {
- return new Member_Validator();
+ $validator = Injector::inst()->create('Member_Validator');
+ $this->extend('updateValidator', $validator);
+
+ return $validator;
}
@@ -624,6 +637,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
*/
public static function currentUser() {
$id = Member::currentUserID();
+
if($id) {
return DataObject::get_one("Member", "\"Member\".\"ID\" = $id", true, 1);
}
@@ -1525,18 +1539,21 @@ class Member_GroupSet extends ManyManyList {
/**
* Class used as template to send an email saying that the password has been
- * changed
+ * changed.
+ *
* @package framework
* @subpackage security
*/
class Member_ChangePasswordEmail extends Email {
+
protected $from = ''; // setting a blank from address uses the site's default administrator email
protected $subject = '';
protected $ss_template = 'ChangePasswordEmail';
public function __construct() {
parent::__construct();
- $this->subject = _t('Member.SUBJECTPASSWORDCHANGED', "Your password has been changed", 'Email subject');
+
+ $this->subject = _t('Member.SUBJECTPASSWORDCHANGED', "Your password has been changed", 'Email subject');
}
}
@@ -1544,6 +1561,7 @@ class Member_ChangePasswordEmail extends Email {
/**
* Class used as template to send the forgot password email
+ *
* @package framework
* @subpackage security
*/
@@ -1554,18 +1572,29 @@ class Member_ForgotPasswordEmail extends Email {
public function __construct() {
parent::__construct();
- $this->subject = _t('Member.SUBJECTPASSWORDRESET', "Your password reset link", 'Email subject');
+
+ $this->subject = _t('Member.SUBJECTPASSWORDRESET', "Your password reset link", 'Email subject');
}
}
/**
* Member Validator
+ *
+ * Custom validation for the Member object can be achieved either through an
+ * {@link DataExtension} on the Member object or, by specifying a subclass of
+ * {@link Member_Validator} through the {@link Injector} API.
+ *
+ * {@see Member::getValidator()}
+ *
* @package framework
* @subpackage security
*/
class Member_Validator extends RequiredFields {
- protected $customRequired = array('FirstName', 'Email'); //, 'Password');
+ protected $customRequired = array(
+ 'FirstName',
+ 'Email'
+ );
/**
@@ -1573,15 +1602,16 @@ class Member_Validator extends RequiredFields {
*/
public function __construct() {
$required = func_get_args();
+
if(isset($required[0]) && is_array($required[0])) {
$required = $required[0];
}
+
$required = array_merge($required, $this->customRequired);
parent::__construct($required);
}
-
/**
* Check if the submitted member data is valid (server-side)
*
diff --git a/tests/control/CMSProfileControllerTest.php b/tests/control/CMSProfileControllerTest.php
index d5611c3f3..9545a2c9a 100644
--- a/tests/control/CMSProfileControllerTest.php
+++ b/tests/control/CMSProfileControllerTest.php
@@ -1,4 +1,9 @@
unique_identifier_field = $this->orig['Member_unique_identifier_field'];
-
parent::tearDown();
}
+
+
/**
* @expectedException ValidationException
*/
@@ -698,21 +699,110 @@ class MemberTest extends FunctionalTest {
);
}
+
+ public function testCustomMemberValidator() {
+ $member = $this->objFromFixture('Member', 'admin');
+
+ $form = new MemberTest_ValidatorForm();
+ $form->loadDataFrom($member);
+
+ $validator = new Member_Validator();
+ $validator->setForm($form);
+
+ $pass = $validator->php(array(
+ 'FirstName' => 'Borris',
+ 'Email' => 'borris@silverstripe.com'
+ ));
+
+ $fail = $validator->php(array(
+ 'Email' => 'borris@silverstripe.com',
+ 'Surname' => ''
+ ));
+
+ $this->assertTrue($pass, 'Validator requires on FirstName and Email');
+ $this->assertFalse($fail, 'Missing FirstName');
+
+ $ext = new MemberTest_ValidatorExtension();
+ $ext->updateValidator($validator);
+
+ $pass = $validator->php(array(
+ 'FirstName' => 'Borris',
+ 'Email' => 'borris@silverstripe.com'
+ ));
+
+ $fail = $validator->php(array(
+ 'Email' => 'borris@silverstripe.com'
+ ));
+
+ $this->assertFalse($pass, 'Missing surname');
+ $this->assertFalse($fail, 'Missing surname value');
+
+ $fail = $validator->php(array(
+ 'Email' => 'borris@silverstripe.com',
+ 'Surname' => 'Silverman'
+ ));
+
+ $this->assertTrue($fail, 'Passes with email and surname now (no firstname)');
+ }
+
}
+
+/**
+ * @package framework
+ * @subpackage tests
+ */
+class MemberTest_ValidatorForm extends Form implements TestOnly {
+
+ public function __construct() {
+ parent::__construct(Controller::curr(), __CLASS__, new FieldList(
+ new TextField('Email'),
+ new TextField('Surname'),
+ new TextField('ID'),
+ new TextField('FirstName')
+ ), new FieldList(
+ new FormAction('someAction')
+ ));
+ }
+}
+
+/**
+ * @package framework
+ * @subpackage tests
+ */
+class MemberTest_ValidatorExtension extends DataExtension implements TestOnly {
+
+ public function updateValidator(&$validator) {
+ $validator->addRequiredField('Surname');
+ $validator->removeRequiredField('FirstName');
+ }
+}
+
+/**
+ * @package framework
+ * @subpackage tests
+ */
class MemberTest_ViewingAllowedExtension extends DataExtension implements TestOnly {
public function canView($member = null) {
return true;
}
-
}
+
+/**
+ * @package framework
+ * @subpackage tests
+ */
class MemberTest_ViewingDeniedExtension extends DataExtension implements TestOnly {
public function canView($member = null) {
return false;
}
-
}
+
+/**
+ * @package framework
+ * @subpackage tests
+ */
class MemberTest_EditingAllowedDeletingDeniedExtension extends DataExtension implements TestOnly {
public function canView($member = null) {
@@ -729,6 +819,10 @@ class MemberTest_EditingAllowedDeletingDeniedExtension extends DataExtension imp
}
+/**
+ * @package framework
+ * @subpackage tests
+ */
class MemberTest_PasswordValidator extends PasswordValidator {
public function __construct() {
parent::__construct();