API CHANGE Removed MemberTableField, use GridField with GridFieldConfig_RelationEditor instead

This commit is contained in:
Ingo Schommer 2012-03-05 17:35:10 +01:00
parent 8da89c6f7c
commit 0117b32fee
9 changed files with 36 additions and 1048 deletions

View File

@ -1,370 +0,0 @@
<?php
/**
* Enhances {ComplexTableField} with the ability to list groups and given members.
* It is based around groups, so it deletes Members from a Group rather than from the entire system.
*
* In contrast to the original implementation, the URL-parameters "ParentClass" and "ParentID" are used
* to specify "Group" (hardcoded) and the GroupID-relation.
*
* @todo write a better description about what this field does.
*
* Returns either:
* - provided members
* - members of a provided group
* - all members
* - members based on a search-query
*
* @package cms
* @subpackage security
*/
class MemberTableField extends ComplexTableField {
protected $members;
protected $hidePassword;
protected $detailFormValidator;
protected $group;
protected $template = 'MemberTableField';
public $popupClass = 'MemberTableField_Popup';
public $itemClass = 'MemberTableField_Item';
/**
* Set the page size for this table.
* @var int
*/
public static $page_size = 20;
protected $permissions = array(
"add",
"edit",
"show",
"delete",
'inlineadd'
//"export",
);
/**
* Constructor method for MemberTableField.
*
* @param Controller $controller Controller class which created this field
* @param string $name Name of the field (e.g. "Members")
* @param mixed $group Can be the ID of a Group instance, or a Group instance itself
* @param SS_List $members Optional set of Members to set as the source items for this field
* @param boolean $hidePassword Hide the password field or not in the summary?
*/
function __construct($controller, $name, $group = null, $members = null, $hidePassword = true) {
if(!$members) {
if($group) {
if(is_numeric($group)) $group = DataObject::get_by_id('Group', $group);
$this->group = $group;
$members = $group->Members();
} elseif(isset($_REQUEST['ctf'][$this->getName()]["ID"]) && is_numeric($_REQUEST['ctf'][$this->getName()]["ID"])) {
throw new Exception("Is this still being used? It's a hack and we should remove it.");
$group = DataObject::get_by_id('Group', $_REQUEST['ctf'][$this->getName()]["ID"]);
$this->group = $group;
$members = $group->Members();
} else {
$members = DataObject::get("Member");
}
}
$SNG_member = singleton('Member');
$fieldList = $SNG_member->summaryFields();
$memberDbFields = $SNG_member->db();
$csvFieldList = array();
foreach($memberDbFields as $field => $dbFieldType) {
$csvFieldList[$field] = $field;
}
if(!$hidePassword) {
$fieldList["SetPassword"] = "Password";
}
$this->hidePassword = $hidePassword;
// Add a search filter
$SQL_search = isset($_REQUEST['MemberSearch']) ? Convert::raw2sql($_REQUEST['MemberSearch']) : null;
if(!empty($_REQUEST['MemberSearch'])) {
$searchFilters = array();
foreach($SNG_member->searchableFields() as $fieldName => $fieldSpec) {
if(strpos($fieldName, '.') === false) $searchFilters[] = "\"$fieldName\" LIKE '%{$SQL_search}%'";
}
$members = $members->where('(' . implode(' OR ', $searchFilters) . ')');
}
parent::__construct($controller, $name, $members, $fieldList);
$this->setFieldListCsv($csvFieldList);
$this->setPageSize($this->stat('page_size'));
}
function FieldHolder() {
$ret = parent::FieldHolder();
Requirements::javascript(SAPPHIRE_DIR . "/thirdparty/scriptaculous/controls.js");
Requirements::javascript(SAPPHIRE_ADMIN_DIR . '/javascript/MemberTableField.js');
Requirements::javascript(SAPPHIRE_ADMIN_DIR . "/javascript/MemberTableField_popup.js");
return $ret;
}
function SearchForm() {
$groupID = (isset($this->group)) ? $this->group->ID : 0;
$query = isset($_GET['MemberSearch']) ? $_GET['MemberSearch'] : null;
$searchFields = new FieldGroup(
new TextField('MemberSearch', _t('MemberTableField.SEARCH', 'Search'), $query),
new HiddenField("ctf[ID]", '', $groupID),
new HiddenField('MemberFieldName', '', $this->name),
new HiddenField('MemberDontShowPassword', '', $this->hidePassword)
);
$actionFields = new LiteralField('MemberFilterButton','<input type="submit" class="action" name="MemberFilterButton" value="'._t('MemberTableField.FILTER', 'Filter').'" id="MemberFilterButton"/>');
$fieldContainer = new FieldGroup(
$searchFields,
$actionFields
);
return $fieldContainer->FieldHolder();
}
/**
* Add existing member to group rather than creating a new member
*/
function addtogroup() {
// Protect against CSRF on destructive action
$token = $this->getForm()->getSecurityToken();
if(!$token->checkRequest($this->controller->getRequest())) return $this->httpError(400);
$data = $_REQUEST;
$groupID = (isset($data['ctf']['ID'])) ? $data['ctf']['ID'] : null;
if(!is_numeric($groupID)) {
FormResponse::status_messsage(_t('MemberTableField.ADDINGFIELD', 'Adding failed'), 'bad');
return;
}
// Get existing record either by ID or unique identifier.
$identifierField = Member::get_unique_identifier_field();
$className = 'Member';
$record = null;
if(isset($data[$identifierField])) {
$record = DataObject::get_one(
$className,
sprintf('"%s" = \'%s\'', $identifierField, $data[$identifierField])
);
if($record && !$record->canEdit()) return $this->httpError('401');
}
// Fall back to creating a new record
if(!$record) $record = new $className();
// Update an existing record, or populate a new one.
// If values on an existing (autocompleted) record have been changed,
// they will overwrite current data. We need to unset 'ID'
// record as it points to the group rather than the member record, and would
// cause the member to be written to a potentially existing record.
unset($data['ID']);
$record->update($data);
// Validate record, mainly password restrictions.
// Note: Doesn't use Member_Validator
$valid = $record->validate();
if($valid->valid()) {
$record->write();
$this->getDataList()->add($record);
$this->sourceItems();
// TODO add javascript to highlight added row (problem: might not show up due to sorting/filtering)
FormResponse::update_dom_id($this->id(), $this->renderWith($this->template), true);
FormResponse::status_message(
_t(
'MemberTableField.ADDEDTOGROUP','Added member to group'
),
'good'
);
} else {
$message = sprintf(
_t(
'MemberTableField.ERRORADDINGUSER',
'There was an error adding the user to the group: %s'
),
Convert::raw2xml($valid->starredList())
);
FormResponse::status_message($message, 'bad');
}
return FormResponse::respond();
}
/**
* #################################
* Custom Functions
* #################################
*/
/**
* @return Group
*/
function getGroup() {
return $this->group;
}
/**
* Add existing member to group by name (with JS-autocompletion)
*/
function AddRecordForm() {
$fields = new FieldList();
foreach($this->FieldList() as $fieldName => $fieldTitle) {
// If we're adding the set password field, we want to hide the text from any peeping eyes
if($fieldName == 'SetPassword') {
$fields->push(new PasswordField($fieldName));
} else {
$fields->push(new TextField($fieldName));
}
}
if($this->group) {
$fields->push(new HiddenField('ctf[ID]', null, $this->group->ID));
}
$actions = new FieldList(
new FormAction('addtogroup', _t('MemberTableField.ADD','Add'))
);
return new TabularStyle(
new NestedForm(
new Form(
$this,
'AddRecordForm',
$fields,
$actions
)
)
);
}
function AddForm() {
$form = parent::AddForm();
// Set default groups - also implemented in MemberTableField_Popup::__construct()
if($this->group) {
$groupsField = $form->Fields()->dataFieldByName('Groups');
// TODO Needs to be a string value (not int) because of TreeMultiselectField->getItems(),
// see http://open.silverstripe.org/ticket/5836
if($groupsField) $groupsField->setValue((string)$this->group->ID);
}
return $form;
}
/**
* Same behaviour as parent class, but adds the
* member to the passed GroupID.
*
* @return string
*/
function saveComplexTableField($data, $form, $params) {
$className = $this->sourceClass();
$childData = new $className();
// Needs to write before saveInto() to ensure the 'Groups' TreeMultiselectField saves
$childData->write();
try {
$form->saveInto($childData);
$childData->write();
} catch(ValidationException $e) {
$form->sessionMessage($e->getResult()->message(), 'bad');
return Director::redirectBack();
}
$closeLink = sprintf(
'<small><a href="' . $_SERVER['HTTP_REFERER'] . '" onclick="javascript:window.top.GB_hide(); return false;">(%s)</a></small>',
_t('ComplexTableField.CLOSEPOPUP', 'Close Popup')
);
$message = sprintf(
_t('ComplexTableField.SUCCESSADD', 'Added %s %s %s'),
$childData->singular_name(),
'<a href="' . $this->Link() . '">' . htmlspecialchars($childData->Title, ENT_QUOTES, 'UTF-8') . '</a>',
$closeLink
);
$form->sessionMessage($message, 'good');
$this->controller->redirectBack();
}
}
/**
* Popup window for {@link MemberTableField}.
* @package cms
* @subpackage security
*/
class MemberTableField_Popup extends ComplexTableField_Popup {
function __construct($controller, $name, $fields, $validator, $readonly, $dataObject) {
$group = ($controller instanceof MemberTableField) ? $controller->getGroup() : $controller->getParentController()->getGroup();
// Set default groups - also implemented in AddForm()
if($group) {
$groupsField = $fields->dataFieldByName('Groups');
if($groupsField) $groupsField->setValue($group->ID);
}
parent::__construct($controller, $name, $fields, $validator, $readonly, $dataObject);
}
function forTemplate() {
$ret = parent::forTemplate();
Requirements::css(SAPPHIRE_ADMIN_DIR . '/css/SecurityAdmin.css');
Requirements::javascript(SAPPHIRE_ADMIN_DIR . '/javascript/MemberTableField.js');
Requirements::javascript(SAPPHIRE_ADMIN_DIR . '/javascript/MemberTableField_popup.js');
return $ret;
}
}
/**
* @package cms
* @subpackage security
*/
class MemberTableField_Item extends ComplexTableField_Item {
function Actions() {
$actions = parent::Actions();
foreach($actions as $action) {
if($action->Name == 'delete') {
if($this->parent->getGroup()) {
$action->TitleText = _t('MemberTableField.DeleteTitleText',
'Delete from this group',
PR_MEDIUM,
'Delete button hover text'
);
} else {
$action->TitleText = _t('MemberTableField.DeleteTitleTextDatabase',
'Delete from database and all groups',
PR_MEDIUM,
'Delete button hover text'
);
}
}
}
return $actions;
}
}

View File

@ -17,9 +17,6 @@ class SecurityAdmin extends LeftAndMain implements PermissionProvider {
static $subitem_class = 'Member';
static $allowed_actions = array(
'autocomplete',
'removememberfromgroup',
'AddRecordForm',
'EditForm',
'MemberImportForm',
'memberimport',
@ -223,51 +220,6 @@ class SecurityAdmin extends LeftAndMain implements PermissionProvider {
return $form;
}
public function AddRecordForm() {
$m = Object::create('MemberTableField',
$this,
"Members",
$this->currentPageID()
);
return $m->AddRecordForm();
}
/**
* Ajax autocompletion
*/
public function autocomplete() {
$fieldName = $this->urlParams['ID'];
$fieldVal = $_REQUEST[$fieldName];
$result = '';
$uidField = Member::get_unique_identifier_field();
// Make sure we only autocomplete on keys that actually exist, and that we don't autocomplete on password
if(!singleton($this->stat('subitem_class'))->hasDatabaseField($fieldName) || $fieldName == 'Password') return;
$matches = DataObject::get($this->stat('subitem_class'),"\"$fieldName\" LIKE '" . Convert::raw2sql($fieldVal) . "%'");
if($matches) {
$result .= "<ul>";
foreach($matches as $match) {
// If the current user doesnt have permissions on the target user,
// he's not allowed to add it to a group either: Don't include it in the suggestions.
if(!$match->canView() || !$match->canEdit()) continue;
$data = array();
foreach($match->summaryFields() as $k => $v) {
$data[$k] = $match->$k;
}
$result .= sprintf(
'<li data-fields="%s">%s <span class="informal">(%s)</span></li>',
Convert::raw2att(Convert::raw2json($data)),
$match->$fieldName,
implode(',', array_values($data))
);
}
$result .= "</ul>";
return $result;
}
}
function getCMSTreeTitle() {
return _t('SecurityAdmin.SGROUPS', 'Security Groups');
}

View File

@ -1,347 +0,0 @@
/**
* File: MemberTableField.js
*/
(function($) {
$.entwine('ss', function($){
/**
* Class: #Permissions .checkbox[value=ADMIN]
*
* Automatically check and disable all checkboxes if ADMIN permissions are selected.
* As they're disabled, any changes won't be submitted (which is intended behaviour),
* checking all boxes is purely presentational.
*/
$('#Permissions .checkbox[value=ADMIN]').entwine({
onmatch: function() {
this.toggleCheckboxes();
this._super();
},
/**
* Function: onclick
*/
onclick: function(e) {
this.toggleCheckboxes();
},
/**
* Function: toggleCheckboxes
*/
toggleCheckboxes: function() {
var self = this, checkboxes = this.parents('.field:eq(0)').find('.checkbox').not(this);
if(this.is(':checked')) {
checkboxes.each(function() {
$(this).data('SecurityAdmin.oldChecked', $(this).attr('checked'));
$(this).data('SecurityAdmin.oldDisabled', $(this).attr('disabled'));
$(this).attr('disabled', 'disabled');
$(this).attr('checked', 'checked');
});
} else {
checkboxes.each(function() {
$(this).attr('checked', $(this).data('SecurityAdmin.oldChecked'));
$(this).attr('disabled', $(this).data('SecurityAdmin.oldDisabled'));
});
}
}
});
});
}(jQuery));
/**
* Modified 2006-10-05, Ingo Schommer
* This is more or less a copy of Member.js, with additions and changes
* to match the switch from Member.php to MemberTableField.php all over the UI.
* Eventually it will replace Member.js (please remove this message then).
*/
// no confirm message for removal from a group
if(typeof(ComplexTableField) != 'undefined') {
ComplexTableField.prototype.deleteConfirmMessage = null;
}
/**
* Class: AjaxMemberLookup
*
* Auto-lookup on ajax fields
*/
AjaxMemberLookup = {
initialise : function() {
var div = document.createElement('div');
div.id = this.id + '_ac';
div.className = 'autocomplete';
this.parentNode.appendChild(div);
if(this.id) {
new Ajax.Autocompleter(this.id, div.id, 'admin/security/autocomplete/' + this.name, {
afterUpdateElement : this.afterAutocomplete.bind(this)
});
}
},
afterAutocomplete : function(field, selectedItem) {
var items = jQuery(selectedItem).data('fields'), form = jQuery(selectedItem).parents('form:first');
for(name in items) {
jQuery(form).find('input[name='+name+']').val(items[name]);
}
}
}
/**
* Class: MemberTableField
*/
MemberTableField = Class.create();
MemberTableField.applyTo('.cms-edit-form div.MemberTableField');
MemberTableField.prototype = {
initialize: function() {
Behaviour.register({
'.cms-edit-form div.MemberFilter input' : {
onkeypress : this.prepareSearch.bind(this)
},
'.cms-edit-form div.MemberTableField table.data tr.addtogrouprow input' : {
onkeypress : this.prepareAddToGroup.bind(this)
},
'.cms-edit-form div.MemberTableField table.data tr.addtogrouprow #Form_AddRecordForm_action_addtogroup' : {
onclick : this.prepareAddToGroup.bind(this)
},
'.cms-edit-form div.MemberTableField table.data tr.addtogrouprow td.actions input' : {
initialise: function() {
data = this.parentNode.parentNode.getElementsByTagName('input');
var i,item,error = [];
for(i=0;item=data[i];i++) {
item.originalSerialized = Form.Element.serialize(item);
}
},
onclick : this.addToGroup.bind(this)
},
//'.cms-edit-form div.MemberTableField input' : AjaxMemberLookup,
'.cms-edit-form' : {
changeDetection_fieldsToIgnore : {
'ctf[start]' : true,
'ctf[ID]' : true,
'MemberOrderByField' : true,
'MemberOrderByOrder' : true,
'MemberGroup' : true,
'MemberFilterButton' : true,
'MemberFieldName' : true,
'MemberDontShowPassword' : true,
'MemberSearch' : true
}
}
});
},
// prevent submission of wrong form-button (MemberFilterButton)
prepareAddToGroup: function(e) {
// IE6 doesnt send an event-object with onkeypress
var event = (e) ? e : window.event;
var keyCode = (event.keyCode) ? event.keyCode : event.which;
if(keyCode == Event.KEY_RETURN) {
var el = Event.element(event);
this.addToGroup(event);
Event.stop(event);
return false;
}
},
// prevent submission of wrong form-button (MemberFilterButton)
prepareSearch: function(e) {
// IE6 doesnt send an event-object with onkeypress
var event = (e) ? e : window.event;
var keyCode = (event.keyCode) ? event.keyCode : event.which;
if(keyCode == Event.KEY_RETURN) {
var el = Event.element(event);
$('MemberFilterButton').onclick(event);
Event.stop(event);
return false;
}
},
addToGroup: function(e) {
// only submit parts of the form
var data = this.parentNode.parentNode.getElementsByTagName('input');
var i,item,error = [];
var form = Event.findElement(e,"form");
for(i=0;item=data[i];i++) {
if(item.name == 'Email' && !item.value) error[error.length] = "Email";
if(item.name == 'Password' && !item.value) error[error.length] = "Password";
}
if(error.length > 0) {
alert('Please enter a ' + error.join(' and a ') + ' to add a member.');
} else {
updateURL = "";
updateURL += Event.findElement(e,"form").action;
// we can't set "fieldName" as a HiddenField because there might be multiple ComplexTableFields in a single EditForm-container
updateURL += "?fieldName="+$('MemberFieldName').value;
updateURL += "&action_callfieldmethod&methodName=addtogroup";
ajaxSubmitFieldSet(updateURL, data);
}
return false;
}
/*
initialise : function() {
this.headerMap = [];
var i, item, headers = this.getElementsByTagName('thead')[0].getElementsByTagName('tr')[0].getElementsByTagName('td');
for(i=0;item=headers[i];i++) {
this.headerMap[i] = item.className;
}
},
setRecordDetails : function(id, details, groupID) {
var row = document.getElementById('member-' + id);
if(row) {
var i, item, cells = row.getElementsByTagName('td');
for(i=0;item=cells[i];i++) {
if(details[this.headerMap[i]]) {
item.innerHTML = details[this.headerMap[i]];
}
}
} else {
this.createRecord(id, details, groupID);
}
},
createRecord : function (id, details, groupId) {
var row = document.createElement('tr');
row.id = 'member-' + id;
var i, cell, cellField;
for(i=0;cellField=this.headerMap[i];i++) {
cell = document.createElement('td')
if(details[cellField]) {
cell.innerHTML = details[cellField];
}
row.appendChild(cell);
}
// Add the delete icon
if(typeof groupId == 'undefined')
var groupId = $('Form_EditForm').elements.ID.value;
cell = document.createElement('td')
cell.innerHTML = '<a class="deletelink" href="admin/security/removememberfromgroup/' + groupId + '/' + id + '"><img src="sapphire/images/delete.gif" alt="delete" /></a>';
cell.getElementsByTagName('0');
row.appendChild(cell);
var tbody = this.getElementsByTagName('tbody')[0];
var addRow = document.getElementsByClassName('addrow',tbody)[0];
if(addRow) tbody.insertBefore(row, addRow);
else tbody.appendChild(row);
Behaviour.apply(row, true);
},
clearAddForm : function() {
var tbody = this.getElementsByTagName('tbody')[0];
var addRow = document.getElementsByClassName('addrow',tbody)[0];
if(addRow) {
var i,field,fields = addRow.getElementsByTagName('input');
for(i=0;field=fields[i];i++) {
if(field.type != 'hidden' && field.type != 'submit') field.value = '';
}
}
},
removeMember : function(memberID) {
var record;
if(record = $('member-' + memberID)) {
record.parentNode.removeChild(record);
}
}
*/
}
/**
* Class: MemberFilterButton
*/
MemberFilterButton = Class.create();
MemberFilterButton.applyTo('#MemberFilterButton');
MemberFilterButton.prototype = {
initialize: function() {
this.inputFields = new Array();
var childNodes = this.parentNode.parentNode.getElementsByTagName('input');
for( var index = 0; index < childNodes.length; index++ ) {
if( childNodes[index].tagName ) {
childNodes[index].resetChanged = function() { return false; }
childNodes[index].isChanged = function() { return false; }
this.inputFields.push( childNodes[index] );
}
}
childNodes = this.parentNode.getElementsByTagName('select');
for( var index = 0; index < childNodes.length; index++ ) {
if( childNodes[index].tagName ) {
childNodes[index].resetChanged = function() { return false; }
childNodes[index].field_changed = function() { return false; }
this.inputFields.push( childNodes[index] );
}
}
},
isChanged: function() {
return false;
},
onclick: function(e) {
if(!$('ctf-ID') || !$('MemberFieldName')) {
return false;
}
try {
var form = Event.findElement(e,"form");
var fieldName = $('MemberFieldName').value;
var fieldID = form.id + '_' + fieldName;
var updateURL = form.action + '/field/' + fieldName + '?ajax=1';
for( var index = 0; index < this.inputFields.length; index++ ) {
if( this.inputFields[index].tagName ) {
updateURL += '&' + this.inputFields[index].name + '=' + encodeURIComponent( this.inputFields[index].value );
}
}
jQuery($(fieldID)).get(updateURL, null, function() {Behaviour.apply($(fieldID), true);});
} catch(er) {
errorMessage('Error searching');
}
return false;
}
}
// has to be external from initialize() because otherwise request will double on each reload - WTF
Behaviour.register({
'.cms-edit-form div.MemberTableField table.data input.text' : AjaxMemberLookup
});
/**
* Post the given fields to the given url
*/
function ajaxSubmitFieldSet(href, fieldSet, extraData) {
// Build data
var i,field,data = "ajax=1";
for(i=0;field=fieldSet[i];i++) {
data += '&' + Form.Element.serialize(field);
}
if(extraData){
data += '&'+extraData;
}
// Send request
jQuery.ajax({
'url': href,
'method' : 'post',
'data' : data,
'success' : function(response) {
eval(response);
},
'error' : function(response) {
alert(response.responseText);
}
});
}

View File

@ -1,21 +0,0 @@
/**
* File: MemberTableField_popup.js
*/
/**
* Class: MemberTableFieldPopupForm
*/
MemberTableFieldPopupForm = Class.extend("ComplexTableFieldPopupForm");
MemberTableFieldPopupForm.prototype = {
initialize: function() {
this.ComplexTableFieldPopupForm.initialize();
Behaviour.register('MemberTableFieldPopupForm',{
"div.MemberTableField_Popup .Actions input.action": {
onclick: this.submitForm.bind(this)
}
});
}
}
MemberTableFieldPopupForm.applyTo('div.MemberTableField_Popup .Actions');

View File

@ -30,42 +30,47 @@
$(this).bind('load', refreshAfterImport);
}
});
})
/**
* Delete selected folders through "batch actions" tab.
*/
$(document).ready(function() {
$('#Form_BatchActionsForm').entwine('ss').register(
// TODO Hardcoding of base URL
'admin/security/batchactions/delete',
function(ids) {
var confirmed = confirm(
ss.i18n.sprintf(
ss.i18n._t('SecurityAdmin.BATCHACTIONSDELETECONFIRM'),
ids.length
)
);
return (confirmed) ? ids : false;
}
);
});
$.entwine('ss', function($){
/**
* Class: .cms-edit-form .Actions #Form_EditForm_action_addmember
* Class: #Permissions .checkbox[value=ADMIN]
*
* Automatically check and disable all checkboxes if ADMIN permissions are selected.
* As they're disabled, any changes won't be submitted (which is intended behaviour),
* checking all boxes is purely presentational.
*/
$('.cms-edit-form .Actions #Form_EditForm_action_addmember').entwine({
// Function: onclick
$('#Permissions .checkbox[value=ADMIN]').entwine({
onmatch: function() {
this.toggleCheckboxes();
this._super();
},
/**
* Function: onclick
*/
onclick: function(e) {
// CAUTION: Assumes that a MemberTableField-instance is present as an editing form
var t = $('#Form_EditForm_Members');
t[0].openPopup(
null,
$('base').attr('href') + t.find('a.addlink').attr('href'),
t.find('table')[0]
);
return false;
this.toggleCheckboxes();
},
/**
* Function: toggleCheckboxes
*/
toggleCheckboxes: function() {
var self = this, checkboxes = this.parents('.field:eq(0)').find('.checkbox').not(this);
if(this.is(':checked')) {
checkboxes.each(function() {
$(this).data('SecurityAdmin.oldChecked', $(this).attr('checked'));
$(this).data('SecurityAdmin.oldDisabled', $(this).attr('disabled'));
$(this).attr('disabled', 'disabled');
$(this).attr('checked', 'checked');
});
} else {
checkboxes.each(function() {
$(this).attr('checked', $(this).data('SecurityAdmin.oldChecked'));
$(this).attr('disabled', $(this).data('SecurityAdmin.oldDisabled'));
});
}
}
});
});

View File

@ -1,62 +0,0 @@
<div id="$id" class="$CSSClasses $extraClass field" href="$CurrentLink">
<div class="MemberFilter filterBox">
$SearchForm
</div>
<div id="MemberList">
<% if Markable %>
<% include TableListField_SelectOptions %>
<% end_if %>
<% include TableListField_PageControls %>
<table class="data">
<thead>
<tr>
<% if Markable %><th width="18">&nbsp;</th><% end_if %>
<% control Headings %>
<th class="$Name">$Title</th>
<% end_control %>
<% if Can(show) %><th width="18">&nbsp;</th><% end_if %>
<% if Can(edit) %><th width="18">&nbsp;</th><% end_if %>
<% if Can(delete) %><th width="18">&nbsp;</th><% end_if %>
</tr>
</thead>
<tfoot>
<% if Can(inlineadd) %>
<tr class="addtogrouprow">
<% if Markable %><td width="18">&nbsp;</dh><% end_if %>
$AddRecordForm.CellFields
<td class="actions" colspan="3">$AddRecordForm.CellActions</td>
</tr>
<% end_if %>
<tr style="display: none;">
<% if Markable %><td width="18">&nbsp;</td><% end_if %>
<td colspan="$ItemCount">
<input type="hidden" id="{$id}_PopupHeight" value="$PopupHeight" disabled="disabled"/>
<input type="hidden" id="{$id}_PopupWidth" value="$PopupWidth" disabled="disabled"/>
<a class="popuplink addlink" href="$AddLink" alt="add"><img src="sapphire/images/add.gif" alt="add" /></a><a class="popuplink addlink" href="$AddLink" alt="add"><% _t('ADDNEW','Add new',50,'Followed by a member type') %> $Title</a>
</td>
<% if Can(show) %><td width="18">&nbsp;</td><% end_if %>
<% if Can(edit) %><td width="18">&nbsp;</td><% end_if %>
<% if Can(delete) %><td width="18">&nbsp;</td><% end_if %>
</tr>
</tfoot>
<tbody>
<% if Items %>
<% control Items %>
<% include TableListField_Item %>
<% end_control %>
<% else %>
<tr class="notfound">
<% if Markable %><th width="18">&nbsp;</th><% end_if %>
<td colspan="$Headings.Count"><i><% _t('NOITEMSFOUND', 'No items found') %></i></td>
<% control Actions %><td width="18">&nbsp;</td><% end_control %>
</tr>
<% end_if %>
</tbody>
</table>
<div class="utility">
<% control Utility %>
<span class="item"><a href="$Link" target="_blank">$Title</a></span>
<% end_control %>
</div>
</div>
</div>

View File

@ -1,139 +0,0 @@
<?php
/**
* @package cms
* @subpackage tests
*/
class MemberTableFieldTest extends FunctionalTest {
static $fixture_file = 'sapphire/admin/tests/MemberTableFieldTest.yml';
function testLimitsToMembersInGroup() {
$member1 = $this->objFromFixture('Member', 'member1');
$member2 = $this->objFromFixture('Member', 'member2');
$member3 = $this->objFromFixture('Member', 'member3');
$group1 = $this->objFromFixture('Group', 'group1');
$tf = new MemberTableField(
$this,
"Members",
$group1
);
$members = $tf->sourceItems();
$this->assertContains($member1->ID, $members->column('ID'),
'Members in the associated group are listed'
);
$this->assertContains($member2->ID, $members->column('ID'),
'Members in children groups are listed as well'
);
$this->assertNotContains($member3->ID, $members->column('ID'),
'Members in other groups are filtered out'
);
}
function testShowsAllMembersWithoutGroupParameter() {
$member1 = $this->objFromFixture('Member', 'member1');
$member2 = $this->objFromFixture('Member', 'member2');
$member3 = $this->objFromFixture('Member', 'member3');
$group1 = $this->objFromFixture('Group', 'group1');
$tf = new MemberTableField(
$this,
"Members"
// no group assignment
);
$members = $tf->sourceItems();
$this->assertContains($member1->ID, $members->column('ID'),
'Members in the associated group are listed'
);
$this->assertContains($member2->ID, $members->column('ID'),
'Members in children groups are listed as well'
);
$this->assertContains($member3->ID, $members->column('ID'),
'Members in other groups are listed'
);
}
function testDeleteWithGroupOnlyDeletesRelation() {
$member1 = $this->objFromFixture('Member', 'member1');
$group1 = $this->objFromFixture('Group', 'group1');
$response = $this->get('MemberTableFieldTest_Controller');
$token = SecurityToken::inst();
$url = sprintf('MemberTableFieldTest_Controller/Form/field/Members/item/%d/delete/?usetestmanifest=1', $member1->ID);
$url = $token->addToUrl($url);
$response = $this->get($url);
$group1->flushCache();
$this->assertNotContains($member1->ID, $group1->Members()->column('ID'),
'Member relation to group is removed'
);
$this->assertType(
'DataObject',
DataObject::get_by_id('Member', $member1->ID),
'Member record still exists'
);
}
function testDeleteWithoutGroupDeletesFromDatabase() {
$member1 = $this->objFromFixture('Member', 'member1');
$member1ID = $member1->ID;
$group1 = $this->objFromFixture('Group', 'group1');
$response = $this->get('MemberTableFieldTest_Controller');
$token = SecurityToken::inst();
$url = sprintf('MemberTableFieldTest_Controller/FormNoGroup/field/Members/item/%d/delete/?usetestmanifest=1', $member1->ID);
$url = $token->addToUrl($url);
$response = $this->get($url);
$group1->flushCache();
$this->assertNotContains($member1->ID, $group1->Members()->column('ID'),
'Member relation to group is removed'
);
DataObject::flush_and_destroy_cache();
$this->assertFalse(
DataObject::get_by_id('Member', $member1ID),
'Member record is removed from database'
);
}
}
class MemberTableFieldTest_Controller extends Controller implements TestOnly {
protected $template = 'BlankPage';
function Link($action = null) {
return Controller::join_links('MemberTableFieldTest_Controller', $action);
}
function Form() {
$group1 = DataObject::get_one('Group', '"Code" = \'group1\'');
return new Form(
$this,
'FormNoGroup',
new FieldList(new MemberTableField($this, "Members", $group1)),
new FieldList(new FormAction('submit'))
);
}
function FormNoGroup() {
$tf = new MemberTableField(
$this,
"Members"
// no group
);
return new Form(
$this,
'FormNoGroup',
new FieldList(new MemberTableField($this, "Members")),
new FieldList(new FormAction('submit'))
);
}
}

View File

@ -1,31 +0,0 @@
Group:
admin:
Title: Administrators
Code: admin
group1:
Title: Group1
Code: group1
group1_child:
Title: Group1 Child
Parent: =>Group.group1
Code: group1_child
group2:
Title: Group2
Code: group2
Member:
admin:
Email: admin@example.com
Groups: =>Group.admin
member1:
Email: member1@test.com
Groups: =>Group.group1
member2:
Email: member2@test.com
Groups: =>Group.group1_child
member3:
Email: member3@test.com
Groups: =>Group.group2
Permission:
admin:
Code: ADMIN
GroupID: =>Group.admin

View File

@ -182,3 +182,4 @@ BreadcrumbsTemplate.ss from cms/template to your theme or application.
* `DataObjectLog`: There is no replacement for this.
* `GeoIP`: Moved to separate ["geoip" module](https://github.com/silverstripe-labs/silverstripe-geoip)
* `NZGovtPasswordValidator`: Moved to ["securityextras" module](https://github.com/silverstripe-labs/silverstripe-securityextras)
* `MemberTableField`: Use GridField with GridFieldConfig_RelationEditor instead