silverstripe-framework/Security/PermissionCheckboxSetField.php
Damian Mooyman 5c9044a007 API Enforce default_cast for all field usages
API Introduce HTMLFragment as casting helper for HTMLText with shortcodes disabled
API Introduce DBField::CDATA for XML file value encoding
API RSSFeed now casts from the underlying model rather than by override
API Introduce CustomMethods::getExtraMethodConfig() to allow metadata to be queried
BUG Remove _call hack from VirtualPage
API Remove FormField::$dontEscape
API Introduce HTMLReadonlyField for non-editable readonly HTML
API FormField::Field() now returns string in many cases rather than DBField instance.
API Remove redundant *_val methods from ViewableData
API ViewableData::obj() no longer has a $forceReturnObject parameter as it always returns an object
BUG  Fix issue with ViewableData caching incorrect field values after being modified.
API Remove deprecated DB class methods
API Enforce plain text left/right formfield titles
2016-07-13 17:15:45 +12:00

355 lines
11 KiB
PHP

<?php
namespace SilverStripe\Security;
use SilverStripe\ORM\FieldType\DBHTMLText;
use SilverStripe\ORM\SS_List;
use SilverStripe\ORM\ArrayList;
use SilverStripe\ORM\FieldType\DBField;
use SilverStripe\ORM\DataObjectInterface;
use FormField;
use InvalidArgumentException;
use Requirements;
use Config;
/**
* 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.
*
* The field can gets its assignment data either from {@link Group} or {@link PermissionRole} records.
*
* @package framework
* @subpackage security
*/
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()) {
Requirements::css(FRAMEWORK_DIR . '/client/dist/styles/CheckboxSetField.css');
Requirements::javascript(FRAMEWORK_DIR . '/client/dist/js/PermissionCheckboxSetField.js');
$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(
'PermissionCheckboxSetField.AssignedTo', 'assigned to "{title}"',
array('title' => $record->dbObject('Title')->forTemplate())
);
}
// Special case for Group records (not PermissionRole):
// Determine inherited assignments
if(is_a($record, 'SilverStripe\\Security\\Group')) {
// Get all permissions from roles
if ($record->Roles()->Count()) {
foreach($record->Roles() as $role) {
foreach($role->Codes() as $code) {
if (!isset($inheritedCodes[$code->Code])) $inheritedCodes[$code->Code] = array();
$inheritedCodes[$code->Code][] = _t(
'PermissionCheckboxSetField.FromRole',
'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(
'PermissionCheckboxSetField.FromRoleOnGroup',
'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(
'PermissionCheckboxSetField.FromGroup',
'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);
$checked = $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>';
$icon = ($checked) ? 'accept' : 'decline';
// 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\">"
. "<span class=\"ui-button-icon-primary ui-icon btn-icon-$icon\"></span>"
. "$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();
}
if($fieldname && $record && ($record->hasManyComponent($fieldname) || $record->manyManyComponent($fieldname))) {
if(!$record->ID) $record->write(); // We need a record ID to write permissions
$idList = array();
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;
}
/**
* Retrieves all permission codes for the currently set records
*
* @return array
*/
public function getAssignedPermissionCodes() {
if(!$this->records) return false;
// TODO
return $codes;
}
}
/**
* Readonly version of a {@link PermissionCheckboxSetField} -
* uses the same structure, but has all checkboxes disabled.
*
* @package framework
* @subpackage security
*/
class PermissionCheckboxSetField_Readonly extends PermissionCheckboxSetField {
protected $readonly = true;
public function saveInto(DataObjectInterface $record) {
return false;
}
}