Merge remote-tracking branch 'origin/3'

# Conflicts:
#	forms/gridfield/GridFieldDetailForm.php
#	javascript/GridField.js
This commit is contained in:
Ingo Schommer 2016-03-07 09:26:35 +13:00
commit 3b11d73c46
6 changed files with 252 additions and 54 deletions

View File

@ -1293,7 +1293,9 @@ class LeftAndMain extends Controller implements PermissionProvider {
// The clientside (mainly LeftAndMain*.js) rely on ajax responses
// which can be evaluated as javascript, hence we need
// to override any global changes to the validation handler.
$form->setValidator($validator);
if($validator != NULL){
$form->setValidator($validator);
}
} else {
$form->unsetValidator();
}

View File

@ -379,15 +379,9 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler {
if(!$fields) $fields = $this->record->getCMSFields();
// If we are creating a new record in a has-many list, then
// pre-populate the record's foreign key. Also disable the form field as
// it has no effect.
// Disable the form field as it has no effect.
if($list instanceof HasManyList) {
$key = $list->getForeignKey();
$id = $list->getForeignID();
if(!$this->record->isInDB()) {
$this->record->$key = $id;
}
if($field = $fields->dataFieldByName($key)) {
$fields->makeFieldReadonly($field);

View File

@ -318,9 +318,7 @@ it:
PAGE: Pagina
SUBJECT: 'Oggetto email'
URL: URL
URLDESCRIPTION: 'Inserisci video e immagini dal Web nella tua pagina semplicemente inserendo l''URL del file.
Si sicuro di avere i diritti o i permessi prima di condividere media direttamente dal Web.<br /><br />NB : i file non sono aggiunti allo storage file del CMS, ma incorpora il file dalla sua location principale, se per un qualsiasi motivo il file non e'' più raggiungibile nella sua location principale, non sara'' più visibile su questa pagina.'
URLDESCRIPTION: 'Inserisci video e immagini dal Web nella tua pagina semplicemente inserendo l''URL del file.<br />Si sicuro di avere i diritti o i permessi prima di condividere media direttamente dal Web.<br /><br />NB : i file non sono aggiunti allo storage file del CMS, ma incorpora il file dalla sua location principale, se per un qualsiasi motivo il file non e'' più raggiungibile nella sua location principale, non sara'' più visibile su questa pagina.'
URLNOTANOEMBEDRESOURCE: 'L''URL ''{url}'' non può essere convertito in una risorsa media.'
UpdateMEDIA: 'Aggiorna Media'
Image:

View File

@ -115,7 +115,7 @@ class Group extends DataObject {
$config->getComponentByType('GridFieldAddExistingAutocompleter')
->setResultsFormat('$Title ($Email)')->setSearchFields(array('FirstName', 'Surname', 'Email'));
$config->getComponentByType('GridFieldDetailForm')
->setValidator(new Member_Validator())
->setValidator(Member_Validator::create())
->setItemEditFormCallback(function($form, $component) use($group) {
$record = $form->getRecord();
$groupsField = $form->Fields()->dataFieldByName('DirectGroups');

View File

@ -757,6 +757,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
*/
public function getValidator() {
$validator = Injector::inst()->create('Member_Validator');
$validator->setForMember($this);
$this->extend('updateValidator', $validator);
return $validator;
@ -1802,21 +1803,39 @@ class Member_ForgotPasswordEmail extends Email {
* 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 DataExtension} on the Member_Validator object or, by specifying a subclass of
* {@link Member_Validator} through the {@link Injector} API.
*
* The Validator can also be modified by adding an Extension to Member and implement the
* <code>updateValidator</code> hook.
* {@see Member::getValidator()}
*
* Additional required fields can also be set via config API, eg.
* <code>
* Member_Validator:
* customRequired:
* - Surname
* </code>
*
* @package framework
* @subpackage security
*/
class Member_Validator extends RequiredFields {
class Member_Validator extends RequiredFields
{
/**
* Fields that are required by this validator
* @config
* @var array
*/
protected $customRequired = array(
'FirstName',
'Email'
);
/**
* Determine what member this validator is meant for
* @var Member
*/
protected $forMember = null;
/**
* Constructor
@ -1830,7 +1849,33 @@ class Member_Validator extends RequiredFields {
$required = array_merge($required, $this->customRequired);
parent::__construct($required);
// check for config API values and merge them in
$config = $this->config()->customRequired;
if(is_array($config)){
$required = array_merge($required, $config);
}
parent::__construct(array_unique($required));
}
/**
* Get the member this validator applies to.
* @return Member
*/
public function getForMember()
{
return $this->forMember;
}
/**
* Set the Member this validator applies to.
* @param Member $value
* @return $this
*/
public function setForMember(Member $value)
{
$this->forMember = $value;
return $this;
}
/**
@ -1843,47 +1888,54 @@ class Member_Validator extends RequiredFields {
* @return bool Returns TRUE if the submitted data is valid, otherwise
* FALSE.
*/
public function php($data) {
public function php($data)
{
$valid = parent::php($data);
$identifierField = Member::config()->unique_identifier_field;
$member = DataObject::get_one('Member', array(
"\"$identifierField\"" => $data[$identifierField]
));
$identifierField = (string)Member::config()->unique_identifier_field;
// if we are in a complex table field popup, use ctf[childID], else use ID
if(isset($_REQUEST['ctf']['childID'])) {
$id = $_REQUEST['ctf']['childID'];
} elseif(isset($_REQUEST['ID'])) {
$id = $_REQUEST['ID'];
} else {
$id = null;
}
if($id && is_object($member) && $member->ID != $id) {
$uniqueField = $this->form->Fields()->dataFieldByName($identifierField);
$this->validationError(
$uniqueField->id(),
_t(
'Member.VALIDATIONMEMBEREXISTS',
'A member already exists with the same %s',
array('identifier' => strtolower($identifierField))
),
'required'
);
$valid = false;
}
// Execute the validators on the extensions
if($this->extension_instances) {
foreach($this->extension_instances as $extension) {
if(method_exists($extension, 'hasMethod') && $extension->hasMethod('updatePHP')) {
$valid &= $extension->updatePHP($data, $this->form);
// Only validate identifier field if it's actually set. This could be the case if
// somebody removes `Email` from the list of required fields.
if(isset($data[$identifierField])){
$id = isset($data['ID']) ? (int)$data['ID'] : 0;
if(!$id && ($ctrl = $this->form->getController())){
// get the record when within GridField (Member editing page in CMS)
if($ctrl instanceof GridFieldDetailForm_ItemRequest && $record = $ctrl->getRecord()){
$id = $record->ID;
}
}
// If there's no ID passed via controller or form-data, use the assigned member (if available)
if(!$id && ($member = $this->getForMember())){
$id = $member->exists() ? $member->ID : 0;
}
// set the found ID to the data array, so that extensions can also use it
$data['ID'] = $id;
$members = Member::get()->filter($identifierField, $data[$identifierField]);
if($id) {
$members = $members->exclude('ID', $id);
}
if($members->count() > 0) {
$this->validationError(
$identifierField,
_t(
'Member.VALIDATIONMEMBEREXISTS',
'A member already exists with the same {identifier}',
array('identifier' => Member::singleton()->fieldLabel($identifierField))
),
'required'
);
$valid = false;
}
}
return $valid;
}
// Execute the validators on the extensions
$results = $this->extend('updatePHP', $data, $this->form);
$results[] = $valid;
return min($results);
}
}

View File

@ -1072,7 +1072,135 @@ class MemberTest extends FunctionalTest {
}
}
public function testCustomMemberValidator() {
public function testMemberValidator()
{
// clear custom requirements for this test
Config::inst()->update('Member_Validator', 'customRequired', null);
$memberA = $this->objFromFixture('Member', 'admin');
$memberB = $this->objFromFixture('Member', 'test');
// create a blank form
$form = new MemberTest_ValidatorForm();
$validator = new Member_Validator();
$validator->setForm($form);
// Simulate creation of a new member via form, but use an existing member identifier
$fail = $validator->php(array(
'FirstName' => 'Test',
'Email' => $memberA->Email
));
$this->assertFalse(
$fail,
'Member_Validator must fail when trying to create new Member with existing Email.'
);
// populate the form with values from another member
$form->loadDataFrom($memberB);
// Assign the validator to an existing member
// (this is basically the same as passing the member ID with the form data)
$validator->setForMember($memberB);
// Simulate update of a member via form and use an existing member Email
$fail = $validator->php(array(
'FirstName' => 'Test',
'Email' => $memberA->Email
));
// Simulate update to a new Email address
$pass1 = $validator->php(array(
'FirstName' => 'Test',
'Email' => 'membervalidatortest@testing.com'
));
// Pass in the same Email address that the member already has. Ensure that case is valid
$pass2 = $validator->php(array(
'FirstName' => 'Test',
'Surname' => 'User',
'Email' => $memberB->Email
));
$this->assertFalse(
$fail,
'Member_Validator must fail when trying to update existing member with existing Email.'
);
$this->assertTrue(
$pass1,
'Member_Validator must pass when Email is updated to a value that\'s not in use.'
);
$this->assertTrue(
$pass2,
'Member_Validator must pass when Member updates his own Email to the already existing value.'
);
}
public function testMemberValidatorWithExtensions()
{
// clear custom requirements for this test
Config::inst()->update('Member_Validator', 'customRequired', null);
// create a blank form
$form = new MemberTest_ValidatorForm();
// Test extensions
Member_Validator::add_extension('MemberTest_MemberValidator_SurnameMustMatchFirstNameExtension');
$validator = new Member_Validator();
$validator->setForm($form);
// This test should fail, since the extension enforces FirstName == Surname
$fail = $validator->php(array(
'FirstName' => 'Test',
'Surname' => 'User',
'Email' => 'test-member-validator-extension@testing.com'
));
$pass = $validator->php(array(
'FirstName' => 'Test',
'Surname' => 'Test',
'Email' => 'test-member-validator-extension@testing.com'
));
$this->assertFalse(
$fail,
'Member_Validator must fail because of added extension.'
);
$this->assertTrue(
$pass,
'Member_Validator must succeed, since it meets all requirements.'
);
// Add another extension that always fails. This ensures that all extensions are considered in the validation
Member_Validator::add_extension('MemberTest_MemberValidator_AlwaysFailsExtension');
$validator = new Member_Validator();
$validator->setForm($form);
// Even though the data is valid, This test should still fail, since one extension always returns false
$fail = $validator->php(array(
'FirstName' => 'Test',
'Surname' => 'Test',
'Email' => 'test-member-validator-extension@testing.com'
));
$this->assertFalse(
$fail,
'Member_Validator must fail because of added extensions.'
);
// Remove added extensions
Member_Validator::remove_extension('MemberTest_MemberValidator_AlwaysFailsExtension');
Member_Validator::remove_extension('MemberTest_MemberValidator_SurnameMustMatchFirstNameExtension');
}
public function testCustomMemberValidator()
{
// clear custom requirements for this test
Config::inst()->update('Member_Validator', 'customRequired', null);
$member = $this->objFromFixture('Member', 'admin');
$form = new MemberTest_ValidatorForm();
@ -1091,7 +1219,7 @@ class MemberTest extends FunctionalTest {
'Surname' => ''
));
$this->assertTrue($pass, 'Validator requires on FirstName and Email');
$this->assertTrue($pass, 'Validator requires a FirstName and Email');
$this->assertFalse($fail, 'Missing FirstName');
$ext = new MemberTest_ValidatorExtension();
@ -1149,6 +1277,30 @@ class MemberTest_ValidatorExtension extends DataExtension implements TestOnly {
}
}
/**
* Extension that adds additional validation criteria
* @package framework
* @subpackage tests
*/
class MemberTest_MemberValidator_SurnameMustMatchFirstNameExtension extends DataExtension implements TestOnly
{
public function updatePHP($data, $form) {
return $data['FirstName'] == $data['Surname'];
}
}
/**
* Extension that adds additional validation criteria
* @package framework
* @subpackage tests
*/
class MemberTest_MemberValidator_AlwaysFailsExtension extends DataExtension implements TestOnly
{
public function updatePHP($data, $form) {
return false;
}
}
/**
* @package framework
* @subpackage tests