Completed
Push — 4.3 ( d63e4b...4cfce3 )
by Maxime
39s queued 22s
created

Group::populateDefaults()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 0
dl 0
loc 6
rs 10
c 0
b 0
f 0
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
    /**
101
     * Caution: Only call on instances, not through a singleton.
102
     * The "root group" fields will be created through {@link SecurityAdmin->EditForm()}.
103
     *
104
     * @skipUpgrade
105
     * @return FieldList
106
     */
107
    public function getCMSFields()
108
    {
109
        $fields = new FieldList(
110
            new TabSet(
111
                "Root",
112
                new Tab(
113
                    'Members',
114
                    _t(__CLASS__ . '.MEMBERS', 'Members'),
115
                    new TextField("Title", $this->fieldLabel('Title')),
116
                    $parentidfield = DropdownField::create(
117
                        'ParentID',
118
                        $this->fieldLabel('Parent'),
119
                        Group::get()->exclude('ID', $this->ID)->map('ID', 'Breadcrumbs')
120
                    )->setEmptyString(' '),
121
                    new TextareaField('Description', $this->fieldLabel('Description'))
122
                ),
123
                $permissionsTab = new Tab(
124
                    'Permissions',
125
                    _t(__CLASS__ . '.PERMISSIONS', 'Permissions'),
126
                    $permissionsField = new PermissionCheckboxSetField(
127
                        'Permissions',
128
                        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

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

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

326
        return $result->/** @scrutinizer ignore-call */ where($filter);
Loading history...
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

326
        return $result->/** @scrutinizer ignore-call */ where($filter);
Loading history...
327
    }
328
329
    /**
330
     * Return only the members directly added to this group
331
     */
332
    public function DirectMembers()
333
    {
334
        return $this->getManyManyComponents('Members');
335
    }
336
337
    /**
338
     * Return a set of this record's "family" of IDs - the IDs of
339
     * this record and all its descendants.
340
     *
341
     * @return array
342
     */
343
    public function collateFamilyIDs()
344
    {
345
        if (!$this->exists()) {
346
            throw new \InvalidArgumentException("Cannot call collateFamilyIDs on unsaved Group.");
347
        }
348
349
        $familyIDs = array();
350
        $chunkToAdd = array($this->ID);
351
352
        while ($chunkToAdd) {
353
            $familyIDs = array_merge($familyIDs, $chunkToAdd);
354
355
            // Get the children of *all* the groups identified in the previous chunk.
356
            // This minimises the number of SQL queries necessary
357
            $chunkToAdd = Group::get()->filter("ParentID", $chunkToAdd)->column('ID');
358
        }
359
360
        return $familyIDs;
361
    }
362
363
    /**
364
     * Returns an array of the IDs of this group and all its parents
365
     *
366
     * @return array
367
     */
368
    public function collateAncestorIDs()
369
    {
370
        $parent = $this;
371
        $items = [];
372
        while ($parent instanceof Group) {
373
            $items[] = $parent->ID;
374
            $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

374
            /** @scrutinizer ignore-call */ 
375
            $parent = $parent->getParent();
Loading history...
375
        }
376
        return $items;
377
    }
378
379
    /**
380
     * Check if the group is a child of the given group or any parent groups
381
     *
382
     * @param string|int|Group $group Group instance, Group Code or ID
383
     * @return bool Returns TRUE if the Group is a child of the given group, otherwise FALSE
384
     */
385
    public function inGroup($group)
386
    {
387
        return in_array($this->identifierToGroupID($group), $this->collateAncestorIDs());
388
    }
389
390
    /**
391
     * Check if the group is a child of the given groups or any parent groups
392
     *
393
     * @param (string|int|Group)[] $groups
394
     * @param bool $requireAll set to TRUE if must be in ALL groups, or FALSE if must be in ANY
395
     * @return bool Returns TRUE if the Group is a child of any of the given groups, otherwise FALSE
396
     */
397
    public function inGroups($groups, $requireAll = false)
398
    {
399
        $ancestorIDs = $this->collateAncestorIDs();
400
        $candidateIDs = [];
401
        foreach ($groups as $group) {
402
            $groupID = $this->identifierToGroupID($group);
403
            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...
404
                $candidateIDs[] = $groupID;
405
            } elseif ($requireAll) {
406
                return false;
407
            }
408
        }
409
        if (empty($candidateIDs)) {
410
            return false;
411
        }
412
        $matches = array_intersect($candidateIDs, $ancestorIDs);
413
        if ($requireAll) {
414
            return count($candidateIDs) === count($matches);
415
        }
416
        return !empty($matches);
417
    }
418
419
    /**
420
     * Turn a string|int|Group into a GroupID
421
     *
422
     * @param string|int|Group $groupID Group instance, Group Code or ID
423
     * @return int|null the Group ID or NULL if not found
424
     */
425
    protected function identifierToGroupID($groupID)
426
    {
427
        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

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

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