Permission   F
last analyzed

Complexity

Total Complexity 92

Size/Duplication

Total Lines 716
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 92
eloc 287
dl 0
loc 716
rs 2
c 0
b 0
f 0

17 Methods

Rating   Name   Duplication   Size   Complexity  
A reset() 0 3 1
A deny() 0 27 4
A onBeforeWrite() 0 6 1
A get_label_for_permission() 0 7 2
A get_members_by_permission() 0 27 5
A provideI18nEntities() 0 10 1
F checkMember() 0 121 27
A grant() 0 27 4
A permissions_for_member() 0 31 2
A traverse_declared_permissions() 0 12 4
A get_declared_permissions_list() 0 15 3
B groupList() 0 35 9
D get_codes() 0 99 20
A get_groups_by_permission() 0 16 2
A get_template_global_variables() 0 4 1
A check() 0 10 3
A sort_permissions() 0 8 3

How to fix   Complexity   

Complex Class

Complex classes like Permission often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Permission, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace SilverStripe\Security;
4
5
use SilverStripe\Core\ClassInfo;
6
use SilverStripe\Core\Resettable;
7
use SilverStripe\Dev\TestOnly;
8
use SilverStripe\i18n\i18nEntityProvider;
9
use SilverStripe\ORM\ArrayList;
10
use SilverStripe\ORM\DataObject;
11
use SilverStripe\ORM\DB;
12
use SilverStripe\ORM\SS_List;
13
use SilverStripe\View\TemplateGlobalProvider;
14
15
/**
16
 * Represents a permission assigned to a group.
17
 *
18
 * @property string Code
19
 * @property int Arg
20
 * @property int Type
21
 * @property int GroupID
22
 * @method Group Group()
23
 */
24
class Permission extends DataObject implements TemplateGlobalProvider, Resettable, i18nEntityProvider
25
{
26
27
    // the (1) after Type specifies the DB default value which is needed for
28
    // upgrades from older SilverStripe versions
29
    private static $db = array(
0 ignored issues
show
introduced by
The private property $db is not used, and could be removed.
Loading history...
30
        "Code" => "Varchar(255)",
31
        "Arg" => "Int",
32
        "Type" => "Int(1)"
33
    );
34
35
    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...
36
        "Group" => Group::class,
37
    );
38
39
    private static $indexes = array(
0 ignored issues
show
introduced by
The private property $indexes is not used, and could be removed.
Loading history...
40
        "Code" => true
41
    );
42
43
    private static $defaults = array(
0 ignored issues
show
introduced by
The private property $defaults is not used, and could be removed.
Loading history...
44
        "Type" => 1
45
    );
46
47
    private static $table_name = "Permission";
0 ignored issues
show
introduced by
The private property $table_name is not used, and could be removed.
Loading history...
48
49
    /**
50
     * This is the value to use for the "Type" field if a permission should be
51
     * granted.
52
     */
53
    const GRANT_PERMISSION = 1;
54
55
    /**
56
     * This is the value to use for the "Type" field if a permission should be
57
     * denied.
58
     */
59
    const DENY_PERMISSION = -1;
60
61
    /**
62
     * This is the value to use for the "Type" field if a permission should be
63
     * inherited.
64
     */
65
    const INHERIT_PERMISSION = 0;
66
67
68
    /**
69
     * Method to globally disable "strict" checking, which means a permission
70
     * will be granted if the key does not exist at all.
71
     *
72
     * @deprecated 4.4.0
73
     * @var array
74
     */
75
    private static $declared_permissions = null;
76
77
    /**
78
     * Linear list of declared permissions in the system.
79
     *
80
     * @deprecated 4.4.0
81
     * @var array
82
     */
83
    private static $declared_permissions_list = null;
84
85
    /**
86
     * @config
87
     * @var $strict_checking Boolean Method to globally disable "strict" checking,
0 ignored issues
show
Documentation Bug introduced by
The doc comment $strict_checking at position 0 could not be parsed: Unknown type name '$strict_checking' at position 0 in $strict_checking.
Loading history...
88
     * which means a permission will be granted if the key does not exist at all.
89
     */
90
    private static $strict_checking = true;
91
92
    /**
93
     * Set to false to prevent the 'ADMIN' permission from implying all
94
     * permissions in the system
95
     *
96
     * @config
97
     * @var bool
98
     */
99
    private static $admin_implies_all = true;
100
101
    /**
102
     * a list of permission codes which doesn't appear in the Permission list
103
     * when make the {@link PermissionCheckboxSetField}
104
     * @config
105
     * @var array;
106
     */
107
    private static $hidden_permissions = array();
0 ignored issues
show
introduced by
The private property $hidden_permissions is not used, and could be removed.
Loading history...
108
109
    /**
110
     * @config These permissions can only be applied by ADMIN users, to prevent
111
     * privilege escalation on group assignments and inheritance.
112
     * @var array
113
     */
114
    private static $privileged_permissions = array(
0 ignored issues
show
introduced by
The private property $privileged_permissions is not used, and could be removed.
Loading history...
115
        'ADMIN',
116
        'APPLY_ROLES',
117
        'EDIT_PERMISSIONS'
118
    );
119
120
    /**
121
     * Check that the current member has the given permission.
122
     *
123
     * @param string|array $code Code of the permission to check (case-sensitive)
124
     * @param string $arg Optional argument (e.g. a permissions for a specific page)
125
     * @param int|Member $member Optional member instance or ID. If set to NULL, the permssion
126
     *  will be checked for the current user
127
     * @param bool $strict Use "strict" checking (which means a permission
128
     *  will be granted if the key does not exist at all)?
129
     * @return int|bool The ID of the permission record if the permission
130
     *  exists; FALSE otherwise. If "strict" checking is
131
     *  disabled, TRUE will be returned if the permission does not exist at all.
132
     */
133
    public static function check($code, $arg = "any", $member = null, $strict = true)
134
    {
135
        if (!$member) {
136
            if (!Security::getCurrentUser()) {
137
                return false;
138
            }
139
            $member = Security::getCurrentUser();
140
        }
141
142
        return self::checkMember($member, $code, $arg, $strict);
143
    }
144
145
    /**
146
     * Permissions cache.  The format is a map, where the keys are member IDs, and the values are
147
     * arrays of permission codes.
148
     */
149
    private static $cache_permissions = array();
150
151
    /**
152
     * Flush the permission cache, for example if you have edited group membership or a permission record.
153
     * @todo Call this whenever Group_Members is added to or removed from
154
     */
155
    public static function reset()
156
    {
157
        self::$cache_permissions = array();
158
    }
159
160
    /**
161
     * Check that the given member has the given permission.
162
     *
163
     * @param int|Member memberID The ID of the member to check. Leave blank for the current member.
0 ignored issues
show
Bug introduced by
The type SilverStripe\Security\memberID 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...
164
     *  Alternatively you can use a member object.
165
     * @param string|array $code Code of the permission to check (case-sensitive)
166
     * @param string $arg Optional argument (e.g. a permissions for a specific page)
167
     * @param bool $strict Use "strict" checking (which means a permission
168
     *  will be granted if the key does not exist at all)?
169
     * @return int|bool The ID of the permission record if the permission
170
     *  exists; FALSE otherwise. If "strict" checking is
171
     *  disabled, TRUE will be returned if the permission does not exist at all.
172
     */
173
    public static function checkMember($member, $code, $arg = "any", $strict = true)
174
    {
175
        if (!$member) {
176
            $member = Security::getCurrentUser();
177
        }
178
        $memberID = ($member instanceof Member) ? $member->ID : $member;
179
180
        if (!$memberID) {
181
            return false;
182
        }
183
184
        // Turn the code into an array as we may need to add other permsissions to the set we check
185
        if (!is_array($code)) {
186
            $code = array($code);
187
        }
188
189
        // Check if admin should be treated as holding all permissions
190
        $adminImpliesAll = (bool)static::config()->admin_implies_all;
0 ignored issues
show
Bug Best Practice introduced by
The property admin_implies_all does not exist on SilverStripe\Core\Config\Config_ForClass. Since you implemented __get, consider adding a @property annotation.
Loading history...
191
192
        if ($arg == 'any') {
193
            // Cache the permissions in memory
194
            if (!isset(self::$cache_permissions[$memberID])) {
195
                self::$cache_permissions[$memberID] = self::permissions_for_member($memberID);
196
            }
197
            foreach ($code as $permCode) {
198
                if ($permCode === 'CMS_ACCESS') {
199
                    foreach (self::$cache_permissions[$memberID] as $perm) {
200
                        //if they have admin rights OR they have an explicit access to the CMS then give permission
201
                        if (($adminImpliesAll && $perm == 'ADMIN') || substr($perm, 0, 11) === 'CMS_ACCESS_') {
202
                            return true;
203
                        }
204
                    }
205
                } elseif (substr($permCode, 0, 11) === 'CMS_ACCESS_' && !in_array('CMS_ACCESS_LeftAndMain', $code)) {
206
                    //cms_access_leftandmain means access to all CMS areas
207
                    $code[] = 'CMS_ACCESS_LeftAndMain';
208
                }
209
            }
210
211
            // if ADMIN has all privileges, then we need to push that code in
212
            if ($adminImpliesAll) {
213
                $code[] = "ADMIN";
214
            }
215
216
            // Multiple $code values - return true if at least one matches, ie, intersection exists
217
            return (bool)array_intersect($code, self::$cache_permissions[$memberID]);
218
        }
219
220
        // Code filters
221
        $codeParams = is_array($code) ? $code : array($code);
0 ignored issues
show
introduced by
The condition is_array($code) is always true.
Loading history...
222
        $codeClause = DB::placeholders($codeParams);
223
        $adminParams = $adminImpliesAll ? array('ADMIN') : array();
224
        $adminClause = $adminImpliesAll ?  ", ?" : '';
225
226
        // The following code should only be used if you're not using the "any" arg.  This is kind
227
        // of obselete functionality and could possibly be deprecated.
228
        $groupParams = self::groupList($memberID);
229
        if (empty($groupParams)) {
230
            return false;
231
        }
232
        $groupClause = DB::placeholders($groupParams);
233
234
        // Arg component
235
        $argClause = "";
236
        $argParams = array();
237
        switch ($arg) {
238
            case "any":
239
                break;
240
            case "all":
241
                $argClause = " AND \"Arg\" = ?";
242
                $argParams = array(-1);
243
                break;
244
            default:
245
                if (is_numeric($arg)) {
246
                    $argClause = "AND \"Arg\" IN (?, ?) ";
247
                    $argParams = array(-1, $arg);
248
                } else {
249
                    user_error("Permission::checkMember: bad arg '$arg'", E_USER_ERROR);
250
                }
251
        }
252
253
        // Raw SQL for efficiency
254
        $permission = DB::prepared_query(
255
            "SELECT \"ID\"
256
			FROM \"Permission\"
257
			WHERE (
258
				\"Code\" IN ($codeClause $adminClause)
259
				AND \"Type\" = ?
260
				AND \"GroupID\" IN ($groupClause)
261
				$argClause
262
			)",
263
            array_merge(
264
                $codeParams,
265
                $adminParams,
266
                array(self::GRANT_PERMISSION),
267
                $groupParams,
268
                $argParams
269
            )
270
        )->value();
271
272
        if ($permission) {
273
            return $permission;
274
        }
275
276
        // Strict checking disabled?
277
        if (!static::config()->strict_checking || !$strict) {
0 ignored issues
show
Bug Best Practice introduced by
The property strict_checking does not exist on SilverStripe\Core\Config\Config_ForClass. Since you implemented __get, consider adding a @property annotation.
Loading history...
278
            $hasPermission = DB::prepared_query(
279
                "SELECT COUNT(*)
280
				FROM \"Permission\"
281
				WHERE (
282
					\"Code\" IN ($codeClause) AND
283
					\"Type\" = ?
284
				)",
285
                array_merge($codeParams, array(self::GRANT_PERMISSION))
286
            )->value();
287
288
            if (!$hasPermission) {
289
                return false;
290
            }
291
        }
292
293
        return false;
294
    }
295
296
    /**
297
     * Get all the 'any' permission codes available to the given member.
298
     *
299
     * @param int $memberID
300
     * @return array
301
     */
302
    public static function permissions_for_member($memberID)
303
    {
304
        $groupList = self::groupList($memberID);
305
306
        if ($groupList) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $groupList of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
307
            $groupCSV = implode(", ", $groupList);
308
309
            $allowed = array_unique(DB::query("
310
				SELECT \"Code\"
311
				FROM \"Permission\"
312
				WHERE \"Type\" = " . self::GRANT_PERMISSION . " AND \"GroupID\" IN ($groupCSV)
313
314
				UNION
315
316
				SELECT \"Code\"
317
				FROM \"PermissionRoleCode\" PRC
318
				INNER JOIN \"PermissionRole\" PR ON PRC.\"RoleID\" = PR.\"ID\"
319
				INNER JOIN \"Group_Roles\" GR ON GR.\"PermissionRoleID\" = PR.\"ID\"
320
				WHERE \"GroupID\" IN ($groupCSV)
321
			")->column());
322
323
            $denied = array_unique(DB::query("
324
				SELECT \"Code\"
325
				FROM \"Permission\"
326
				WHERE \"Type\" = " . self::DENY_PERMISSION . " AND \"GroupID\" IN ($groupCSV)
327
			")->column());
328
329
            return array_diff($allowed, $denied);
330
        }
331
332
        return array();
333
    }
334
335
336
    /**
337
     * Get the list of groups that the given member belongs to.
338
     *
339
     * Call without an argument to get the groups that the current member
340
     * belongs to. In this case, the results will be session-cached.
341
     *
342
     * @param int $memberID The ID of the member. Leave blank for the current
343
     *                      member.
344
     * @return array Returns a list of group IDs to which the member belongs
345
     *               to or NULL.
346
     */
347
    public static function groupList($memberID = null)
348
    {
349
        // Default to current member, with session-caching
350
        if (!$memberID) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $memberID of type integer|null is loosely compared to false; 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...
351
            $member = Security::getCurrentUser();
352
            if ($member && isset($_SESSION['Permission_groupList'][$member->ID])) {
353
                return $_SESSION['Permission_groupList'][$member->ID];
354
            }
355
        } else {
356
            $member = DataObject::get_by_id("SilverStripe\\Security\\Member", $memberID);
357
        }
358
359
        if ($member) {
360
            // Build a list of the IDs of the groups.  Most of the heavy lifting
361
            // is done by Member::Groups
362
            // NOTE: This isn't effecient; but it's called once per session so
363
            // it's a low priority to fix.
364
            $groups = $member->Groups();
0 ignored issues
show
Bug introduced by
The method Groups() does not exist on SilverStripe\ORM\DataObject. 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

364
            /** @scrutinizer ignore-call */ 
365
            $groups = $member->Groups();
Loading history...
365
            $groupList = array();
366
367
            if ($groups) {
368
                foreach ($groups as $group) {
369
                    $groupList[] = $group->ID;
370
                }
371
            }
372
373
374
            // Session caching
375
            if (!$memberID) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $memberID of type integer|null is loosely compared to false; 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...
376
                $_SESSION['Permission_groupList'][$member->ID] = $groupList;
377
            }
378
379
            return isset($groupList) ? $groupList : null;
380
        }
381
        return null;
382
    }
383
384
385
    /**
386
     * Grant the given permission code/arg to the given group
387
     *
388
     * @param int $groupID The ID of the group
389
     * @param string $code The permission code
390
     * @param string $arg Optional: The permission argument (e.g. a page ID).
391
     * @returns Permission Returns the new permission object.
392
     */
393
    public static function grant($groupID, $code, $arg = "any")
394
    {
395
        $perm = new Permission();
396
        $perm->GroupID = $groupID;
397
        $perm->Code = $code;
398
        $perm->Type = self::GRANT_PERMISSION;
399
400
        // Arg component
401
        switch ($arg) {
402
            case "any":
403
                break;
404
            case "all":
405
                $perm->Arg = -1;
406
                break;
407
            default:
408
                if (is_numeric($arg)) {
409
                    $perm->Arg = $arg;
0 ignored issues
show
Documentation Bug introduced by
The property $Arg was declared of type integer, but $arg is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
410
                } else {
411
                    user_error(
412
                        "Permission::checkMember: bad arg '$arg'",
413
                        E_USER_ERROR
414
                    );
415
                }
416
        }
417
418
        $perm->write();
419
        return $perm;
420
    }
421
422
423
    /**
424
     * Deny the given permission code/arg to the given group
425
     *
426
     * @param int $groupID The ID of the group
427
     * @param string $code The permission code
428
     * @param string $arg Optional: The permission argument (e.g. a page ID).
429
     * @returns Permission Returns the new permission object.
430
     */
431
    public static function deny($groupID, $code, $arg = "any")
432
    {
433
        $perm = new Permission();
434
        $perm->GroupID = $groupID;
435
        $perm->Code = $code;
436
        $perm->Type = self::DENY_PERMISSION;
437
438
        // Arg component
439
        switch ($arg) {
440
            case "any":
441
                break;
442
            case "all":
443
                $perm->Arg = -1;
444
                break;
445
            default:
446
                if (is_numeric($arg)) {
447
                    $perm->Arg = $arg;
0 ignored issues
show
Documentation Bug introduced by
The property $Arg was declared of type integer, but $arg is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
448
                } else {
449
                    user_error(
450
                        "Permission::checkMember: bad arg '$arg'",
451
                        E_USER_ERROR
452
                    );
453
                }
454
        }
455
456
        $perm->write();
457
        return $perm;
458
    }
459
460
    /**
461
     * Returns all members for a specific permission.
462
     *
463
     * @param string|array $code Either a single permission code, or a list of permission codes
464
     * @return SS_List Returns a set of member that have the specified
465
     *                       permission.
466
     */
467
    public static function get_members_by_permission($code)
468
    {
469
        $toplevelGroups = self::get_groups_by_permission($code);
470
        if (!$toplevelGroups) {
0 ignored issues
show
introduced by
$toplevelGroups is of type SilverStripe\ORM\DataList, thus it always evaluated to true.
Loading history...
471
            return new ArrayList();
472
        }
473
474
        $groupIDs = array();
475
        foreach ($toplevelGroups as $group) {
476
            $familyIDs = $group->collateFamilyIDs();
0 ignored issues
show
Bug introduced by
The method collateFamilyIDs() does not exist on SilverStripe\ORM\DataObject. 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

476
            /** @scrutinizer ignore-call */ 
477
            $familyIDs = $group->collateFamilyIDs();
Loading history...
477
            if (is_array($familyIDs)) {
478
                $groupIDs = array_merge($groupIDs, array_values($familyIDs));
479
            }
480
        }
481
482
        if (empty($groupIDs)) {
483
            return new ArrayList();
484
        }
485
486
        $groupClause = DB::placeholders($groupIDs);
487
        /** @skipUpgrade */
488
        $members = Member::get()
489
            ->where(array("\"Group\".\"ID\" IN ($groupClause)" => $groupIDs))
490
            ->leftJoin("Group_Members", '"Member"."ID" = "Group_Members"."MemberID"')
491
            ->leftJoin("Group", '"Group_Members"."GroupID" = "Group"."ID"');
492
493
        return $members;
494
    }
495
496
    /**
497
     * Return all of the groups that have one of the given permission codes
498
     * @param array|string $codes Either a single permission code, or an array of permission codes
499
     * @return SS_List The matching group objects
500
     */
501
    public static function get_groups_by_permission($codes)
502
    {
503
        $codeParams = is_array($codes) ? $codes : array($codes);
504
        $codeClause = DB::placeholders($codeParams);
505
506
        // Via Roles are groups that have the permission via a role
507
        /** @skipUpgrade */
508
        return Group::get()
509
            ->where(array(
510
                "\"PermissionRoleCode\".\"Code\" IN ($codeClause) OR \"Permission\".\"Code\" IN ($codeClause)"
511
                => array_merge($codeParams, $codeParams)
512
            ))
513
            ->leftJoin('Permission', "\"Permission\".\"GroupID\" = \"Group\".\"ID\"")
514
            ->leftJoin('Group_Roles', "\"Group_Roles\".\"GroupID\" = \"Group\".\"ID\"")
515
            ->leftJoin('PermissionRole', "\"Group_Roles\".\"PermissionRoleID\" = \"PermissionRole\".\"ID\"")
516
            ->leftJoin('PermissionRoleCode', "\"PermissionRoleCode\".\"RoleID\" = \"PermissionRole\".\"ID\"");
517
    }
518
519
520
    /**
521
     * Get a list of all available permission codes, both defined through the
522
     * {@link PermissionProvider} interface, and all not explicitly defined codes existing
523
     * as a {@link Permission} database record. By default, the results are
524
     * grouped as denoted by {@link Permission_Group}.
525
     *
526
     * @param bool $grouped Group results into an array of permission groups.
527
     * @return array Returns an array of all available permission codes. The
528
     *  array indicies are the permission codes as used in
529
     *  {@link Permission::check()}. The value is a description
530
     *  suitable for using in an interface.
531
     */
532
    public static function get_codes($grouped = true)
533
    {
534
        $classes = ClassInfo::implementorsOf('SilverStripe\\Security\\PermissionProvider');
535
536
        $allCodes = array();
537
        $adminCategory = _t(__CLASS__ . '.AdminGroup', 'Administrator');
538
        $allCodes[$adminCategory]['ADMIN'] = array(
539
            'name' => _t(__CLASS__ . '.FULLADMINRIGHTS', 'Full administrative rights'),
540
            'help' => _t(
541
                'SilverStripe\\Security\\Permission.FULLADMINRIGHTS_HELP',
542
                'Implies and overrules all other assigned permissions.'
543
            ),
544
            'sort' => 100000
545
        );
546
547
        if ($classes) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $classes of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
548
            foreach ($classes as $class) {
549
                $SNG = singleton($class);
550
                if ($SNG instanceof TestOnly) {
551
                    continue;
552
                }
553
554
                $someCodes = $SNG->providePermissions();
555
                if ($someCodes) {
556
                    foreach ($someCodes as $k => $v) {
557
                        if (is_array($v)) {
558
                            // There must be a category and name key.
559
                            if (!isset($v['category'])) {
560
                                user_error(
561
                                    "The permission $k must have a category key",
562
                                    E_USER_WARNING
563
                                );
564
                            }
565
                            if (!isset($v['name'])) {
566
                                user_error(
567
                                    "The permission $k must have a name key",
568
                                    E_USER_WARNING
569
                                );
570
                            }
571
572
                            if (!isset($allCodes[$v['category']])) {
573
                                $allCodes[$v['category']] = array();
574
                            }
575
576
                            $allCodes[$v['category']][$k] = array(
577
                            'name' => $v['name'],
578
                            'help' => isset($v['help']) ? $v['help'] : null,
579
                            'sort' => isset($v['sort']) ? $v['sort'] : 0
580
                            );
581
                        } else {
582
                            $allCodes['Other'][$k] = array(
583
                            'name' => $v,
584
                            'help' => null,
585
                            'sort' => 0
586
                            );
587
                        }
588
                    }
589
                }
590
            }
591
        }
592
593
        $flatCodeArray = array();
594
        foreach ($allCodes as $category) {
595
            foreach ($category as $code => $permission) {
596
                $flatCodeArray[] = $code;
597
            }
598
        }
599
        $otherPerms = DB::query("SELECT DISTINCT \"Code\" From \"Permission\" WHERE \"Code\" != ''")->column();
600
601
        if ($otherPerms) {
602
            foreach ($otherPerms as $otherPerm) {
603
                if (!in_array($otherPerm, $flatCodeArray)) {
604
                    $allCodes['Other'][$otherPerm] = array(
605
                    'name' => $otherPerm,
606
                    'help' => null,
607
                    'sort' => 0
608
                    );
609
                }
610
            }
611
        }
612
613
        // Don't let people hijack ADMIN rights
614
        if (!Permission::check("ADMIN")) {
615
            unset($allCodes['ADMIN']);
616
        }
617
618
        ksort($allCodes);
619
620
        $returnCodes = array();
621
        foreach ($allCodes as $category => $permissions) {
622
            if ($grouped) {
623
                uasort($permissions, array(__CLASS__, 'sort_permissions'));
624
                $returnCodes[$category] = $permissions;
625
            } else {
626
                $returnCodes = array_merge($returnCodes, $permissions);
627
            }
628
        }
629
630
        return $returnCodes;
631
    }
632
633
    /**
634
     * Sort permissions based on their sort value, or name
635
     *
636
     * @param array $a
637
     * @param array $b
638
     * @return int
639
     */
640
    public static function sort_permissions($a, $b)
641
    {
642
        if ($a['sort'] == $b['sort']) {
643
            // Same sort value, do alpha instead
644
            return strcmp($a['name'], $b['name']);
645
        } else {
646
            // Just numeric.
647
            return $a['sort'] < $b['sort'] ? -1 : 1;
648
        }
649
    }
650
651
    /**
652
     * Get a linear list of the permissions in the system.
653
     *
654
     * @return array Linear list of declared permissions in the system.
655
     * @deprecated 4.4.0
656
     */
657
    public static function get_declared_permissions_list()
658
    {
659
        if (!self::$declared_permissions) {
0 ignored issues
show
Bug Best Practice introduced by
The expression self::declared_permissions of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
660
            return null;
661
        }
662
663
        if (self::$declared_permissions_list) {
0 ignored issues
show
Bug Best Practice introduced by
The expression self::declared_permissions_list of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
664
            return self::$declared_permissions_list;
665
        }
666
667
        self::$declared_permissions_list = array();
668
669
        self::traverse_declared_permissions(self::$declared_permissions, self::$declared_permissions_list);
0 ignored issues
show
Deprecated Code introduced by
The function SilverStripe\Security\Pe..._declared_permissions() has been deprecated: 4.4.0 ( Ignorable by Annotation )

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

669
        /** @scrutinizer ignore-deprecated */ self::traverse_declared_permissions(self::$declared_permissions, self::$declared_permissions_list);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
670
671
        return self::$declared_permissions_list;
672
    }
673
674
    /**
675
     * Look up the human-readable title for the permission as defined by <code>Permission::declare_permissions</code>
676
     *
677
     * @param string $perm Permission code
678
     * @return string Label for the given permission, or the permission itself if the label doesn't exist
679
     * @deprecated 4.4.0
680
     */
681
    public static function get_label_for_permission($perm)
682
    {
683
        $list = self::get_declared_permissions_list();
0 ignored issues
show
Deprecated Code introduced by
The function SilverStripe\Security\Pe...ared_permissions_list() has been deprecated: 4.4.0 ( Ignorable by Annotation )

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

683
        $list = /** @scrutinizer ignore-deprecated */ self::get_declared_permissions_list();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
684
        if (array_key_exists($perm, $list)) {
685
            return $list[$perm];
686
        }
687
        return $perm;
688
    }
689
690
    /**
691
     * Recursively traverse the nested list of declared permissions and create
692
     * a linear list.
693
     *
694
     * @param array $declared Nested structure of permissions.
695
     * @param array $list List of permissions in the structure. The result will be
696
     *              written to this array.
697
     * @deprecated 4.4.0
698
     */
699
    protected static function traverse_declared_permissions($declared, &$list)
700
    {
701
        if (!is_array($declared)) {
0 ignored issues
show
introduced by
The condition is_array($declared) is always true.
Loading history...
702
            return;
703
        }
704
705
        foreach ($declared as $perm => $value) {
706
            if ($value instanceof Permission_Group) {
707
                $list[] = $value->getName();
708
                self::traverse_declared_permissions($value->getPermissions(), $list);
0 ignored issues
show
Deprecated Code introduced by
The function SilverStripe\Security\Pe..._declared_permissions() has been deprecated: 4.4.0 ( Ignorable by Annotation )

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

708
                /** @scrutinizer ignore-deprecated */ self::traverse_declared_permissions($value->getPermissions(), $list);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
709
            } else {
710
                $list[$perm] = $value;
711
            }
712
        }
713
    }
714
715
    public function onBeforeWrite()
716
    {
717
        parent::onBeforeWrite();
718
719
        // Just in case we've altered someone's permissions
720
        Permission::reset();
721
    }
722
723
    public static function get_template_global_variables()
724
    {
725
        return array(
726
            'HasPerm' => 'check'
727
        );
728
    }
729
730
    public function provideI18nEntities()
731
    {
732
        $keys = parent::provideI18nEntities(); // TODO: Change the autogenerated stub
733
734
        // Localise all permission categories
735
        $keys[__CLASS__ . '.AdminGroup'] = 'Administrator';
736
        $keys[__CLASS__ . '.CMS_ACCESS_CATEGORY'] = 'CMS Access';
737
        $keys[__CLASS__ . '.CONTENT_CATEGORY'] = 'Content permissions';
738
        $keys[__CLASS__ . '.PERMISSIONS_CATEGORY'] = 'Roles and access permissions';
739
        return $keys;
740
    }
741
}
742