Merge pull request #2537 from wilr/membervalidator

FIX: Use Injector API for managing Member_Validator instance.
This commit is contained in:
Ingo Schommer 2013-10-23 03:01:15 -07:00
commit d7e10e620a
14 changed files with 293 additions and 61 deletions

View File

@ -1,5 +1,9 @@
<?php <?php
/**
* @package framework
* @subpackage admin
*/
class AdminRootController extends Controller { class AdminRootController extends Controller {
/** /**

View File

@ -10,8 +10,8 @@
* _t('CMSBatchActions.PUBLISHED_PAGES', 'published %d pages'))); * _t('CMSBatchActions.PUBLISHED_PAGES', 'published %d pages')));
* </code> * </code>
* *
* @package cms * @package framework
* @subpackage batchaction * @subpackage admin
*/ */
abstract class CMSBatchAction extends Object { abstract class CMSBatchAction extends Object {

View File

@ -3,8 +3,8 @@
/** /**
* Special request handler for admin/batchaction * Special request handler for admin/batchaction
* *
* @package cms * @package framework
* @subpackage batchaction * @subpackage admin
*/ */
class CMSBatchActionHandler extends RequestHandler { class CMSBatchActionHandler extends RequestHandler {

View File

@ -1,6 +1,11 @@
<?php <?php
/** /**
* Deals with special form handling in CMS, mainly around {@link PjaxResponseNegotiator} * Deals with special form handling in CMS, mainly around
* {@link PjaxResponseNegotiator}
*
* @package framework
* @subpackage admin
*/ */
class CMSForm extends Form { class CMSForm extends Form {

View File

@ -1,12 +1,15 @@
<?php <?php
/** /**
* Interface to provide enough information about a record to make it previewable * Interface to provide enough information about a record to make it previewable
* through the CMS. It uses the record database ID, its "frontend" and "backend" links * through the CMS. It uses the record database ID, its "frontend" and "backend"
* to link up the edit form with its preview. * links to link up the edit form with its preview.
* *
* Also used by {@link SilverStripeNavigator} to generate links - * Also used by {@link SilverStripeNavigator} to generate links - both within
* both within the CMS preview, and as a frontend utility * the CMS preview, and as a frontend utility for logged-in CMS authors in
* for logged-in CMS authors in custom themes (with the $SilverStripeNavigator template marker). * custom themes (with the $SilverStripeNavigator template marker).
*
* @package framework
* @subpackage admin
*/ */
interface CMSPreviewable { interface CMSPreviewable {
@ -17,8 +20,9 @@ interface CMSPreviewable {
public function Link(); public function Link();
/** /**
* @return String Absolute URL to the CMS-author view. Should point to a controller subclassing {@link LeftAndMain}. * @return String Absolute URL to the CMS-author view. Should point to a
* Example: http://mysite.com/admin/edit/6 * controller subclassing {@link LeftAndMain}. Example:
* http://mysite.com/admin/edit/6
*/ */
public function CMSEditLink(); public function CMSEditLink();

View File

@ -1,4 +1,9 @@
<?php <?php
/**
* @package framework
* @subpackage admin
*/
class CMSProfileController extends LeftAndMain { class CMSProfileController extends LeftAndMain {
private static $url_segment = 'myprofile'; private static $url_segment = 'myprofile';
@ -22,8 +27,11 @@ class CMSProfileController extends LeftAndMain {
$this->setCurrentPageID(Member::currentUserID()); $this->setCurrentPageID(Member::currentUserID());
$form = parent::getEditForm($id, $fields); $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()->removeByName('LastVisited');
$form->Fields()->push(new HiddenField('ID', null, Member::currentUserID())); $form->Fields()->push(new HiddenField('ID', null, Member::currentUserID()));
$form->Actions()->push( $form->Actions()->push(
@ -32,11 +40,21 @@ class CMSProfileController extends LeftAndMain {
->setAttribute('data-icon', 'accept') ->setAttribute('data-icon', 'accept')
->setUseButtonTag(true) ->setUseButtonTag(true)
); );
$form->Actions()->removeByName('action_delete'); $form->Actions()->removeByName('action_delete');
$form->setValidator(new Member_Validator());
$form->setTemplate('Form'); $form->setTemplate('Form');
$form->setAttribute('data-pjax-fragment', null); $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'); $form->addExtraClass('member-profile-form root-form cms-edit-form cms-panel-padded center');
return $form; return $form;

View File

@ -1,10 +1,11 @@
<?php <?php
/** /**
* Imports {@link Group} records by CSV upload, as defined in * Imports {@link Group} records by CSV upload, as defined in
* {@link GroupCsvBulkLoader}. * {@link GroupCsvBulkLoader}.
* *
* @package cms * @package framework
* @subpackage batchactions * @subpackage admin
*/ */
class GroupImportForm extends Form { class GroupImportForm extends Form {

View File

@ -1,4 +1,5 @@
<?php <?php
/** /**
* Plug-ins for additional functionality in your LeftAndMain classes. * Plug-ins for additional functionality in your LeftAndMain classes.
* *

View File

@ -1,10 +1,11 @@
<?php <?php
/** /**
* Imports {@link Member} records by CSV upload, as defined in * Imports {@link Member} records by CSV upload, as defined in
* {@link MemberCsvBulkLoader}. * {@link MemberCsvBulkLoader}.
* *
* @package cms * @package framework
* @subpackage batchactions * @subpackage admin
*/ */
class MemberImportForm extends Form { class MemberImportForm extends Form {

View File

@ -1,8 +1,10 @@
<?php <?php
/** /**
* Security section of the CMS * Security section of the CMS
* @package cms *
* @subpackage security * @package framework
* @subpackage admin
*/ */
class SecurityAdmin extends LeftAndMain implements PermissionProvider { class SecurityAdmin extends LeftAndMain implements PermissionProvider {
@ -60,7 +62,10 @@ class SecurityAdmin extends LeftAndMain implements PermissionProvider {
// TODO Duplicate record fetching (see parent implementation) // TODO Duplicate record fetching (see parent implementation)
$record = $this->getRecord($id); $record = $this->getRecord($id);
if($record && !$record->canView()) return Security::permissionFailure($this);
if($record && !$record->canView()) {
return Security::permissionFailure($this);
}
$memberList = GridField::create( $memberList = GridField::create(
'Members', 'Members',
@ -70,7 +75,16 @@ class SecurityAdmin extends LeftAndMain implements PermissionProvider {
->addComponent(new GridFieldButtonRow('after')) ->addComponent(new GridFieldButtonRow('after'))
->addComponent(new GridFieldExportButton('buttons-after-left')) ->addComponent(new GridFieldExportButton('buttons-after-left'))
)->addExtraClass("members_grid"); )->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( $groupList = GridField::create(
'Groups', 'Groups',

View File

@ -1,10 +1,12 @@
<?php <?php
/** /**
* Required Fields allows you to set which fields * Required Fields allows you to set which fields need to be present before
* need to be present before submitting the form * submitting the form. Submit an array of arguments or each field as a separate
* Submit an array of arguments or each field as a * argument.
* seperate argument. Validation is performed on a name by *
* name basis. * Validation is performed on a field by field basis through
* {@link FormField::validate}.
* *
* @package forms * @package forms
* @subpackage validators * @subpackage validators
@ -15,8 +17,8 @@ class RequiredFields extends Validator {
protected $useLabels = true; protected $useLabels = true;
/** /**
* Pass each field to be validated as a seperate argument * Pass each field to be validated as a seperate argument to the constructor
* to the constructor of this object. (an array of elements are ok) * of this object. (an array of elements are ok).
*/ */
public function __construct() { public function __construct() {
$required = func_get_args(); $required = func_get_args();
@ -40,9 +42,12 @@ class RequiredFields extends Validator {
/** /**
* Clears all the validation from this object. * Clears all the validation from this object.
*
* @return RequiredFields
*/ */
public function removeValidation(){ public function removeValidation() {
$this->required = array(); $this->required = array();
return $this; return $this;
} }
@ -50,7 +55,9 @@ class RequiredFields extends Validator {
* Debug helper * Debug helper
*/ */
public function debug() { public function debug() {
if(!is_array($this->required)) return false; if(!is_array($this->required)) {
return false;
}
$result = "<ul>"; $result = "<ul>";
foreach( $this->required as $name ){ 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 * Allows validation of fields via specification of a php function for
* the form is submitted * validation which is executed after the form is submitted.
*/ *
* @param array $data
*
* @return boolean
*/
public function php($data) { public function php($data) {
$valid = true; $valid = true;
$fields = $this->form->Fields(); $fields = $this->form->Fields();
foreach($fields as $field) { foreach($fields as $field) {
$valid = ($field->validate($this) && $valid); $valid = ($field->validate($this) && $valid);
} }
if($this->required) { if($this->required) {
foreach($this->required as $fieldName) { 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; $error = true;
// submitted data for file upload fields come back as an array // submitted data for file upload fields come back as an array
$value = isset($data[$fieldName]) ? $data[$fieldName] : null; $value = isset($data[$fieldName]) ? $data[$fieldName] : null;
if(is_array($value)) { if(is_array($value)) {
if($formField instanceof FileField && isset($value['error']) && $value['error']) { if($formField instanceof FileField && isset($value['error']) && $value['error']) {
$error = true; $error = true;
@ -106,11 +128,13 @@ class RequiredFields extends Validator {
if($msg = $formField->getCustomValidationMessage()) { if($msg = $formField->getCustomValidationMessage()) {
$errorMessage = $msg; $errorMessage = $msg;
} }
$this->validationError( $this->validationError(
$fieldName, $fieldName,
$errorMessage, $errorMessage,
"required" "required"
); );
$valid = false; $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; $this->required[$field] = $field;
return $this;
}
public function removeRequiredField($field) {
unset($this->required[$field]);
return $this; 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){ public function removeRequiredField($field) {
$this->required = $this->required + ArrayLib::valuekey($requiredFields->getRequired()); 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; return $this;
} }
/** /**
* Returns true if the named field is "required". * 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) { public function fieldIsRequired($fieldName) {
return isset($this->required[$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); return array_values($this->required);
} }
} }

View File

@ -611,8 +611,21 @@ class Member extends DataObject implements TemplateGlobalProvider {
return $fields; 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() { 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() { public static function currentUser() {
$id = Member::currentUserID(); $id = Member::currentUserID();
if($id) { if($id) {
return DataObject::get_one("Member", "\"Member\".\"ID\" = $id", true, 1); 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 * Class used as template to send an email saying that the password has been
* changed * changed.
*
* @package framework * @package framework
* @subpackage security * @subpackage security
*/ */
class Member_ChangePasswordEmail extends Email { class Member_ChangePasswordEmail extends Email {
protected $from = ''; // setting a blank from address uses the site's default administrator email protected $from = ''; // setting a blank from address uses the site's default administrator email
protected $subject = ''; protected $subject = '';
protected $ss_template = 'ChangePasswordEmail'; protected $ss_template = 'ChangePasswordEmail';
public function __construct() { public function __construct() {
parent::__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 * Class used as template to send the forgot password email
*
* @package framework * @package framework
* @subpackage security * @subpackage security
*/ */
@ -1554,18 +1572,29 @@ class Member_ForgotPasswordEmail extends Email {
public function __construct() { public function __construct() {
parent::__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 * 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 * @package framework
* @subpackage security * @subpackage security
*/ */
class Member_Validator extends RequiredFields { 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() { public function __construct() {
$required = func_get_args(); $required = func_get_args();
if(isset($required[0]) && is_array($required[0])) { if(isset($required[0]) && is_array($required[0])) {
$required = $required[0]; $required = $required[0];
} }
$required = array_merge($required, $this->customRequired); $required = array_merge($required, $this->customRequired);
parent::__construct($required); parent::__construct($required);
} }
/** /**
* Check if the submitted member data is valid (server-side) * Check if the submitted member data is valid (server-side)
* *

View File

@ -1,4 +1,9 @@
<?php <?php
/**
* @package framework
* @subpackage tests
*/
class CMSProfileControllerTest extends FunctionalTest { class CMSProfileControllerTest extends FunctionalTest {
protected static $fixture_file = 'CMSProfileControllerTest.yml'; protected static $fixture_file = 'CMSProfileControllerTest.yml';
@ -74,6 +79,11 @@ class CMSProfileControllerTest extends FunctionalTest {
} }
} }
/**
* @package framework
* @subpackage tests
*/
class CMSProfileControllerTestExtension extends DataExtension { class CMSProfileControllerTestExtension extends DataExtension {
public function canEdit($member = null) { public function canEdit($member = null) {

View File

@ -42,10 +42,11 @@ class MemberTest extends FunctionalTest {
public function tearDown() { public function tearDown() {
Member::config()->unique_identifier_field = $this->orig['Member_unique_identifier_field']; Member::config()->unique_identifier_field = $this->orig['Member_unique_identifier_field'];
parent::tearDown(); parent::tearDown();
} }
/** /**
* @expectedException ValidationException * @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 { class MemberTest_ViewingAllowedExtension extends DataExtension implements TestOnly {
public function canView($member = null) { public function canView($member = null) {
return true; return true;
} }
} }
/**
* @package framework
* @subpackage tests
*/
class MemberTest_ViewingDeniedExtension extends DataExtension implements TestOnly { class MemberTest_ViewingDeniedExtension extends DataExtension implements TestOnly {
public function canView($member = null) { public function canView($member = null) {
return false; return false;
} }
} }
/**
* @package framework
* @subpackage tests
*/
class MemberTest_EditingAllowedDeletingDeniedExtension extends DataExtension implements TestOnly { class MemberTest_EditingAllowedDeletingDeniedExtension extends DataExtension implements TestOnly {
public function canView($member = null) { public function canView($member = null) {
@ -729,6 +819,10 @@ class MemberTest_EditingAllowedDeletingDeniedExtension extends DataExtension imp
} }
/**
* @package framework
* @subpackage tests
*/
class MemberTest_PasswordValidator extends PasswordValidator { class MemberTest_PasswordValidator extends PasswordValidator {
public function __construct() { public function __construct() {
parent::__construct(); parent::__construct();