|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
namespace SilverStripe\Security; |
|
4
|
|
|
|
|
5
|
|
|
|
|
6
|
|
|
|
|
7
|
|
|
use SilverStripe\ORM\FieldType\DBHTMLText; |
|
8
|
|
|
use SilverStripe\ORM\SS_List; |
|
9
|
|
|
use SilverStripe\ORM\ArrayList; |
|
10
|
|
|
use SilverStripe\ORM\FieldType\DBField; |
|
11
|
|
|
use SilverStripe\ORM\DataObjectInterface; |
|
12
|
|
|
use FormField; |
|
13
|
|
|
use InvalidArgumentException; |
|
14
|
|
|
use Requirements; |
|
15
|
|
|
use Config; |
|
16
|
|
|
|
|
17
|
|
|
|
|
18
|
|
|
|
|
19
|
|
|
/** |
|
20
|
|
|
* Shows a categorized list of available permissions (through {@link Permission::get_codes()}). |
|
21
|
|
|
* Permissions which are assigned to a given {@link Group} record |
|
22
|
|
|
* (either directly, inherited from parent groups, or through a {@link PermissionRole}) |
|
23
|
|
|
* will be checked automatically. All checkboxes for "inherited" permissions will be readonly. |
|
24
|
|
|
* |
|
25
|
|
|
* The field can gets its assignment data either from {@link Group} or {@link PermissionRole} records. |
|
26
|
|
|
* |
|
27
|
|
|
* @package framework |
|
28
|
|
|
* @subpackage security |
|
29
|
|
|
*/ |
|
30
|
|
|
class PermissionCheckboxSetField extends FormField { |
|
31
|
|
|
|
|
32
|
|
|
/** |
|
33
|
|
|
* @var array Filter certain permission codes from the output. |
|
34
|
|
|
* Useful to simplify the interface |
|
35
|
|
|
*/ |
|
36
|
|
|
protected $hiddenPermissions = array(); |
|
37
|
|
|
|
|
38
|
|
|
/** |
|
39
|
|
|
* @var SS_List |
|
40
|
|
|
*/ |
|
41
|
|
|
protected $records = null; |
|
42
|
|
|
|
|
43
|
|
|
/** |
|
44
|
|
|
* @var array Array Nested array in same notation as {@link CheckboxSetField}. |
|
45
|
|
|
*/ |
|
46
|
|
|
protected $source = null; |
|
47
|
|
|
|
|
48
|
|
|
/** |
|
49
|
|
|
* @param String $name |
|
50
|
|
|
* @param String $title |
|
51
|
|
|
* @param String $managedClass |
|
52
|
|
|
* @param String $filterField |
|
53
|
|
|
* @param Group|SS_List $records One or more {@link Group} or {@link PermissionRole} records |
|
54
|
|
|
* used to determine permission checkboxes. |
|
55
|
|
|
* Caution: saveInto() can only be used with a single record, all inherited permissions will be marked readonly. |
|
56
|
|
|
* Setting multiple groups only makes sense in a readonly context. (Optional) |
|
57
|
|
|
*/ |
|
58
|
|
|
public function __construct($name, $title, $managedClass, $filterField, $records = null) { |
|
59
|
|
|
$this->filterField = $filterField; |
|
|
|
|
|
|
60
|
|
|
$this->managedClass = $managedClass; |
|
|
|
|
|
|
61
|
|
|
|
|
62
|
|
|
if($records instanceof SS_List) { |
|
63
|
|
|
$this->records = $records; |
|
64
|
|
|
} elseif($records instanceof Group) { |
|
65
|
|
|
$this->records = new ArrayList(array($records)); |
|
66
|
|
|
} elseif($records) { |
|
67
|
|
|
throw new InvalidArgumentException( |
|
68
|
|
|
'$record should be either a Group record, or a SS_List of Group records'); |
|
69
|
|
|
} |
|
70
|
|
|
|
|
71
|
|
|
// Get all available codes in the system as a categorized nested array |
|
72
|
|
|
$this->source = Permission::get_codes(true); |
|
73
|
|
|
|
|
74
|
|
|
parent::__construct($name, $title); |
|
75
|
|
|
} |
|
76
|
|
|
|
|
77
|
|
|
/** |
|
78
|
|
|
* @param array $codes |
|
79
|
|
|
*/ |
|
80
|
|
|
public function setHiddenPermissions($codes) { |
|
81
|
|
|
$this->hiddenPermissions = $codes; |
|
82
|
|
|
} |
|
83
|
|
|
|
|
84
|
|
|
/** |
|
85
|
|
|
* @return array |
|
86
|
|
|
*/ |
|
87
|
|
|
public function getHiddenPermissions() { |
|
88
|
|
|
return $this->hiddenPermissions; |
|
89
|
|
|
} |
|
90
|
|
|
|
|
91
|
|
|
/** |
|
92
|
|
|
* @param array $properties |
|
93
|
|
|
* @return DBHTMLText |
|
94
|
|
|
*/ |
|
95
|
|
|
public function Field($properties = array()) { |
|
96
|
|
|
Requirements::css(FRAMEWORK_DIR . '/client/dist/styles/CheckboxSetField.css'); |
|
97
|
|
|
Requirements::javascript(FRAMEWORK_DIR . '/client/dist/js/PermissionCheckboxSetField.js'); |
|
98
|
|
|
|
|
99
|
|
|
$uninheritedCodes = array(); |
|
100
|
|
|
$inheritedCodes = array(); |
|
101
|
|
|
$records = ($this->records) ? $this->records : new ArrayList(); |
|
102
|
|
|
|
|
103
|
|
|
// Get existing values from the form record (assuming the formfield name is a join field on the record) |
|
104
|
|
|
if(is_object($this->form)) { |
|
105
|
|
|
$record = $this->form->getRecord(); |
|
106
|
|
|
if( |
|
107
|
|
|
$record |
|
108
|
|
|
&& ($record instanceof Group || $record instanceof PermissionRole) |
|
109
|
|
|
&& !$records->find('ID', $record->ID) |
|
110
|
|
|
) { |
|
111
|
|
|
$records->push($record); |
|
|
|
|
|
|
112
|
|
|
} |
|
113
|
|
|
} |
|
114
|
|
|
|
|
115
|
|
|
// Get all 'inherited' codes not directly assigned to the group (which is stored in $values) |
|
116
|
|
|
foreach($records as $record) { |
|
117
|
|
|
// Get all uninherited permissions |
|
118
|
|
|
$relationMethod = $this->name; |
|
119
|
|
|
foreach($record->$relationMethod() as $permission) { |
|
120
|
|
|
if(!isset($uninheritedCodes[$permission->Code])) $uninheritedCodes[$permission->Code] = array(); |
|
121
|
|
|
$uninheritedCodes[$permission->Code][] = _t( |
|
122
|
|
|
'PermissionCheckboxSetField.AssignedTo', 'assigned to "{title}"', |
|
123
|
|
|
array('title' => $record->dbObject('Title')->forTemplate()) |
|
124
|
|
|
); |
|
125
|
|
|
} |
|
126
|
|
|
|
|
127
|
|
|
// Special case for Group records (not PermissionRole): |
|
128
|
|
|
// Determine inherited assignments |
|
129
|
|
|
if(is_a($record, 'SilverStripe\\Security\\Group')) { |
|
130
|
|
|
// Get all permissions from roles |
|
131
|
|
|
if ($record->Roles()->Count()) { |
|
132
|
|
|
foreach($record->Roles() as $role) { |
|
133
|
|
|
foreach($role->Codes() as $code) { |
|
134
|
|
|
if (!isset($inheritedCodes[$code->Code])) $inheritedCodes[$code->Code] = array(); |
|
135
|
|
|
$inheritedCodes[$code->Code][] = _t( |
|
136
|
|
|
'PermissionCheckboxSetField.FromRole', |
|
137
|
|
|
'inherited from role "{title}"', |
|
138
|
|
|
'A permission inherited from a certain permission role', |
|
139
|
|
|
array('title' => $role->dbObject('Title')->forTemplate()) |
|
140
|
|
|
); |
|
141
|
|
|
} |
|
142
|
|
|
} |
|
143
|
|
|
} |
|
144
|
|
|
|
|
145
|
|
|
// Get from parent groups |
|
146
|
|
|
$parentGroups = $record->getAncestors(); |
|
147
|
|
|
if ($parentGroups) { |
|
148
|
|
|
foreach ($parentGroups as $parent) { |
|
149
|
|
|
if (!$parent->Roles()->Count()) continue; |
|
150
|
|
|
foreach($parent->Roles() as $role) { |
|
151
|
|
|
if ($role->Codes()) { |
|
152
|
|
|
foreach($role->Codes() as $code) { |
|
153
|
|
|
if (!isset($inheritedCodes[$code->Code])) $inheritedCodes[$code->Code] = array(); |
|
154
|
|
|
$inheritedCodes[$code->Code][] = _t( |
|
155
|
|
|
'PermissionCheckboxSetField.FromRoleOnGroup', |
|
156
|
|
|
'inherited from role "%s" on group "%s"', |
|
157
|
|
|
'A permission inherited from a role on a certain group', |
|
158
|
|
|
array('roletitle' => $role->dbObject('Title')->forTemplate(), 'grouptitle' => $parent->dbObject('Title')->forTemplate()) |
|
159
|
|
|
); |
|
160
|
|
|
} |
|
161
|
|
|
} |
|
162
|
|
|
} |
|
163
|
|
|
if ($parent->Permissions()->Count()) { |
|
164
|
|
|
foreach($parent->Permissions() as $permission) { |
|
165
|
|
|
if (!isset($inheritedCodes[$permission->Code])) { |
|
166
|
|
|
$inheritedCodes[$permission->Code] = array(); |
|
167
|
|
|
} |
|
168
|
|
|
$inheritedCodes[$permission->Code][] = |
|
169
|
|
|
_t( |
|
170
|
|
|
'PermissionCheckboxSetField.FromGroup', |
|
171
|
|
|
'inherited from group "{title}"', |
|
172
|
|
|
'A permission inherited from a certain group', |
|
173
|
|
|
array('title' => $parent->dbObject('Title')->forTemplate()) |
|
174
|
|
|
); |
|
175
|
|
|
} |
|
176
|
|
|
} |
|
177
|
|
|
} |
|
178
|
|
|
} |
|
179
|
|
|
} |
|
180
|
|
|
} |
|
181
|
|
|
|
|
182
|
|
|
$odd = 0; |
|
183
|
|
|
$options = ''; |
|
184
|
|
|
$globalHidden = (array)Config::inst()->get('SilverStripe\\Security\\Permission', 'hidden_permissions'); |
|
185
|
|
|
if($this->source) { |
|
|
|
|
|
|
186
|
|
|
$privilegedPermissions = Permission::config()->privileged_permissions; |
|
|
|
|
|
|
187
|
|
|
|
|
188
|
|
|
// loop through all available categorized permissions and see if they're assigned for the given groups |
|
189
|
|
|
foreach($this->source as $categoryName => $permissions) { |
|
190
|
|
|
$options .= "<li><h5>$categoryName</h5></li>"; |
|
191
|
|
|
foreach($permissions as $code => $permission) { |
|
192
|
|
|
if(in_array($code, $this->hiddenPermissions)) continue; |
|
193
|
|
|
if(in_array($code, $globalHidden)) continue; |
|
194
|
|
|
|
|
195
|
|
|
$value = $permission['name']; |
|
196
|
|
|
|
|
197
|
|
|
$odd = ($odd + 1) % 2; |
|
198
|
|
|
$extraClass = $odd ? 'odd' : 'even'; |
|
199
|
|
|
$extraClass .= ' val' . str_replace(' ', '', $code); |
|
200
|
|
|
$itemID = $this->ID() . '_' . preg_replace('/[^a-zA-Z0-9]+/', '', $code); |
|
201
|
|
|
$checked = $disabled = $inheritMessage = ''; |
|
202
|
|
|
$checked = (isset($uninheritedCodes[$code]) || isset($inheritedCodes[$code])) |
|
203
|
|
|
? ' checked="checked"' |
|
204
|
|
|
: ''; |
|
205
|
|
|
$title = $permission['help'] |
|
206
|
|
|
? 'title="' . htmlentities($permission['help'], ENT_COMPAT, 'UTF-8') . '" ' |
|
207
|
|
|
: ''; |
|
208
|
|
|
|
|
209
|
|
|
if (isset($inheritedCodes[$code])) { |
|
210
|
|
|
// disable inherited codes, as any saving logic would be too complicate to express in this |
|
211
|
|
|
// interface |
|
212
|
|
|
$disabled = ' disabled="true"'; |
|
213
|
|
|
$inheritMessage = ' (' . join(', ', $inheritedCodes[$code]) . ')'; |
|
214
|
|
|
} elseif($this->records && $this->records->Count() > 1 && isset($uninheritedCodes[$code])) { |
|
215
|
|
|
// If code assignments are collected from more than one "source group", |
|
216
|
|
|
// show its origin automatically |
|
217
|
|
|
$inheritMessage = ' (' . join(', ', $uninheritedCodes[$code]).')'; |
|
218
|
|
|
} |
|
219
|
|
|
|
|
220
|
|
|
// Disallow modification of "privileged" permissions unless currently logged-in user is an admin |
|
221
|
|
|
if(!Permission::check('ADMIN') && in_array($code, $privilegedPermissions)) { |
|
222
|
|
|
$disabled = ' disabled="true"'; |
|
223
|
|
|
} |
|
224
|
|
|
|
|
225
|
|
|
// If the field is readonly, always mark as "disabled" |
|
226
|
|
|
if($this->readonly) $disabled = ' disabled="true"'; |
|
227
|
|
|
|
|
228
|
|
|
$inheritMessage = '<small>' . $inheritMessage . '</small>'; |
|
229
|
|
|
$icon = ($checked) ? 'accept' : 'decline'; |
|
230
|
|
|
|
|
231
|
|
|
// If the field is readonly, add a span that will replace the disabled checkbox input |
|
232
|
|
|
if($this->readonly) { |
|
233
|
|
|
$options .= "<li class=\"$extraClass\">" |
|
234
|
|
|
. "<input id=\"$itemID\"$disabled name=\"$this->name[$code]\" type=\"checkbox\"" |
|
235
|
|
|
. " value=\"$code\"$checked class=\"checkbox\" />" |
|
236
|
|
|
. "<label {$title}for=\"$itemID\">" |
|
237
|
|
|
. "<span class=\"ui-button-icon-primary ui-icon btn-icon-$icon\"></span>" |
|
238
|
|
|
. "$value$inheritMessage</label>" |
|
239
|
|
|
. "</li>\n"; |
|
240
|
|
|
} else { |
|
241
|
|
|
$options .= "<li class=\"$extraClass\">" |
|
242
|
|
|
. "<input id=\"$itemID\"$disabled name=\"$this->name[$code]\" type=\"checkbox\"" |
|
243
|
|
|
. " value=\"$code\"$checked class=\"checkbox\" />" |
|
244
|
|
|
. "<label {$title}for=\"$itemID\">$value$inheritMessage</label>" |
|
245
|
|
|
. "</li>\n"; |
|
246
|
|
|
} |
|
247
|
|
|
} |
|
248
|
|
|
} |
|
249
|
|
|
} |
|
250
|
|
|
if($this->readonly) { |
|
251
|
|
|
return DBField::create_field('HTMLText', |
|
252
|
|
|
"<ul id=\"{$this->ID()}\" class=\"optionset checkboxsetfield{$this->extraClass()}\">\n" . |
|
253
|
|
|
"<li class=\"help\">" . |
|
254
|
|
|
_t( |
|
255
|
|
|
'Permissions.UserPermissionsIntro', |
|
256
|
|
|
'Assigning groups to this user will adjust the permissions they have.' |
|
257
|
|
|
. ' See the groups section for details of permissions on individual groups.' |
|
258
|
|
|
) . |
|
259
|
|
|
"</li>" . |
|
260
|
|
|
$options . |
|
261
|
|
|
"</ul>\n" |
|
262
|
|
|
); |
|
263
|
|
|
} else { |
|
264
|
|
|
return DBField::create_field('HTMLText', |
|
265
|
|
|
"<ul id=\"{$this->ID()}\" class=\"optionset checkboxsetfield{$this->extraClass()}\">\n" . |
|
266
|
|
|
$options . |
|
267
|
|
|
"</ul>\n" |
|
268
|
|
|
); |
|
269
|
|
|
} |
|
270
|
|
|
} |
|
271
|
|
|
|
|
272
|
|
|
/** |
|
273
|
|
|
* Update the permission set associated with $record DataObject |
|
274
|
|
|
* |
|
275
|
|
|
* @param DataObjectInterface $record |
|
276
|
|
|
*/ |
|
277
|
|
|
public function saveInto(DataObjectInterface $record) { |
|
278
|
|
|
$fieldname = $this->name; |
|
279
|
|
|
$managedClass = $this->managedClass; |
|
|
|
|
|
|
280
|
|
|
|
|
281
|
|
|
// Remove all "privileged" permissions if the currently logged-in user is not an admin |
|
282
|
|
|
$privilegedPermissions = Permission::config()->privileged_permissions; |
|
|
|
|
|
|
283
|
|
|
if(!Permission::check('ADMIN')) { |
|
284
|
|
|
foreach($this->value as $id => $bool) { |
|
285
|
|
|
if(in_array($id, $privilegedPermissions)) { |
|
286
|
|
|
unset($this->value[$id]); |
|
287
|
|
|
} |
|
288
|
|
|
} |
|
289
|
|
|
} |
|
290
|
|
|
|
|
291
|
|
|
// remove all permissions and re-add them afterwards |
|
292
|
|
|
$permissions = $record->$fieldname(); |
|
293
|
|
|
foreach ( $permissions as $permission ) { |
|
294
|
|
|
$permission->delete(); |
|
295
|
|
|
} |
|
296
|
|
|
|
|
297
|
|
|
if($fieldname && $record && ($record->hasManyComponent($fieldname) || $record->manyManyComponent($fieldname))) { |
|
298
|
|
|
|
|
299
|
|
|
if(!$record->ID) $record->write(); // We need a record ID to write permissions |
|
|
|
|
|
|
300
|
|
|
|
|
301
|
|
|
$idList = array(); |
|
302
|
|
|
if($this->value) foreach($this->value as $id => $bool) { |
|
303
|
|
|
if($bool) { |
|
304
|
|
|
$perm = new $managedClass(); |
|
305
|
|
|
$perm->{$this->filterField} = $record->ID; |
|
|
|
|
|
|
306
|
|
|
$perm->Code = $id; |
|
307
|
|
|
$perm->write(); |
|
308
|
|
|
} |
|
309
|
|
|
} |
|
310
|
|
|
} |
|
311
|
|
|
} |
|
312
|
|
|
|
|
313
|
|
|
/** |
|
314
|
|
|
* @return PermissionCheckboxSetField_Readonly |
|
315
|
|
|
*/ |
|
316
|
|
|
public function performReadonlyTransformation() { |
|
317
|
|
|
$readonly = new PermissionCheckboxSetField_Readonly( |
|
318
|
|
|
$this->name, |
|
319
|
|
|
$this->title, |
|
320
|
|
|
$this->managedClass, |
|
321
|
|
|
$this->filterField, |
|
322
|
|
|
$this->records |
|
323
|
|
|
); |
|
324
|
|
|
|
|
325
|
|
|
return $readonly; |
|
326
|
|
|
} |
|
327
|
|
|
|
|
328
|
|
|
/** |
|
329
|
|
|
* Retrieves all permission codes for the currently set records |
|
330
|
|
|
* |
|
331
|
|
|
* @return array |
|
332
|
|
|
*/ |
|
333
|
|
|
public function getAssignedPermissionCodes() { |
|
334
|
|
|
if(!$this->records) return false; |
|
335
|
|
|
|
|
336
|
|
|
// TODO |
|
337
|
|
|
|
|
338
|
|
|
return $codes; |
|
|
|
|
|
|
339
|
|
|
} |
|
340
|
|
|
} |
|
341
|
|
|
|
|
342
|
|
|
/** |
|
343
|
|
|
* Readonly version of a {@link PermissionCheckboxSetField} - |
|
344
|
|
|
* uses the same structure, but has all checkboxes disabled. |
|
345
|
|
|
* |
|
346
|
|
|
* @package framework |
|
347
|
|
|
* @subpackage security |
|
348
|
|
|
*/ |
|
349
|
|
|
class PermissionCheckboxSetField_Readonly extends PermissionCheckboxSetField { |
|
350
|
|
|
|
|
351
|
|
|
protected $readonly = true; |
|
352
|
|
|
|
|
353
|
|
|
public function saveInto(DataObjectInterface $record) { |
|
354
|
|
|
return false; |
|
355
|
|
|
} |
|
356
|
|
|
} |
|
357
|
|
|
|
Since your code implements the magic setter
_set, this function will be called for any write access on an undefined variable. You can add the@propertyannotation to your class or interface to document the existence of this variable.Since the property has write access only, you can use the @property-write annotation instead.
Of course, you may also just have mistyped another name, in which case you should fix the error.
See also the PhpDoc documentation for @property.