Version: current

      1 <?php
      2 /**
      3  * Shows a categorized list of available permissions (through {@link Permission::get_codes()}).
      4  * Permissions which are assigned to a given {@link Group} record
      5  * (either directly, inherited from parent groups, or through a {@link PermissionRole})
      6  * will be checked automatically. All checkboxes for "inherited" permissions will be readonly.
      7  *
      8  * The field can gets its assignment data either from {@link Group} or {@link PermissionRole} records.
      9  *
     10  * @package sapphire
     11  * @subpackage security
     12  */
     13 class PermissionCheckboxSetField extends FormField {
     14
     15     /**
     16      * @var Array Filter certain permission codes from the output.
     17      * Useful to simplify the interface
     18      */
     19     protected $hiddenPermissions = array();
     20
     21     /**
     22      * @var DataObjectSet
     23      */
     24     protected $records = null;
     25
     26     /**
     27      * @var array Array Nested array in same notation as {@link CheckboxSetField}.
     28      */
     29     protected $source = null;
     30
     31     /**
     32      * @param String $name
     33      * @param String $title
     34      * @param String $managedClass
     35      * @param String $filterField
     36      * @param Group|DataObjectSet $records One or more {@link Group} or {@link PermissionRole} records
     37      *  used to determine permission checkboxes.
     38      *  Caution: saveInto() can only be used with a single record, all inherited permissions will be marked readonly.
     39      *  Setting multiple groups only makes sense in a readonly context. (Optional)
     40      */
     41     function __construct($name, $title, $managedClass, $filterField, $records = null) {
     42         $this->filterField = $filterField;
     43         $this->managedClass = $managedClass;
     44
     45         if(is_a($records, 'DataObjectSet')) {
     46             $this->records = $records;
     47         } elseif(is_a($records, 'DataObject')) {
     48             $this->records = new DataObjectSet($records);
     49         } elseif($records) {
     50             throw new InvalidArgumentException('$record should be either a Group record, or a DataObjectSet of Group records');
     51         }
     52
     53         // Get all available codes in the system as a categorized nested array
     54         $this->source = Permission::get_codes(true);
     55
     56         parent::__construct($name, $title);
     57     }
     58
     59     /**
     60      * @param Array $codes
     61      */
     62     function setHiddenPermissions($codes) {
     63         $this->hiddenPermissions = $codes;
     64     }
     65
     66     /**
     67      * @return Array
     68      */
     69     function getHiddenPermissions() {
     70         return $this->hiddenPermissions;
     71     }
     72
     73     function Field() {
     74         Requirements::css(SAPPHIRE_DIR . '/css/CheckboxSetField.css');
     75         Requirements::javascript(SAPPHIRE_DIR . '/javascript/PermissionCheckboxSetField.js');
     76
     77         $uninheritedCodes = array();
     78         $inheritedCodes = array();
     79         $records = ($this->records) ? $this->records : new DataObjectSet();
     80
     81         // Get existing values from the form record (assuming the formfield name is a join field on the record)
     82         if(is_object($this->form)) {
     83             $record = $this->form->getRecord();
     84             if(
     85                 $record
     86                 && (is_a($record, 'Group') || is_a($record, 'PermissionRole'))
     87                 && !$records->find('ID', $record->ID)
     88             ) {
     89                 $records->push($record);
     90             }
     91         }
     92
     93         // Get all 'inherited' codes not directly assigned to the group (which is stored in $values)
     94         foreach($records as $record) {
     95             // Get all uninherited permissions
     96             $relationMethod = $this->name;
     97             foreach($record->$relationMethod() as $permission) {
     98                 if(!isset($uninheritedCodes[$permission->Code])) $uninheritedCodes[$permission->Code] = array();
     99                 $uninheritedCodes[$permission->Code][] = sprintf(
    100                     _t('PermissionCheckboxSetField.AssignedTo', 'assigned to "%s"'),
    101                     $record->Title
    102                 );
    103             }
    104
    105             // Special case for Group records (not PermissionRole):
    106             // Determine inherited assignments
    107             if(is_a($record, 'Group')) {
    108                 // Get all permissions from roles
    109                 if ($record->Roles()->Count()) {
    110                     foreach($record->Roles() as $role) {
    111                         foreach($role->Codes() as $code) {
    112                             if (!isset($inheritedCodes[$code->Code])) $inheritedCodes[$code->Code] = array();
    113                             $inheritedCodes[$code->Code][] = sprintf(
    114                                 _t(
    115                                     'PermissionCheckboxSetField.FromRole',
    116                                     'inherited from role "%s"',
    117                                     PR_MEDIUM,
    118                                     'A permission inherited from a certain permission role'
    119                                 ),
    120                                 $role->Title
    121                             );
    122                         }
    123                     }
    124                 }
    125
    126                 // Get from parent groups
    127                 $parentGroups = $record->getAncestors();
    128                 if ($parentGroups) {
    129                     foreach ($parentGroups as $parent) {
    130                         if (!$parent->Roles()->Count()) continue;
    131                         foreach($parent->Roles() as $role) {
    132                             if ($role->Codes()) {
    133                                 foreach($role->Codes() as $code) {
    134                                     if (!isset($inheritedCodes[$code->Code])) $inheritedCodes[$code->Code] = array();
    135                                     $inheritedCodes[$code->Code][] = sprintf(
    136                                         _t(
    137                                             'PermissionCheckboxSetField.FromRoleOnGroup',
    138                                             'inherited from role "%s" on group "%s"',
    139                                             PR_MEDIUM,
    140                                             'A permission inherited from a role on a certain group'
    141                                         ),
    142                                         $role->Title,
    143                                         $parent->Title
    144                                     );
    145                                 }
    146                             }
    147                         }
    148                         if ($parent->Permissions()->Count()) {
    149                             foreach($parent->Permissions() as $permission) {
    150                                 if (!isset($inheritedCodes[$permission->Code])) $inheritedCodes[$permission->Code] = array();
    151                                 $inheritedCodes[$permission->Code][] =
    152                                 sprintf(
    153                                     _t(
    154                                         'PermissionCheckboxSetField.FromGroup',
    155                                         'inherited from group "%s"',
    156                                         PR_MEDIUM,
    157                                         'A permission inherited from a certain group'
    158                                     ),
    159                                     $parent->Title
    160                                 );
    161                             }
    162                         }
    163                     }
    164                 }
    165             }
    166         }
    167
    168         $odd = 0;
    169         $options = '';
    170         if($this->source) {
    171             // loop through all available categorized permissions and see if they're assigned for the given groups
    172             foreach($this->source as $categoryName => $permissions) {
    173                 $options .= "<li><h5>$categoryName</h5></li>";
    174                 foreach($permissions as $code => $permission) {
    175                     if(in_array($code, $this->hiddenPermissions)) continue;
    176
    177                     $value = $permission['name'];
    178
    179                     $odd = ($odd + 1) % 2;
    180                     $extraClass = $odd ? 'odd' : 'even';
    181                     $extraClass .= ' val' . str_replace(' ', '', $code);
    182                     $itemID = $this->id() . '_' . ereg_replace('[^a-zA-Z0-9]+', '', $code);
    183                     $checked = $disabled = $inheritMessage = '';
    184                     $checked = (isset($uninheritedCodes[$code]) || isset($inheritedCodes[$code])) ? ' checked="checked"' : '';
    185                     $title = $permission['help'] ? 'title="' . htmlspecialchars($permission['help']) . '" ' : '';
    186
    187                     if (isset($inheritedCodes[$code])) {
    188                         // disable inherited codes, as any saving logic would be too complicate to express in this interface
    189                         $disabled = ' disabled="true"';
    190                         $inheritMessage = ' (' . join(', ', $inheritedCodes[$code]) . ')';
    191                     } elseif($this->records && $this->records->Count() > 1 && isset($uninheritedCodes[$code])) {
    192                         // If code assignments are collected from more than one "source group",
    193                         // show its origin automatically
    194                         $inheritMessage = ' (' . join(', ', $uninheritedCodes[$code]).')';
    195                     }
    196
    197                     // If the field is readonly, always mark as "disabled"
    198                     if($this->readonly) $disabled = ' disabled="true"';
    199
    200                     $inheritMessage = '<small>' . $inheritMessage . '</small>';
    201                     $options .= "<li class=\"$extraClass\">" .
    202                         "<input id=\"$itemID\"$disabled name=\"$this->name[$code]\" type=\"checkbox\" value=\"$code\"$checked class=\"checkbox\" />" .
    203                         "<label {$title}for=\"$itemID\">$value$inheritMessage</label>" .
    204                         "</li>\n";
    205                 }
    206             }
    207         }
    208
    209         return "<ul id=\"{$this->id()}\" class=\"optionset checkboxsetfield{$this->extraClass()}\">\n$options</ul>\n";
    210     }
    211
    212     /**
    213      * Update the permission set associated with $record DataObject
    214      *
    215      * @param DataObject $record
    216      */
    217     function saveInto(DataObject $record) {
    218         $fieldname = $this->name;
    219         $managedClass = $this->managedClass;
    220
    221         // remove all permissions and re-add them afterwards
    222         $permissions = $record->$fieldname();
    223         foreach ( $permissions as $permission ) {
    224             $permission->delete();
    225         }
    226
    227         if($fieldname && $record && ($record->has_many($fieldname) || $record->many_many($fieldname))) {
    228             $idList = array();
    229             if($this->value) foreach($this->value as $id => $bool) {
    230                if($bool) {
    231                     $perm = new $managedClass();
    232                     $perm->{$this->filterField} = $record->ID;
    233                     $perm->Code = $id;
    234                     $perm->write();
    235                 }
    236             }
    237         }
    238     }
    239
    240     /**
    241      * @return PermissionCheckboxSetField_Readonly
    242      */
    243     function performReadonlyTransformation() {
    244         $readonly = new PermissionCheckboxSetField_Readonly(
    245             $this->name,
    246             $this->title,
    247             $this->managedClass,
    248             $this->filterField,
    249             $this->records
    250         );
    251
    252         return $readonly;
    253     }
    254
    255     /**
    256      * Retrieves all permission codes for the currently set records
    257      *
    258      * @return array
    259      */
    260     function getAssignedPermissionCodes() {
    261         if(!$this->records) return false;
    262
    263         // TODO
    264
    265         return $codes;
    266     }
    267 }
    268
    269 /**
    270  * Readonly version of a {@link PermissionCheckboxSetField} -
    271  * uses the same structure, but has all checkboxes disabled.
    272  *
    273  * @package sapphire
    274  * @subpackage security
    275  */
    276 class PermissionCheckboxSetField_Readonly extends PermissionCheckboxSetField {
    277
    278     protected $readonly = true;
    279
    280     function saveInto($record) {
    281         return false;
    282     }
    283 }

[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the Silverstripe Forums to ask development related questions. -