3 * Enhances {ComplexTableField} with the ability to list groups and given members.
4 * It is based around groups, so it deletes Members from a Group rather than from the entire system.
6 * In contrast to the original implementation, the URL-parameters "ParentClass" and "ParentID" are used
7 * to specify "Group" (hardcoded) and the GroupID-relation.
11 * - members of a provided group
13 * - members based on a search-query
15 class MemberTableField
extends ComplexTableField
{
17 protected $hidePassword;
19 protected $detailFormValidator;
22 protected $template = "MemberTableField";
24 static $data_class = "Member";
26 protected $permissions = array(
32 private static $addedPermissions = array();
33 private static $addedFields = array();
34 private static $addedCsvFields = array();
36 public static function addPermissions( $addingPermissionList ) {
37 self
::$addedPermissions = $addingPermissionList;
40 public static function addMembershipFields( $addingFieldList, $addingCsvFieldList = null ) {
41 self
::$addedFields = $addingFieldList;
42 $addingCsvFieldList == null ? self
::$addedCsvFields = $addingFieldList : self
::$addedCsvFields = $addingCsvFieldList;
45 function __construct($controller, $name, $group, $members = null, $hidePassword = true, $pageLimit = 10) {
48 if(is_object($group)) {
49 $this->group
= $group;
50 } else if(is_numeric($group)){
51 $this->group
= DataObject
::get_by_id('Group',$group);
53 } else if(is_numeric($_REQUEST['ctf'][$this->Name()]["ID"])) {
54 $this->group
= DataObject
::get_by_id('Group',$_REQUEST['ctf'][$this->Name()]["ID"]);
58 $sourceClass = $this->stat("data_class");
60 foreach( self
::$addedPermissions as $permission )
61 array_push( $this->permissions
, $permission );
64 "FirstName" => "Firstname",
65 "Surname" => "Surname",
69 $csvFieldList = $fieldList;
70 foreach( self
::$addedCsvFields as $key => $value ) {
71 $csvFieldList[$key] = $value;
74 foreach( self
::$addedFields as $key => $value ) {
75 $fieldList[$key] = $value;
79 $fieldList["Password"] = "Password";
82 if(isset($_REQUEST['ctf']['childID']) && is_numeric($_REQUEST['ctf']['childID'])) {
83 $SNG_member = DataObject
::get_by_id($this->stat("data_class"),$_REQUEST['ctf']['childID']);
85 $SNG_member = singleton(Object::getCustomClass($this->stat("data_class")));
87 $detailFormFields = $SNG_member->getCMSFields();
88 $this->detailFormValidator
= $SNG_member->getValidator();
90 $this->pageSize
= $pageLimit;
92 // Legacy: Use setCustomSourceItems() instead.
94 $this->customSourceItems
= $this->memberListWithGroupID($members, $group);
97 $this->hidePassword
= $hidePassword;
99 parent
::__construct($controller, $name, $sourceClass, $fieldList, $detailFormFields);
101 Requirements
::javascript("cms/javascript/MemberTableField.js");
103 // construct the filter and sort
104 if(isset($_REQUEST['MemberOrderByField'])) {
105 $this->sourceSort
= "`" . Convert
::raw2sql($_REQUEST['MemberOrderByField']) . "`" . Convert
::raw2sql( $_REQUEST['MemberOrderByOrder'] );
109 $search = isset($_REQUEST['MemberSearch']) ? Convert
::raw2sql($_REQUEST['MemberSearch']) : null;
110 if(!empty($_REQUEST['MemberSearch'])) {
111 //$this->sourceFilter[] = "( `Email` LIKE '%$search%' OR `FirstName` LIKE '%$search%' OR `Surname` LIKE '%$search%' )";
113 foreach( $fieldList as $k => $v )
114 $sourceF .= "`$k` LIKE '%$search%' OR ";
115 $this->sourceFilter
[] = substr( $sourceF, 0, -3 ) . ")";
119 // TODO Not implemented yet
120 if(isset($_REQUEST['ctf'][$this->Name()]['GroupID']) && is_numeric($_REQUEST['ctf'][$this->Name()]['GroupID'])) {
121 $this->sourceFilter
[] = "`GroupID`='{$_REQUEST['ctf'][$this->Name()]['GroupID']}'";
122 } elseif($this->group
) {
123 //$this->sourceFilter[] = "`GroupID`='{$this->group->ID}'";
124 // If the table is not clean (without duplication), the total and navigation wil not work well, so uncheck the big line below
125 $this->sourceFilter
[] = "`Group_Members`.`ID` IN (SELECT `ID` FROM `Group_Members` WHERE `GroupID`='{$this->group->ID}' GROUP BY `MemberID` HAVING MIN(`ID`))";
128 $this->sourceJoin
= " INNER JOIN `Group_Members` ON `MemberID`=`Member`.`ID`";
130 $this->setFieldListCsv( $csvFieldList );
134 * Overridden functions
138 function sourceID() {
139 return $this->group
->ID
;
143 return "{$this->PopupBaseLink()}&methodName=add";
146 function DetailForm() {
147 $ID = Convert
::raw2xml(isset($_REQUEST['ctf']['ID'])
148 ?
$_REQUEST['ctf']['ID']
150 $childID = Convert
::raw2xml(@$_REQUEST['ctf']['childID']);
151 $childClass = Convert
::raw2xml($_REQUEST['fieldName']);
152 $methodName = isset($_REQUEST['methodName']) ?
$_REQUEST['methodName'] : '';
154 if($methodName == "add") {
155 $parentIdName = $this->getParentIdName($childClass,$this->getParentClass());
157 user_error("ComplexTableField::DetailForm() Dataobject does not seem to have an 'has-one'-relationship", E_USER_WARNING
);
160 $this->detailFormFields
->push(new HiddenField('parentClass'," ",$this->getParentClass()));
163 // the ID field confuses the Controller-logic in finding the right view for ReferencedField
164 $this->detailFormFields
->removeByName('ID');
166 $this->detailFormFields
->push(new HiddenField("ctf[ID]"," ",$ID));
167 // add a namespaced ID instead thats "converted" by saveComplexTableField()
168 $this->detailFormFields
->push(new HiddenField("ctf[childID]","",$childID));
169 $this->detailFormFields
->push(new HiddenField("ClassName","",$this->sourceClass
));
171 $form = new MemberTableField_Popup($this, "DetailForm", $this->detailFormFields
, $this->sourceClass
, $methodName == "show", $this->detailFormValidator
);
173 if (is_numeric($childID)) {
174 if ($methodName == "show" ||
$methodName == "edit") {
175 $childData = DataObject
::get_by_id($this->sourceClass
, $childID);
176 $form->loadDataFrom($childData);
180 if ($methodName == "show") {
181 $form->makeReadonly();
187 function SearchForm() {
188 $searchFields = new FieldGroup(
189 new TextField('MemberSearch', 'Search'),
190 new HiddenField("ctf[ID]",'',$this->group
->ID
),
191 new HiddenField('MemberFieldName','',$this->name
),
192 new HiddenField('MemberDontShowPassword','',$this->hidePassword
)
195 $orderByFields = new FieldGroup(
196 new LabelField('Order by'),
198 new DropdownField('MemberOrderByField','', array(
199 'FirstName' => 'FirstName',
200 'Surname' => 'Surname',
203 new DropdownField('MemberOrderByOrder','',array(
204 'ASC' => 'Ascending',
205 'DESC' => 'Descending'
210 $groups = DataObject
::get('Group');
211 $groupArray = array('' => 'Any group');
212 foreach( $groups as $group ) {
213 $groupArray[$group->ID
] = $group->Title
;
215 $groupFields = new DropdownField('MemberGroup','Filter by group',$groupArray );
217 $actionFields = new LiteralField('MemberFilterButton','<input type="submit" name="MemberFilterButton" value="Filter" id="MemberFilterButton"/>');
219 $fieldContainer = new FieldGroup(
226 return $fieldContainer->FieldHolder();
231 * Add existing member to group rather than creating a new member
233 function addtogroup() {
237 if(!is_numeric($data['ctf']['ID'])) {
238 FormResponse
::status_messsage('Adding failed', 'bad');
241 $className = $this->stat('data_class');
242 $record = new $className();
244 $record->update($data);
247 // To Avoid duplication in the Group_Members table if the ComponentSet.php is not modified just uncomment le line below
249 //if( ! $record->isInGroup( $data['ctf']['ID'] ) )
250 $record->Groups()->add( $data['ctf']['ID'] );
252 $this->sourceItems();
254 // TODO add javascript to highlight added row (problem: might not show up due to sorting/filtering)
255 FormResponse
::update_dom_id($this->id(), $this->renderWith($this->template
), true);
256 FormResponse
::status_message('Added member to group', 'good');
258 return FormResponse
::respond();
262 * Custom delete implementation:
263 * Remove member from group rather than from the database
266 $groupID = Convert
::raw2sql($_REQUEST["ctf"]["ID"]);
267 $memberID = Convert
::raw2sql($_REQUEST["ctf"]["childID"]);
268 if(is_numeric($groupID) && is_numeric($memberID)) {
269 $member = DataObject
::get_by_id('Member', $memberID);
270 $member->Groups()->remove($groupID);
272 user_error("MemberTableField::delete: Bad parameters: Group=$groupID, Member=$memberID", E_USER_ERROR
);
275 return FormResponse
::respond();
282 * #################################
284 * #################################
286 function getParentClass() {
290 function getParentIdName($childClass,$parentClass){
296 * #################################
298 * #################################
300 function memberListWithGroupID($members, $group) {
301 $newMembers = new DataObjectSet();
302 foreach($members as $member) {
303 $newMembers->push($member->customise(array("GroupID" => $group->ID
)));
308 function setGroup($group) {
309 $this->group
= $group;
311 function setController($controller) {
312 $this->controller
= $controller;
315 function GetControllerName() {
316 return $this->controller
->class;
320 * Add existing member to group by name (with JS-autocompletion)
322 function AddRecordForm() {
323 $fields = new FieldSet();
324 foreach($this->FieldList() as $fieldName=>$fieldTitle) {
325 $fields->push(new TextField($fieldName));
327 $fields->push(new HiddenField("ctf[ID]", null, $this->group
->ID
));
329 return new TabularStyle(new Form($this->controller
,'AddRecordForm',
332 new FormAction("addtogroup", "Add")
338 * Cached version for getting the appropraite members for this particular group.
340 * This includes getting inherited groups, such as groups under groups.
342 function sourceItems(){
344 if($this->sourceItems
) {
345 return $this->sourceItems
;
350 if(isset($_REQUEST['ctf'][$this->Name()]['start']) && is_numeric($_REQUEST['ctf'][$this->Name()]['start'])) {
351 $limitClause = ($_REQUEST['ctf'][$this->Name()]['start']) . ", {$this->pageSize}";
353 $limitClause = "0, {$this->pageSize}";
357 // We use the group to get the members, as they already have the bulk of the look up functions
358 $start = isset($_REQUEST['ctf'][$this->Name()]['start']) ?
$_REQUEST['ctf'][$this->Name()]['start'] : 0;
359 $this->sourceItems
= $this->group
->Members(
360 $this->pageSize
, // limit
365 $this->unpagedSourceItems
= $this->group
->Members( "", "", $this->sourceFilter
, $this->sourceSort
);
366 $this->totalCount
= ($this->sourceItems
) ?
$this->sourceItems
->TotalItems() : 0;
367 return $this->sourceItems
;
370 function TotalCount() {
371 $this->sourceItems(); // Called for its side-effect of setting total count
372 return $this->totalCount
;
380 class MemberTableField_Popup
extends ComplexTableField_Popup
{
381 function __construct($controller, $name, $fields, $sourceClass, $readonly=false, $validator = null) {
383 // DO NOT CHANGE THE ORDER OF THESE JS FILES. THESE ARE ONLY REQUIRED FOR THIS INSTANCE !!!11onetwo
385 parent
::__construct($controller, $name, $fields, $sourceClass, $readonly, $validator);
387 Requirements
::javascript("cms/javascript/MemberTableField.js");
388 Requirements
::javascript("cms/javascript/MemberTableField_popup.js");
392 function saveComplexTableField() {
393 $id = Convert
::raw2sql($_REQUEST['ctf']['childID']);
395 if (is_numeric($id)) {
396 $childObject = DataObject
::get_by_id($this->sourceClass
, $id);
398 $childObject = new $this->sourceClass();
400 $this->saveInto($childObject);
401 $childObject->write();
403 $childObject->Groups()->add($_REQUEST['ctf']['ID']);
405 // if ajax-call in an iframe, close window by javascript, else redirect to referrer
406 if(!Director
::is_ajax()) {
407 Director
::redirect(substr($_SERVER['REQUEST_URI'],0,strpos($_SERVER['REQUEST_URI'],"?")));