Passed
Push — 4.4 ( a2a202...24015c )
by Maxime
08:07
created

Group::getDecodedBreadcrumbs()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 5
c 1
b 0
f 0
nc 2
nop 0
dl 0
loc 8
rs 10
1
<?php
2
3
namespace SilverStripe\Security;
4
5
use SilverStripe\Admin\SecurityAdmin;
0 ignored issues
show
Bug introduced by
The type SilverStripe\Admin\SecurityAdmin was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
6
use SilverStripe\Core\Convert;
7
use SilverStripe\Forms\DropdownField;
8
use SilverStripe\Forms\FieldList;
9
use SilverStripe\Forms\Form;
10
use SilverStripe\Forms\GridField\GridField;
11
use SilverStripe\Forms\GridField\GridFieldAddExistingAutocompleter;
12
use SilverStripe\Forms\GridField\GridFieldButtonRow;
13
use SilverStripe\Forms\GridField\GridFieldConfig_RelationEditor;
14
use SilverStripe\Forms\GridField\GridFieldDeleteAction;
15
use SilverStripe\Forms\GridField\GridFieldDetailForm;
16
use SilverStripe\Forms\GridField\GridFieldExportButton;
17
use SilverStripe\Forms\GridField\GridFieldGroupDeleteAction;
18
use SilverStripe\Forms\GridField\GridFieldPageCount;
19
use SilverStripe\Forms\GridField\GridFieldPrintButton;
20
use SilverStripe\Forms\HiddenField;
21
use SilverStripe\Forms\HTMLEditor\HTMLEditorConfig;
22
use SilverStripe\Forms\ListboxField;
23
use SilverStripe\Forms\LiteralField;
24
use SilverStripe\Forms\Tab;
25
use SilverStripe\Forms\TabSet;
26
use SilverStripe\Forms\TextareaField;
27
use SilverStripe\Forms\TextField;
28
use SilverStripe\ORM\ArrayList;
29
use SilverStripe\ORM\DataObject;
30
use SilverStripe\ORM\DataQuery;
31
use SilverStripe\ORM\HasManyList;
32
use SilverStripe\ORM\Hierarchy\Hierarchy;
33
use SilverStripe\ORM\ManyManyList;
34
use SilverStripe\ORM\UnsavedRelationList;
35
36
/**
37
 * A security group.
38
 *
39
 * @property string $Title Name of the group
40
 * @property string $Description Description of the group
41
 * @property string $Code Group code
42
 * @property string $Locked Boolean indicating whether group is locked in security panel
43
 * @property int $Sort
44
 * @property string HtmlEditorConfig
45
 *
46
 * @property int $ParentID ID of parent group
47
 *
48
 * @method Group Parent() Return parent group
49
 * @method HasManyList Permissions() List of group permissions
50
 * @method HasManyList Groups() List of child groups
51
 * @method ManyManyList Roles() List of PermissionRoles
52
 * @mixin Hierarchy
53
 */
54
class Group extends DataObject
55
{
56
57
    private static $db = array(
0 ignored issues
show
introduced by
The private property $db is not used, and could be removed.
Loading history...
58
        "Title" => "Varchar(255)",
59
        "Description" => "Text",
60
        "Code" => "Varchar(255)",
61
        "Locked" => "Boolean",
62
        "Sort" => "Int",
63
        "HtmlEditorConfig" => "Text"
64
    );
65
66
    private static $has_one = array(
0 ignored issues
show
introduced by
The private property $has_one is not used, and could be removed.
Loading history...
67
        "Parent" => Group::class,
68
    );
69
70
    private static $has_many = array(
0 ignored issues
show
introduced by
The private property $has_many is not used, and could be removed.
Loading history...
71
        "Permissions" => Permission::class,
72
        "Groups" => Group::class,
73
    );
74
75
    private static $many_many = array(
0 ignored issues
show
introduced by
The private property $many_many is not used, and could be removed.
Loading history...
76
        "Members" => Member::class,
77
        "Roles" => PermissionRole::class,
78
    );
79
80
    private static $extensions = array(
0 ignored issues
show
introduced by
The private property $extensions is not used, and could be removed.
Loading history...
81
        Hierarchy::class,
82
    );
83
84
    private static $table_name = "Group";
0 ignored issues
show
introduced by
The private property $table_name is not used, and could be removed.
Loading history...
85
86
    public function getAllChildren()
87
    {
88
        $doSet = new ArrayList();
89
90
        $children = Group::get()->filter("ParentID", $this->ID);
91
        /** @var Group $child */
92
        foreach ($children as $child) {
93
            $doSet->push($child);
94
            $doSet->merge($child->getAllChildren());
95
        }
96
97
        return $doSet;
98
    }
99
    
100
    private function getDecodedBreadcrumbs()
101
    {
102
        $list = Group::get()->exclude('ID', $this->ID);
103
        $groups = ArrayList::create();
104
        foreach ($list as $group) {
105
            $groups->push(['ID' => $group->ID, 'Title' => $group->getBreadcrumbs(' » ')]);
106
        }
107
        return $groups;
108
    }
109
110
    /**
111
     * Caution: Only call on instances, not through a singleton.
112
     * The "root group" fields will be created through {@link SecurityAdmin->EditForm()}.
113
     *
114
     * @skipUpgrade
115
     * @return FieldList
116
     */
117
    public function getCMSFields()
118
    {
119
        $fields = new FieldList(
120
            new TabSet(
121
                "Root",
122
                new Tab(
123
                    'Members',
124
                    _t(__CLASS__ . '.MEMBERS', 'Members'),
125
                    new TextField("Title", $this->fieldLabel('Title')),
126
                    $parentidfield = DropdownField::create(
127
                        'ParentID',
128
                        $this->fieldLabel('Parent'),
129
                        $this->getDecodedBreadcrumbs()
130
                    )->setEmptyString(' '),
131
                    new TextareaField('Description', $this->fieldLabel('Description'))
132
                ),
133
                $permissionsTab = new Tab(
134
                    'Permissions',
135
                    _t(__CLASS__ . '.PERMISSIONS', 'Permissions'),
136
                    $permissionsField = new PermissionCheckboxSetField(
137
                        'Permissions',
138
                        false,
0 ignored issues
show
Bug introduced by
false of type false is incompatible with the type string expected by parameter $title of SilverStripe\Security\Pe...SetField::__construct(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

138
                        /** @scrutinizer ignore-type */ false,
Loading history...
139
                        Permission::class,
140
                        'GroupID',
141
                        $this
142
                    )
143
                )
144
            )
145
        );
146
147
        $parentidfield->setDescription(
148
            _t('SilverStripe\\Security\\Group.GroupReminder', 'If you choose a parent group, this group will take all it\'s roles')
149
        );
150
151
        if ($this->ID) {
152
            $group = $this;
153
            $config = GridFieldConfig_RelationEditor::create();
154
            $config->addComponent(new GridFieldButtonRow('after'));
155
            $config->addComponents(new GridFieldExportButton('buttons-after-left'));
156
            $config->addComponents(new GridFieldPrintButton('buttons-after-left'));
157
            $config->removeComponentsByType(GridFieldDeleteAction::class);
158
            $config->addComponent(new GridFieldGroupDeleteAction($this->ID), GridFieldPageCount::class);
159
160
            /** @var GridFieldAddExistingAutocompleter $autocompleter */
161
            $autocompleter = $config->getComponentByType(GridFieldAddExistingAutocompleter::class);
162
            /** @skipUpgrade */
163
            $autocompleter
164
                ->setResultsFormat('$Title ($Email)')
165
                ->setSearchFields(array('FirstName', 'Surname', 'Email'));
166
            /** @var GridFieldDetailForm $detailForm */
167
            $detailForm = $config->getComponentByType(GridFieldDetailForm::class);
168
            $detailForm
169
                ->setValidator(Member_Validator::create())
170
                ->setItemEditFormCallback(function ($form) use ($group) {
171
                    /** @var Form $form */
172
                    $record = $form->getRecord();
173
                    $groupsField = $form->Fields()->dataFieldByName('DirectGroups');
174
                    if ($groupsField) {
0 ignored issues
show
introduced by
$groupsField is of type SilverStripe\Forms\FormField, thus it always evaluated to true.
Loading history...
175
                        // If new records are created in a group context,
176
                        // set this group by default.
177
                        if ($record && !$record->ID) {
178
                            $groupsField->setValue($group->ID);
179
                        } elseif ($record && $record->ID) {
180
                            // TODO Mark disabled once chosen.js supports it
181
                            // $groupsField->setDisabledItems(array($group->ID));
182
                            $form->Fields()->replaceField(
183
                                'DirectGroups',
184
                                $groupsField->performReadonlyTransformation()
185
                            );
186
                        }
187
                    }
188
                });
189
            $memberList = GridField::create('Members', false, $this->DirectMembers(), $config)
190
                ->addExtraClass('members_grid');
191
            // @todo Implement permission checking on GridField
192
            //$memberList->setPermissions(array('edit', 'delete', 'export', 'add', 'inlineadd'));
193
            $fields->addFieldToTab('Root.Members', $memberList);
194
        }
195
196
        // Only add a dropdown for HTML editor configurations if more than one is available.
197
        // Otherwise Member->getHtmlEditorConfigForCMS() will default to the 'cms' configuration.
198
        $editorConfigMap = HTMLEditorConfig::get_available_configs_map();
199
        if (count($editorConfigMap) > 1) {
200
            $fields->addFieldToTab(
201
                'Root.Permissions',
202
                new DropdownField(
203
                    'HtmlEditorConfig',
204
                    'HTML Editor Configuration',
205
                    $editorConfigMap
206
                ),
207
                'Permissions'
208
            );
209
        }
210
211
        if (!Permission::check('EDIT_PERMISSIONS')) {
212
            $fields->removeFieldFromTab('Root', 'Permissions');
213
        }
214
215
        // Only show the "Roles" tab if permissions are granted to edit them,
216
        // and at least one role exists
217
        if (Permission::check('APPLY_ROLES') &&
218
            PermissionRole::get()->count() &&
219
            class_exists(SecurityAdmin::class)
220
        ) {
221
            $fields->findOrMakeTab('Root.Roles', _t(__CLASS__ . '.ROLES', 'Roles'));
222
            $fields->addFieldToTab(
223
                'Root.Roles',
224
                new LiteralField(
225
                    "",
226
                    "<p>" .
227
                    _t(
228
                        __CLASS__ . '.ROLESDESCRIPTION',
229
                        "Roles are predefined sets of permissions, and can be assigned to groups.<br />"
230
                        . "They are inherited from parent groups if required."
231
                    ) . '<br />' .
232
                    sprintf(
233
                        '<a href="%s" class="add-role">%s</a>',
234
                        SecurityAdmin::singleton()->Link('show/root#Root_Roles'),
235
                        // TODO This should include #Root_Roles to switch directly to the tab,
236
                        // but tabstrip.js doesn't display tabs when directly adressed through a URL pragma
237
                        _t('SilverStripe\\Security\\Group.RolesAddEditLink', 'Manage roles')
238
                    ) .
239
                    "</p>"
240
                )
241
            );
242
243
            // Add roles (and disable all checkboxes for inherited roles)
244
            $allRoles = PermissionRole::get();
245
            if (!Permission::check('ADMIN')) {
246
                $allRoles = $allRoles->filter("OnlyAdminCanApply", 0);
247
            }
248
            if ($this->ID) {
249
                $groupRoles = $this->Roles();
250
                $inheritedRoles = new ArrayList();
251
                $ancestors = $this->getAncestors();
0 ignored issues
show
Bug introduced by
The method getAncestors() does not exist on SilverStripe\Security\Group. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

251
                /** @scrutinizer ignore-call */ 
252
                $ancestors = $this->getAncestors();
Loading history...
252
                foreach ($ancestors as $ancestor) {
253
                    $ancestorRoles = $ancestor->Roles();
254
                    if ($ancestorRoles) {
255
                        $inheritedRoles->merge($ancestorRoles);
256
                    }
257
                }
258
                $groupRoleIDs = $groupRoles->column('ID') + $inheritedRoles->column('ID');
259
                $inheritedRoleIDs = $inheritedRoles->column('ID');
260
            } else {
261
                $groupRoleIDs = array();
262
                $inheritedRoleIDs = array();
263
            }
264
265
            $rolesField = ListboxField::create('Roles', false, $allRoles->map()->toArray())
266
                    ->setDefaultItems($groupRoleIDs)
267
                    ->setAttribute('data-placeholder', _t('SilverStripe\\Security\\Group.AddRole', 'Add a role for this group'))
268
                    ->setDisabledItems($inheritedRoleIDs);
269
            if (!$allRoles->count()) {
270
                $rolesField->setAttribute('data-placeholder', _t('SilverStripe\\Security\\Group.NoRoles', 'No roles found'));
271
            }
272
            $fields->addFieldToTab('Root.Roles', $rolesField);
273
        }
274
275
        $fields->push($idField = new HiddenField("ID"));
276
277
        $this->extend('updateCMSFields', $fields);
278
279
        return $fields;
280
    }
281
282
    /**
283
     * @param bool $includerelations Indicate if the labels returned include relation fields
284
     * @return array
285
     * @skipUpgrade
286
     */
287
    public function fieldLabels($includerelations = true)
288
    {
289
        $labels = parent::fieldLabels($includerelations);
290
        $labels['Title'] = _t(__CLASS__ . '.GROUPNAME', 'Group name');
291
        $labels['Description'] = _t('SilverStripe\\Security\\Group.Description', 'Description');
292
        $labels['Code'] = _t('SilverStripe\\Security\\Group.Code', 'Group Code', 'Programmatical code identifying a group');
293
        $labels['Locked'] = _t('SilverStripe\\Security\\Group.Locked', 'Locked?', 'Group is locked in the security administration area');
294
        $labels['Sort'] = _t('SilverStripe\\Security\\Group.Sort', 'Sort Order');
295
        if ($includerelations) {
296
            $labels['Parent'] = _t('SilverStripe\\Security\\Group.Parent', 'Parent Group', 'One group has one parent group');
297
            $labels['Permissions'] = _t('SilverStripe\\Security\\Group.has_many_Permissions', 'Permissions', 'One group has many permissions');
298
            $labels['Members'] = _t('SilverStripe\\Security\\Group.many_many_Members', 'Members', 'One group has many members');
299
        }
300
301
        return $labels;
302
    }
303
304
    /**
305
     * Get many-many relation to {@link Member},
306
     * including all members which are "inherited" from children groups of this record.
307
     * See {@link DirectMembers()} for retrieving members without any inheritance.
308
     *
309
     * @param string $filter
310
     * @return ManyManyList
311
     */
312
    public function Members($filter = '')
313
    {
314
        // First get direct members as a base result
315
        $result = $this->DirectMembers();
316
317
        // Unsaved group cannot have child groups because its ID is still 0.
318
        if (!$this->exists()) {
319
            return $result;
320
        }
321
322
        // Remove the default foreign key filter in prep for re-applying a filter containing all children groups.
323
        // Filters are conjunctive in DataQuery by default, so this filter would otherwise overrule any less specific
324
        // ones.
325
        if (!($result instanceof UnsavedRelationList)) {
326
            $result = $result->alterDataQuery(function ($query) {
327
                /** @var DataQuery $query */
328
                $query->removeFilterOn('Group_Members');
329
            });
330
        }
331
332
        // Now set all children groups as a new foreign key
333
        $familyIDs = $this->collateFamilyIDs();
334
        $result = $result->forForeignID($familyIDs);
335
        
336
        return $result->where($filter);
0 ignored issues
show
Bug introduced by
The method where() does not exist on SilverStripe\ORM\Relation. Since it exists in all sub-types, consider adding an abstract or default implementation to SilverStripe\ORM\Relation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

336
        return $result->/** @scrutinizer ignore-call */ where($filter);
Loading history...
Bug introduced by
The method where() does not exist on SilverStripe\ORM\UnsavedRelationList. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

336
        return $result->/** @scrutinizer ignore-call */ where($filter);
Loading history...
337
    }
338
339
    /**
340
     * Return only the members directly added to this group
341
     */
342
    public function DirectMembers()
343
    {
344
        return $this->getManyManyComponents('Members');
345
    }
346
347
    /**
348
     * Return a set of this record's "family" of IDs - the IDs of
349
     * this record and all its descendants.
350
     *
351
     * @return array
352
     */
353
    public function collateFamilyIDs()
354
    {
355
        if (!$this->exists()) {
356
            throw new \InvalidArgumentException("Cannot call collateFamilyIDs on unsaved Group.");
357
        }
358
359
        $familyIDs = array();
360
        $chunkToAdd = array($this->ID);
361
362
        while ($chunkToAdd) {
363
            $familyIDs = array_merge($familyIDs, $chunkToAdd);
364
365
            // Get the children of *all* the groups identified in the previous chunk.
366
            // This minimises the number of SQL queries necessary
367
            $chunkToAdd = Group::get()->filter("ParentID", $chunkToAdd)->column('ID');
368
        }
369
370
        return $familyIDs;
371
    }
372
373
    /**
374
     * Returns an array of the IDs of this group and all its parents
375
     *
376
     * @return array
377
     */
378
    public function collateAncestorIDs()
379
    {
380
        $parent = $this;
381
        $items = [];
382
        while ($parent instanceof Group) {
383
            $items[] = $parent->ID;
384
            $parent = $parent->getParent();
0 ignored issues
show
Bug introduced by
The method getParent() does not exist on SilverStripe\Security\Group. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

384
            /** @scrutinizer ignore-call */ 
385
            $parent = $parent->getParent();
Loading history...
385
        }
386
        return $items;
387
    }
388
389
    /**
390
     * Check if the group is a child of the given group or any parent groups
391
     *
392
     * @param string|int|Group $group Group instance, Group Code or ID
393
     * @return bool Returns TRUE if the Group is a child of the given group, otherwise FALSE
394
     */
395
    public function inGroup($group)
396
    {
397
        return in_array($this->identifierToGroupID($group), $this->collateAncestorIDs());
398
    }
399
400
    /**
401
     * Check if the group is a child of the given groups or any parent groups
402
     *
403
     * @param (string|int|Group)[] $groups
404
     * @param bool $requireAll set to TRUE if must be in ALL groups, or FALSE if must be in ANY
405
     * @return bool Returns TRUE if the Group is a child of any of the given groups, otherwise FALSE
406
     */
407
    public function inGroups($groups, $requireAll = false)
408
    {
409
        $ancestorIDs = $this->collateAncestorIDs();
410
        $candidateIDs = [];
411
        foreach ($groups as $group) {
412
            $groupID = $this->identifierToGroupID($group);
413
            if ($groupID) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $groupID of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
414
                $candidateIDs[] = $groupID;
415
            } elseif ($requireAll) {
416
                return false;
417
            }
418
        }
419
        if (empty($candidateIDs)) {
420
            return false;
421
        }
422
        $matches = array_intersect($candidateIDs, $ancestorIDs);
423
        if ($requireAll) {
424
            return count($candidateIDs) === count($matches);
425
        }
426
        return !empty($matches);
427
    }
428
429
    /**
430
     * Turn a string|int|Group into a GroupID
431
     *
432
     * @param string|int|Group $groupID Group instance, Group Code or ID
433
     * @return int|null the Group ID or NULL if not found
434
     */
435
    protected function identifierToGroupID($groupID)
436
    {
437
        if (is_numeric($groupID) && Group::get()->byID($groupID)) {
0 ignored issues
show
Bug introduced by
It seems like $groupID can also be of type string; however, parameter $id of SilverStripe\ORM\DataList::byID() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

437
        if (is_numeric($groupID) && Group::get()->byID(/** @scrutinizer ignore-type */ $groupID)) {
Loading history...
438
            return $groupID;
439
        } elseif (is_string($groupID) && $groupByCode = Group::get()->filter(['Code' => $groupID])->first()) {
440
            return $groupByCode->ID;
441
        } elseif ($groupID instanceof Group && $groupID->exists()) {
442
            return $groupID->ID;
443
        }
444
        return null;
445
    }
446
447
    /**
448
     * This isn't a descendant of SiteTree, but needs this in case
449
     * the group is "reorganised";
450
     */
451
    public function cmsCleanup_parentChanged()
452
    {
453
    }
454
455
    /**
456
     * Override this so groups are ordered in the CMS
457
     */
458
    public function stageChildren()
459
    {
460
        return Group::get()
461
            ->filter("ParentID", $this->ID)
462
            ->exclude("ID", $this->ID)
463
            ->sort('"Sort"');
464
    }
465
466
    /**
467
     * @return string
468
     */
469
    public function getTreeTitle()
470
    {
471
        $title = htmlspecialchars($this->Title, ENT_QUOTES);
472
        $this->extend('updateTreeTitle', $title);
473
        return $title;
474
    }
475
476
    /**
477
     * Overloaded to ensure the code is always descent.
478
     *
479
     * @param string
480
     */
481
    public function setCode($val)
482
    {
483
        $this->setField("Code", Convert::raw2url($val));
484
    }
485
486
    public function validate()
487
    {
488
        $result = parent::validate();
489
490
        // Check if the new group hierarchy would add certain "privileged permissions",
491
        // and require an admin to perform this change in case it does.
492
        // This prevents "sub-admin" users with group editing permissions to increase their privileges.
493
        if ($this->Parent()->exists() && !Permission::check('ADMIN')) {
494
            $inheritedCodes = Permission::get()
495
                ->filter('GroupID', $this->Parent()->collateAncestorIDs())
496
                ->column('Code');
497
            $privilegedCodes = Permission::config()->get('privileged_permissions');
498
            if (array_intersect($inheritedCodes, $privilegedCodes)) {
499
                $result->addError(
500
                    _t(
501
                        'SilverStripe\\Security\\Group.HierarchyPermsError',
502
                        'Can\'t assign parent group "{group}" with privileged permissions (requires ADMIN access)',
503
                        ['group' => $this->Parent()->Title]
504
                    )
505
                );
506
            }
507
        }
508
509
        return $result;
510
    }
511
512
    public function onBeforeWrite()
513
    {
514
        parent::onBeforeWrite();
515
516
        // Only set code property when the group has a custom title, and no code exists.
517
        // The "Code" attribute is usually treated as a more permanent identifier than database IDs
518
        // in custom application logic, so can't be changed after its first set.
519
        if (!$this->Code && $this->Title != _t(__CLASS__ . '.NEWGROUP', "New Group")) {
520
            $this->setCode($this->Title);
521
        }
522
    }
523
524
    public function onBeforeDelete()
525
    {
526
        parent::onBeforeDelete();
527
528
        // if deleting this group, delete it's children as well
529
        foreach ($this->Groups() as $group) {
530
            $group->delete();
531
        }
532
533
        // Delete associated permissions
534
        foreach ($this->Permissions() as $permission) {
535
            $permission->delete();
536
        }
537
    }
538
539
    /**
540
     * Checks for permission-code CMS_ACCESS_SecurityAdmin.
541
     * If the group has ADMIN permissions, it requires the user to have ADMIN permissions as well.
542
     *
543
     * @param Member $member Member
544
     * @return boolean
545
     */
546
    public function canEdit($member = null)
547
    {
548
        if (!$member) {
549
            $member = Security::getCurrentUser();
550
        }
551
552
        // extended access checks
553
        $results = $this->extend('canEdit', $member);
554
        if ($results && is_array($results)) {
555
            if (!min($results)) {
556
                return false;
557
            }
558
        }
559
560
        if (// either we have an ADMIN
561
            (bool)Permission::checkMember($member, "ADMIN")
562
            || (
563
                // or a privileged CMS user and a group without ADMIN permissions.
564
                // without this check, a user would be able to add himself to an administrators group
565
                // with just access to the "Security" admin interface
566
                Permission::checkMember($member, "CMS_ACCESS_SecurityAdmin") &&
567
                !Permission::get()->filter(array('GroupID' => $this->ID, 'Code' => 'ADMIN'))->exists()
568
            )
569
        ) {
570
            return true;
571
        }
572
573
        return false;
574
    }
575
576
    /**
577
     * Checks for permission-code CMS_ACCESS_SecurityAdmin.
578
     *
579
     * @param Member $member
580
     * @return boolean
581
     */
582
    public function canView($member = null)
583
    {
584
        if (!$member) {
585
            $member = Security::getCurrentUser();
586
        }
587
588
        // extended access checks
589
        $results = $this->extend('canView', $member);
590
        if ($results && is_array($results)) {
591
            if (!min($results)) {
592
                return false;
593
            }
594
        }
595
596
        // user needs access to CMS_ACCESS_SecurityAdmin
597
        if (Permission::checkMember($member, "CMS_ACCESS_SecurityAdmin")) {
598
            return true;
599
        }
600
601
        return false;
602
    }
603
604
    public function canDelete($member = null)
605
    {
606
        if (!$member) {
607
            $member = Security::getCurrentUser();
608
        }
609
610
        // extended access checks
611
        $results = $this->extend('canDelete', $member);
612
        if ($results && is_array($results)) {
613
            if (!min($results)) {
614
                return false;
615
            }
616
        }
617
618
        return $this->canEdit($member);
619
    }
620
621
    /**
622
     * Returns all of the children for the CMS Tree.
623
     * Filters to only those groups that the current user can edit
624
     *
625
     * @return ArrayList
626
     */
627
    public function AllChildrenIncludingDeleted()
628
    {
629
        $children = parent::AllChildrenIncludingDeleted();
0 ignored issues
show
introduced by
The method AllChildrenIncludingDeleted() does not exist on SilverStripe\ORM\DataObject. Maybe you want to declare this class abstract? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

629
        /** @scrutinizer ignore-call */ 
630
        $children = parent::AllChildrenIncludingDeleted();
Loading history...
630
631
        $filteredChildren = new ArrayList();
632
633
        if ($children) {
634
            foreach ($children as $child) {
635
                /** @var DataObject $child */
636
                if ($child->canView()) {
637
                    $filteredChildren->push($child);
638
                }
639
            }
640
        }
641
642
        return $filteredChildren;
643
    }
644
645
    /**
646
     * Add default records to database.
647
     *
648
     * This function is called whenever the database is built, after the
649
     * database tables have all been created.
650
     */
651
    public function requireDefaultRecords()
652
    {
653
        parent::requireDefaultRecords();
654
655
        // Add default author group if no other group exists
656
        $allGroups = Group::get();
657
        if (!$allGroups->count()) {
658
            $authorGroup = new Group();
659
            $authorGroup->Code = 'content-authors';
660
            $authorGroup->Title = _t(__CLASS__ . '.DefaultGroupTitleContentAuthors', 'Content Authors');
661
            $authorGroup->Sort = 1;
662
            $authorGroup->write();
663
            Permission::grant($authorGroup->ID, 'CMS_ACCESS_CMSMain');
664
            Permission::grant($authorGroup->ID, 'CMS_ACCESS_AssetAdmin');
665
            Permission::grant($authorGroup->ID, 'CMS_ACCESS_ReportAdmin');
666
            Permission::grant($authorGroup->ID, 'SITETREE_REORGANISE');
667
        }
668
669
        // Add default admin group if none with permission code ADMIN exists
670
        $adminGroups = Permission::get_groups_by_permission('ADMIN');
671
        if (!$adminGroups->count()) {
672
            $adminGroup = new Group();
673
            $adminGroup->Code = 'administrators';
674
            $adminGroup->Title = _t(__CLASS__ . '.DefaultGroupTitleAdministrators', 'Administrators');
675
            $adminGroup->Sort = 0;
676
            $adminGroup->write();
677
            Permission::grant($adminGroup->ID, 'ADMIN');
678
        }
679
680
        // Members are populated through Member->requireDefaultRecords()
681
    }
682
}
683