2009-10-29 00:03:35 +01:00
|
|
|
<?php
|
2015-08-30 07:02:55 +02:00
|
|
|
|
2016-06-23 01:37:22 +02:00
|
|
|
namespace SilverStripe\Security;
|
2016-06-15 06:03:16 +02:00
|
|
|
|
2016-08-19 00:51:35 +02:00
|
|
|
use SilverStripe\Core\Config\Config;
|
|
|
|
use SilverStripe\Forms\FormField;
|
2016-10-06 06:31:38 +02:00
|
|
|
use SilverStripe\ORM\DataObject;
|
2016-06-15 06:03:16 +02:00
|
|
|
use SilverStripe\ORM\SS_List;
|
|
|
|
use SilverStripe\ORM\ArrayList;
|
|
|
|
use SilverStripe\ORM\DataObjectInterface;
|
2016-08-19 00:51:35 +02:00
|
|
|
use SilverStripe\View\Requirements;
|
2016-06-23 01:37:22 +02:00
|
|
|
use InvalidArgumentException;
|
2015-08-30 07:02:55 +02:00
|
|
|
|
2010-02-23 01:55:02 +01:00
|
|
|
/**
|
|
|
|
* Shows a categorized list of available permissions (through {@link Permission::get_codes()}).
|
|
|
|
* Permissions which are assigned to a given {@link Group} record
|
|
|
|
* (either directly, inherited from parent groups, or through a {@link PermissionRole})
|
|
|
|
* will be checked automatically. All checkboxes for "inherited" permissions will be readonly.
|
2014-08-15 08:53:05 +02:00
|
|
|
*
|
2010-02-23 01:57:03 +01:00
|
|
|
* The field can gets its assignment data either from {@link Group} or {@link PermissionRole} records.
|
2010-02-23 01:55:02 +01:00
|
|
|
*/
|
2016-11-29 00:31:16 +01:00
|
|
|
class PermissionCheckboxSetField extends FormField
|
|
|
|
{
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var array Filter certain permission codes from the output.
|
|
|
|
* Useful to simplify the interface
|
|
|
|
*/
|
|
|
|
protected $hiddenPermissions = array();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var SS_List
|
|
|
|
*/
|
|
|
|
protected $records = null;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var array Array Nested array in same notation as {@link CheckboxSetField}.
|
|
|
|
*/
|
|
|
|
protected $source = null;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param String $name
|
|
|
|
* @param String $title
|
|
|
|
* @param String $managedClass
|
|
|
|
* @param String $filterField
|
|
|
|
* @param Group|SS_List $records One or more {@link Group} or {@link PermissionRole} records
|
|
|
|
* used to determine permission checkboxes.
|
|
|
|
* Caution: saveInto() can only be used with a single record, all inherited permissions will be marked readonly.
|
|
|
|
* Setting multiple groups only makes sense in a readonly context. (Optional)
|
|
|
|
*/
|
|
|
|
public function __construct($name, $title, $managedClass, $filterField, $records = null)
|
|
|
|
{
|
|
|
|
$this->filterField = $filterField;
|
|
|
|
$this->managedClass = $managedClass;
|
|
|
|
|
|
|
|
if ($records instanceof SS_List) {
|
|
|
|
$this->records = $records;
|
|
|
|
} elseif ($records instanceof Group) {
|
|
|
|
$this->records = new ArrayList(array($records));
|
|
|
|
} elseif ($records) {
|
|
|
|
throw new InvalidArgumentException(
|
|
|
|
'$record should be either a Group record, or a SS_List of Group records'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get all available codes in the system as a categorized nested array
|
|
|
|
$this->source = Permission::get_codes(true);
|
|
|
|
|
|
|
|
parent::__construct($name, $title);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param array $codes
|
|
|
|
*/
|
|
|
|
public function setHiddenPermissions($codes)
|
|
|
|
{
|
|
|
|
$this->hiddenPermissions = $codes;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function getHiddenPermissions()
|
|
|
|
{
|
|
|
|
return $this->hiddenPermissions;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param array $properties
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function Field($properties = array())
|
|
|
|
{
|
|
|
|
$uninheritedCodes = array();
|
|
|
|
$inheritedCodes = array();
|
|
|
|
$records = ($this->records) ? $this->records : new ArrayList();
|
|
|
|
|
|
|
|
// Get existing values from the form record (assuming the formfield name is a join field on the record)
|
|
|
|
if (is_object($this->form)) {
|
|
|
|
$record = $this->form->getRecord();
|
|
|
|
if ($record
|
|
|
|
&& ($record instanceof Group || $record instanceof PermissionRole)
|
|
|
|
&& !$records->find('ID', $record->ID)
|
|
|
|
) {
|
|
|
|
$records->push($record);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get all 'inherited' codes not directly assigned to the group (which is stored in $values)
|
|
|
|
foreach ($records as $record) {
|
|
|
|
// Get all uninherited permissions
|
|
|
|
$relationMethod = $this->name;
|
|
|
|
foreach ($record->$relationMethod() as $permission) {
|
|
|
|
if (!isset($uninheritedCodes[$permission->Code])) {
|
|
|
|
$uninheritedCodes[$permission->Code] = array();
|
|
|
|
}
|
|
|
|
$uninheritedCodes[$permission->Code][] = _t(
|
2017-04-20 03:15:24 +02:00
|
|
|
'SilverStripe\\Security\\PermissionCheckboxSetField.AssignedTo',
|
2016-11-29 00:31:16 +01:00
|
|
|
'assigned to "{title}"',
|
|
|
|
array('title' => $record->dbObject('Title')->forTemplate())
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Special case for Group records (not PermissionRole):
|
|
|
|
// Determine inherited assignments
|
|
|
|
if ($record instanceof Group) {
|
|
|
|
// Get all permissions from roles
|
|
|
|
if ($record->Roles()->count()) {
|
|
|
|
foreach ($record->Roles() as $role) {
|
|
|
|
/** @var PermissionRole $role */
|
|
|
|
foreach ($role->Codes() as $code) {
|
|
|
|
if (!isset($inheritedCodes[$code->Code])) {
|
|
|
|
$inheritedCodes[$code->Code] = array();
|
|
|
|
}
|
|
|
|
$inheritedCodes[$code->Code][] = _t(
|
2017-04-20 03:15:24 +02:00
|
|
|
'SilverStripe\\Security\\PermissionCheckboxSetField.FromRole',
|
2016-11-29 00:31:16 +01:00
|
|
|
'inherited from role "{title}"',
|
|
|
|
'A permission inherited from a certain permission role',
|
|
|
|
array('title' => $role->dbObject('Title')->forTemplate())
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get from parent groups
|
|
|
|
$parentGroups = $record->getAncestors();
|
|
|
|
if ($parentGroups) {
|
|
|
|
foreach ($parentGroups as $parent) {
|
|
|
|
if (!$parent->Roles()->Count()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
foreach ($parent->Roles() as $role) {
|
|
|
|
if ($role->Codes()) {
|
|
|
|
foreach ($role->Codes() as $code) {
|
|
|
|
if (!isset($inheritedCodes[$code->Code])) {
|
|
|
|
$inheritedCodes[$code->Code] = array();
|
|
|
|
}
|
|
|
|
$inheritedCodes[$code->Code][] = _t(
|
2017-04-20 03:15:24 +02:00
|
|
|
'SilverStripe\\Security\\PermissionCheckboxSetField.FromRoleOnGroup',
|
2016-11-29 00:31:16 +01:00
|
|
|
'inherited from role "%s" on group "%s"',
|
|
|
|
'A permission inherited from a role on a certain group',
|
|
|
|
array('roletitle' => $role->dbObject('Title')->forTemplate(), 'grouptitle' => $parent->dbObject('Title')->forTemplate())
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ($parent->Permissions()->Count()) {
|
|
|
|
foreach ($parent->Permissions() as $permission) {
|
|
|
|
if (!isset($inheritedCodes[$permission->Code])) {
|
|
|
|
$inheritedCodes[$permission->Code] = array();
|
|
|
|
}
|
|
|
|
$inheritedCodes[$permission->Code][] =
|
|
|
|
_t(
|
2017-04-20 03:15:24 +02:00
|
|
|
'SilverStripe\\Security\\PermissionCheckboxSetField.FromGroup',
|
2016-11-29 00:31:16 +01:00
|
|
|
'inherited from group "{title}"',
|
|
|
|
'A permission inherited from a certain group',
|
|
|
|
array('title' => $parent->dbObject('Title')->forTemplate())
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$odd = 0;
|
|
|
|
$options = '';
|
|
|
|
$globalHidden = (array)Config::inst()->get('SilverStripe\\Security\\Permission', 'hidden_permissions');
|
|
|
|
if ($this->source) {
|
|
|
|
$privilegedPermissions = Permission::config()->privileged_permissions;
|
|
|
|
|
|
|
|
// loop through all available categorized permissions and see if they're assigned for the given groups
|
|
|
|
foreach ($this->source as $categoryName => $permissions) {
|
|
|
|
$options .= "<li><h5>$categoryName</h5></li>";
|
|
|
|
foreach ($permissions as $code => $permission) {
|
|
|
|
if (in_array($code, $this->hiddenPermissions)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (in_array($code, $globalHidden)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$value = $permission['name'];
|
|
|
|
|
|
|
|
$odd = ($odd + 1) % 2;
|
|
|
|
$extraClass = $odd ? 'odd' : 'even';
|
|
|
|
$extraClass .= ' val' . str_replace(' ', '', $code);
|
|
|
|
$itemID = $this->ID() . '_' . preg_replace('/[^a-zA-Z0-9]+/', '', $code);
|
|
|
|
$disabled = $inheritMessage = '';
|
|
|
|
$checked = (isset($uninheritedCodes[$code]) || isset($inheritedCodes[$code]))
|
|
|
|
? ' checked="checked"'
|
|
|
|
: '';
|
|
|
|
$title = $permission['help']
|
|
|
|
? 'title="' . htmlentities($permission['help'], ENT_COMPAT, 'UTF-8') . '" '
|
|
|
|
: '';
|
|
|
|
|
|
|
|
if (isset($inheritedCodes[$code])) {
|
|
|
|
// disable inherited codes, as any saving logic would be too complicate to express in this
|
|
|
|
// interface
|
|
|
|
$disabled = ' disabled="true"';
|
|
|
|
$inheritMessage = ' (' . join(', ', $inheritedCodes[$code]) . ')';
|
|
|
|
} elseif ($this->records && $this->records->Count() > 1 && isset($uninheritedCodes[$code])) {
|
|
|
|
// If code assignments are collected from more than one "source group",
|
|
|
|
// show its origin automatically
|
|
|
|
$inheritMessage = ' (' . join(', ', $uninheritedCodes[$code]).')';
|
|
|
|
}
|
|
|
|
|
|
|
|
// Disallow modification of "privileged" permissions unless currently logged-in user is an admin
|
|
|
|
if (!Permission::check('ADMIN') && in_array($code, $privilegedPermissions)) {
|
|
|
|
$disabled = ' disabled="true"';
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the field is readonly, always mark as "disabled"
|
|
|
|
if ($this->readonly) {
|
|
|
|
$disabled = ' disabled="true"';
|
|
|
|
}
|
|
|
|
|
|
|
|
$inheritMessage = '<small>' . $inheritMessage . '</small>';
|
2016-11-30 22:07:07 +01:00
|
|
|
$icon = ($checked) ? 'check-mark-circle' : 'cancel-circled';
|
2016-11-29 00:31:16 +01:00
|
|
|
|
2017-01-18 01:22:29 +01:00
|
|
|
// Inherited codes are shown as a gray x
|
|
|
|
if (Permission::check('ADMIN') && $code != 'ADMIN') {
|
|
|
|
$icon = 'disable-circled';
|
|
|
|
}
|
|
|
|
|
2016-11-29 00:31:16 +01:00
|
|
|
// If the field is readonly, add a span that will replace the disabled checkbox input
|
|
|
|
if ($this->readonly) {
|
|
|
|
$options .= "<li class=\"$extraClass\">"
|
|
|
|
. "<input id=\"$itemID\"$disabled name=\"$this->name[$code]\" type=\"checkbox\""
|
|
|
|
. " value=\"$code\"$checked class=\"checkbox\" />"
|
|
|
|
. "<label {$title}for=\"$itemID\">"
|
2016-11-30 22:07:07 +01:00
|
|
|
. "<span class=\"font-icon-$icon\"></span>"
|
2016-11-29 00:31:16 +01:00
|
|
|
. "$value$inheritMessage</label>"
|
|
|
|
. "</li>\n";
|
|
|
|
} else {
|
|
|
|
$options .= "<li class=\"$extraClass\">"
|
|
|
|
. "<input id=\"$itemID\"$disabled name=\"$this->name[$code]\" type=\"checkbox\""
|
|
|
|
. " value=\"$code\"$checked class=\"checkbox\" />"
|
|
|
|
. "<label {$title}for=\"$itemID\">$value$inheritMessage</label>"
|
|
|
|
. "</li>\n";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ($this->readonly) {
|
|
|
|
return
|
|
|
|
"<ul id=\"{$this->ID()}\" class=\"optionset checkboxsetfield{$this->extraClass()}\">\n" .
|
|
|
|
"<li class=\"help\">" .
|
|
|
|
_t(
|
|
|
|
'Permissions.UserPermissionsIntro',
|
|
|
|
'Assigning groups to this user will adjust the permissions they have.'
|
|
|
|
. ' See the groups section for details of permissions on individual groups.'
|
|
|
|
) .
|
|
|
|
"</li>" .
|
|
|
|
$options .
|
|
|
|
"</ul>\n";
|
|
|
|
} else {
|
|
|
|
return
|
|
|
|
"<ul id=\"{$this->ID()}\" class=\"optionset checkboxsetfield{$this->extraClass()}\">\n" .
|
|
|
|
$options .
|
|
|
|
"</ul>\n";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Update the permission set associated with $record DataObject
|
|
|
|
*
|
|
|
|
* @param DataObjectInterface $record
|
|
|
|
*/
|
|
|
|
public function saveInto(DataObjectInterface $record)
|
|
|
|
{
|
|
|
|
$fieldname = $this->name;
|
|
|
|
$managedClass = $this->managedClass;
|
|
|
|
|
|
|
|
// Remove all "privileged" permissions if the currently logged-in user is not an admin
|
|
|
|
$privilegedPermissions = Permission::config()->privileged_permissions;
|
|
|
|
if (!Permission::check('ADMIN')) {
|
|
|
|
foreach ($this->value as $id => $bool) {
|
|
|
|
if (in_array($id, $privilegedPermissions)) {
|
|
|
|
unset($this->value[$id]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// remove all permissions and re-add them afterwards
|
|
|
|
$permissions = $record->$fieldname();
|
|
|
|
foreach ($permissions as $permission) {
|
|
|
|
$permission->delete();
|
|
|
|
}
|
|
|
|
|
|
|
|
$schema = DataObject::getSchema();
|
|
|
|
if ($fieldname && $record && (
|
|
|
|
$schema->hasManyComponent(get_class($record), $fieldname)
|
|
|
|
|| $schema->manyManyComponent(get_class($record), $fieldname)
|
|
|
|
)) {
|
|
|
|
if (!$record->ID) {
|
|
|
|
$record->write(); // We need a record ID to write permissions
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->value) {
|
|
|
|
foreach ($this->value as $id => $bool) {
|
|
|
|
if ($bool) {
|
|
|
|
$perm = new $managedClass();
|
|
|
|
$perm->{$this->filterField} = $record->ID;
|
|
|
|
$perm->Code = $id;
|
|
|
|
$perm->write();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return PermissionCheckboxSetField_Readonly
|
|
|
|
*/
|
|
|
|
public function performReadonlyTransformation()
|
|
|
|
{
|
|
|
|
$readonly = new PermissionCheckboxSetField_Readonly(
|
|
|
|
$this->name,
|
|
|
|
$this->title,
|
|
|
|
$this->managedClass,
|
|
|
|
$this->filterField,
|
|
|
|
$this->records
|
|
|
|
);
|
|
|
|
|
|
|
|
return $readonly;
|
|
|
|
}
|
2012-02-27 22:14:02 +01:00
|
|
|
}
|