Completed
Push — master ( 8f43bc...c99ed2 )
by Damian
09:10
created

Permission   D

Complexity

Total Complexity 92

Size/Duplication

Total Lines 714
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 9

Importance

Changes 0
Metric Value
dl 0
loc 714
rs 4.4444
c 0
b 0
f 0
wmc 92
lcom 3
cbo 9

17 Methods

Rating   Name   Duplication   Size   Complexity  
A check() 0 11 3
A reset() 0 4 1
F checkMember() 0 123 27
B permissions_for_member() 0 32 2
D groupList() 0 36 9
B grant() 0 28 4
B deny() 0 28 4
B get_members_by_permission() 0 28 5
A get_groups_by_permission() 0 17 2
D get_codes() 0 100 20
A sort_permissions() 0 10 3
A get_declared_permissions_list() 0 16 3
A get_label_for_permission() 0 8 2
A traverse_declared_permissions() 0 15 4
A onBeforeWrite() 0 7 1
A get_template_global_variables() 0 6 1
A provideI18nEntities() 0 11 1

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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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\DB;
10
use SilverStripe\ORM\DataObject;
11
use SilverStripe\ORM\ArrayList;
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(
30
        "Code" => "Varchar(255)",
31
        "Arg" => "Int",
32
        "Type" => "Int(1)"
33
    );
34
35
    private static $has_one = array(
36
        "Group" => "SilverStripe\\Security\\Group"
37
    );
38
39
    private static $indexes = array(
40
        "Code" => true
41
    );
42
43
    private static $defaults = array(
44
        "Type" => 1
45
    );
46
47
    private static $table_name = "Permission";
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
     * @var array
73
     */
74
    private static $declared_permissions = null;
75
76
    /**
77
     * Linear list of declared permissions in the system.
78
     *
79
     * @var array
80
     */
81
    private static $declared_permissions_list = null;
82
83
    /**
84
     * @config
85
     * @var $strict_checking Boolean Method to globally disable "strict" checking,
86
     * which means a permission will be granted if the key does not exist at all.
87
     */
88
    private static $strict_checking = true;
89
90
    /**
91
     * Set to false to prevent the 'ADMIN' permission from implying all
92
     * permissions in the system
93
     *
94
     * @config
95
     * @var bool
96
     */
97
    private static $admin_implies_all = true;
98
99
    /**
100
     * a list of permission codes which doesn't appear in the Permission list
101
     * when make the {@link PermissionCheckboxSetField}
102
     * @config
103
     * @var array;
104
     */
105
    private static $hidden_permissions = array();
106
107
    /**
108
     * @config These permissions can only be applied by ADMIN users, to prevent
109
     * privilege escalation on group assignments and inheritance.
110
     * @var array
111
     */
112
    private static $privileged_permissions = array(
113
        'ADMIN',
114
        'APPLY_ROLES',
115
        'EDIT_PERMISSIONS'
116
    );
117
118
    /**
119
     * Check that the current member has the given permission.
120
     *
121
     * @param string|array $code Code of the permission to check (case-sensitive)
122
     * @param string $arg Optional argument (e.g. a permissions for a specific page)
123
     * @param int|Member $member Optional member instance or ID. If set to NULL, the permssion
124
     *  will be checked for the current user
125
     * @param bool $strict Use "strict" checking (which means a permission
126
     *  will be granted if the key does not exist at all)?
127
     * @return int|bool The ID of the permission record if the permission
128
     *  exists; FALSE otherwise. If "strict" checking is
129
     *  disabled, TRUE will be returned if the permission does not exist at all.
130
     */
131
    public static function check($code, $arg = "any", $member = null, $strict = true)
132
    {
133
        if (!$member) {
134
            if (!Member::currentUserID()) {
135
                return false;
136
            }
137
            $member = Member::currentUserID();
138
        }
139
140
        return self::checkMember($member, $code, $arg, $strict);
141
    }
142
143
    /**
144
     * Permissions cache.  The format is a map, where the keys are member IDs, and the values are
145
     * arrays of permission codes.
146
     */
147
    private static $cache_permissions = array();
148
149
    /**
150
     * Flush the permission cache, for example if you have edited group membership or a permission record.
151
     * @todo Call this whenever Group_Members is added to or removed from
152
     */
153
    public static function reset()
154
    {
155
        self::$cache_permissions = array();
156
    }
157
158
    /**
159
     * Check that the given member has the given permission.
160
     *
161
     * @param int|Member memberID The ID of the member to check. Leave blank for the current member.
162
     *  Alternatively you can use a member object.
163
     * @param string|array $code Code of the permission to check (case-sensitive)
164
     * @param string $arg Optional argument (e.g. a permissions for a specific page)
165
     * @param bool $strict Use "strict" checking (which means a permission
166
     *  will be granted if the key does not exist at all)?
167
     * @return int|bool The ID of the permission record if the permission
168
     *  exists; FALSE otherwise. If "strict" checking is
169
     *  disabled, TRUE will be returned if the permission does not exist at all.
170
     */
171
    public static function checkMember($member, $code, $arg = "any", $strict = true)
172
    {
173
        if (!$member) {
174
            $memberID = $member = Member::currentUserID();
175
        } else {
176
            $memberID = (is_object($member)) ? $member->ID : $member;
177
        }
178
179
        if (!$memberID) {
180
            return false;
181
        }
182
183
        // Turn the code into an array as we may need to add other permsissions to the set we check
184
        if (!is_array($code)) {
185
            $code = array($code);
186
        }
187
188
        // Check if admin should be treated as holding all permissions
189
        $adminImpliesAll = (bool)static::config()->admin_implies_all;
190
191
        if ($arg == 'any') {
192
            // Cache the permissions in memory
193
            if (!isset(self::$cache_permissions[$memberID])) {
194
                self::$cache_permissions[$memberID] = self::permissions_for_member($memberID);
195
            }
196
            foreach ($code as $permCode) {
197
                if ($permCode === 'CMS_ACCESS') {
198
                    foreach (self::$cache_permissions[$memberID] as $perm) {
199
                        //if they have admin rights OR they have an explicit access to the CMS then give permission
200
                        if (($adminImpliesAll && $perm == 'ADMIN') || substr($perm, 0, 11) === 'CMS_ACCESS_') {
201
                            return true;
202
                        }
203
                    }
204
                } elseif (substr($permCode, 0, 11) === 'CMS_ACCESS_' && !in_array('CMS_ACCESS_LeftAndMain', $code)) {
205
                    //cms_access_leftandmain means access to all CMS areas
206
                    $code[] = 'CMS_ACCESS_LeftAndMain';
207
                }
208
            }
209
210
            // if ADMIN has all privileges, then we need to push that code in
211
            if ($adminImpliesAll) {
212
                $code[] = "ADMIN";
213
            }
214
215
            // Multiple $code values - return true if at least one matches, ie, intersection exists
216
            return (bool)array_intersect($code, self::$cache_permissions[$memberID]);
217
        }
218
219
        // Code filters
220
        $codeParams = is_array($code) ? $code : array($code);
221
        $codeClause = DB::placeholders($codeParams);
222
        $adminParams = $adminImpliesAll ? array('ADMIN') : array();
223
        $adminClause = $adminImpliesAll ?  ", ?" : '';
224
225
        // The following code should only be used if you're not using the "any" arg.  This is kind
226
        // of obselete functionality and could possibly be deprecated.
227
        $groupParams = self::groupList($memberID);
228
        if (empty($groupParams)) {
229
            return false;
230
        }
231
        $groupClause = DB::placeholders($groupParams);
232
233
        // Arg component
234
        $argClause = "";
235
        $argParams = array();
236
        switch ($arg) {
237
            case "any":
238
                break;
239
            case "all":
240
                $argClause = " AND \"Arg\" = ?";
241
                $argParams = array(-1);
242
                break;
243
            default:
244
                if (is_numeric($arg)) {
245
                    $argClause = "AND \"Arg\" IN (?, ?) ";
246
                    $argParams = array(-1, $arg);
247
                } else {
248
                    user_error("Permission::checkMember: bad arg '$arg'", E_USER_ERROR);
249
                }
250
        }
251
252
        // Raw SQL for efficiency
253
        $permission = DB::prepared_query(
254
            "SELECT \"ID\"
255
			FROM \"Permission\"
256
			WHERE (
257
				\"Code\" IN ($codeClause $adminClause)
258
				AND \"Type\" = ?
259
				AND \"GroupID\" IN ($groupClause)
260
				$argClause
261
			)",
262
            array_merge(
263
                $codeParams,
264
                $adminParams,
265
                array(self::GRANT_PERMISSION),
266
                $groupParams,
267
                $argParams
268
            )
269
        )->value();
270
271
        if ($permission) {
272
            return $permission;
273
        }
274
275
        // Strict checking disabled?
276
        if (!static::config()->strict_checking || !$strict) {
277
            $hasPermission = DB::prepared_query(
278
                "SELECT COUNT(*)
279
				FROM \"Permission\"
280
				WHERE (
281
					\"Code\" IN ($codeClause) AND
282
					\"Type\" = ?
283
				)",
284
                array_merge($codeParams, array(self::GRANT_PERMISSION))
285
            )->value();
286
287
            if (!$hasPermission) {
288
                return false;
289
            }
290
        }
291
292
        return false;
293
    }
294
295
    /**
296
     * Get all the 'any' permission codes available to the given member.
297
     *
298
     * @param int $memberID
299
     * @return array
300
     */
301
    public static function permissions_for_member($memberID)
302
    {
303
        $groupList = self::groupList($memberID);
304
305
        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...
306
            $groupCSV = implode(", ", $groupList);
307
308
            $allowed = array_unique(DB::query("
309
				SELECT \"Code\"
310
				FROM \"Permission\"
311
				WHERE \"Type\" = " . self::GRANT_PERMISSION . " AND \"GroupID\" IN ($groupCSV)
312
313
				UNION
314
315
				SELECT \"Code\"
316
				FROM \"PermissionRoleCode\" PRC
317
				INNER JOIN \"PermissionRole\" PR ON PRC.\"RoleID\" = PR.\"ID\"
318
				INNER JOIN \"Group_Roles\" GR ON GR.\"PermissionRoleID\" = PR.\"ID\"
319
				WHERE \"GroupID\" IN ($groupCSV)
320
			")->column());
321
322
            $denied = array_unique(DB::query("
323
				SELECT \"Code\"
324
				FROM \"Permission\"
325
				WHERE \"Type\" = " . self::DENY_PERMISSION . " AND \"GroupID\" IN ($groupCSV)
326
			")->column());
327
328
            return array_diff($allowed, $denied);
329
        }
330
331
        return array();
332
    }
333
334
335
    /**
336
     * Get the list of groups that the given member belongs to.
337
     *
338
     * Call without an argument to get the groups that the current member
339
     * belongs to. In this case, the results will be session-cached.
340
     *
341
     * @param int $memberID The ID of the member. Leave blank for the current
342
     *                      member.
343
     * @return array Returns a list of group IDs to which the member belongs
344
     *               to or NULL.
345
     */
346
    public static function groupList($memberID = null)
0 ignored issues
show
Coding Style introduced by
groupList uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
347
    {
348
        // Default to current member, with session-caching
349
        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 zero. 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...
350
            $member = Member::currentUser();
351
            if ($member && isset($_SESSION['Permission_groupList'][$member->ID])) {
352
                return $_SESSION['Permission_groupList'][$member->ID];
353
            }
354
        } else {
355
            $member = DataObject::get_by_id("SilverStripe\\Security\\Member", $memberID);
356
        }
357
358
        if ($member) {
359
            // Build a list of the IDs of the groups.  Most of the heavy lifting
360
            // is done by Member::Groups
361
            // NOTE: This isn't effecient; but it's called once per session so
362
            // it's a low priority to fix.
363
            $groups = $member->Groups();
364
            $groupList = array();
365
366
            if ($groups) {
367
                foreach ($groups as $group) {
368
                    $groupList[] = $group->ID;
369
                }
370
            }
371
372
373
            // Session caching
374
            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 zero. 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...
375
                $_SESSION['Permission_groupList'][$member->ID] = $groupList;
376
            }
377
378
            return isset($groupList) ? $groupList : null;
379
        }
380
        return null;
381
    }
382
383
384
    /**
385
     * Grant the given permission code/arg to the given group
386
     *
387
     * @param int $groupID The ID of the group
388
     * @param string $code The permission code
389
     * @param string $arg Optional: The permission argument (e.g. a page ID).
390
     * @returns Permission Returns the new permission object.
391
     */
392
    public static function grant($groupID, $code, $arg = "any")
393
    {
394
        $perm = new Permission();
395
        $perm->GroupID = $groupID;
396
        $perm->Code = $code;
397
        $perm->Type = self::GRANT_PERMISSION;
398
399
        // Arg component
400
        switch ($arg) {
401
            case "any":
402
                break;
403
            case "all":
404
                $perm->Arg = -1;
405
                break;
406
            default:
407
                if (is_numeric($arg)) {
408
                    $perm->Arg = $arg;
409
                } else {
410
                    user_error(
411
                        "Permission::checkMember: bad arg '$arg'",
412
                        E_USER_ERROR
413
                    );
414
                }
415
        }
416
417
        $perm->write();
418
        return $perm;
419
    }
420
421
422
    /**
423
     * Deny the given permission code/arg to the given group
424
     *
425
     * @param int $groupID The ID of the group
426
     * @param string $code The permission code
427
     * @param string $arg Optional: The permission argument (e.g. a page ID).
428
     * @returns Permission Returns the new permission object.
429
     */
430
    public static function deny($groupID, $code, $arg = "any")
431
    {
432
        $perm = new Permission();
433
        $perm->GroupID = $groupID;
434
        $perm->Code = $code;
435
        $perm->Type = self::DENY_PERMISSION;
436
437
        // Arg component
438
        switch ($arg) {
439
            case "any":
440
                break;
441
            case "all":
442
                $perm->Arg = -1;
443
                break;
444
            default:
445
                if (is_numeric($arg)) {
446
                    $perm->Arg = $arg;
447
                } else {
448
                    user_error(
449
                        "Permission::checkMember: bad arg '$arg'",
450
                        E_USER_ERROR
451
                    );
452
                }
453
        }
454
455
        $perm->write();
456
        return $perm;
457
    }
458
459
    /**
460
     * Returns all members for a specific permission.
461
     *
462
     * @param $code String|array Either a single permission code, or a list of permission codes
463
     * @return SS_List Returns a set of member that have the specified
464
     *                       permission.
465
     */
466
    public static function get_members_by_permission($code)
467
    {
468
        $toplevelGroups = self::get_groups_by_permission($code);
469
        if (!$toplevelGroups) {
470
            return new ArrayList();
471
        }
472
473
        $groupIDs = array();
474
        foreach ($toplevelGroups as $group) {
475
            $familyIDs = $group->collateFamilyIDs();
476
            if (is_array($familyIDs)) {
477
                $groupIDs = array_merge($groupIDs, array_values($familyIDs));
478
            }
479
        }
480
481
        if (empty($groupIDs)) {
482
            return new ArrayList();
483
        }
484
485
        $groupClause = DB::placeholders($groupIDs);
486
        /** @skipUpgrade */
487
        $members = Member::get()
488
            ->where(array("\"Group\".\"ID\" IN ($groupClause)" => $groupIDs))
489
            ->leftJoin("Group_Members", '"Member"."ID" = "Group_Members"."MemberID"')
490
            ->leftJoin("Group", '"Group_Members"."GroupID" = "Group"."ID"');
491
492
        return $members;
493
    }
494
495
    /**
496
     * Return all of the groups that have one of the given permission codes
497
     * @param array|string $codes Either a single permission code, or an array of permission codes
498
     * @return SS_List The matching group objects
499
     */
500
    public static function get_groups_by_permission($codes)
501
    {
502
        $codeParams = is_array($codes) ? $codes : array($codes);
503
        $codeClause = DB::placeholders($codeParams);
504
505
        // Via Roles are groups that have the permission via a role
506
        /** @skipUpgrade */
507
        return Group::get()
508
            ->where(array(
509
                "\"PermissionRoleCode\".\"Code\" IN ($codeClause) OR \"Permission\".\"Code\" IN ($codeClause)"
510
                => array_merge($codeParams, $codeParams)
511
            ))
512
            ->leftJoin('Permission', "\"Permission\".\"GroupID\" = \"Group\".\"ID\"")
513
            ->leftJoin('Group_Roles', "\"Group_Roles\".\"GroupID\" = \"Group\".\"ID\"")
514
            ->leftJoin('PermissionRole', "\"Group_Roles\".\"PermissionRoleID\" = \"PermissionRole\".\"ID\"")
515
            ->leftJoin('PermissionRoleCode', "\"PermissionRoleCode\".\"RoleID\" = \"PermissionRole\".\"ID\"");
516
    }
517
518
519
    /**
520
     * Get a list of all available permission codes, both defined through the
521
     * {@link PermissionProvider} interface, and all not explicitly defined codes existing
522
     * as a {@link Permission} database record. By default, the results are
523
     * grouped as denoted by {@link Permission_Group}.
524
     *
525
     * @param bool $grouped Group results into an array of permission groups.
526
     * @return array Returns an array of all available permission codes. The
527
     *  array indicies are the permission codes as used in
528
     *  {@link Permission::check()}. The value is a description
529
     *  suitable for using in an interface.
530
     */
531
    public static function get_codes($grouped = true)
532
    {
533
        $classes = ClassInfo::implementorsOf('SilverStripe\\Security\\PermissionProvider');
534
535
        $allCodes = array();
536
        $adminCategory = _t(__CLASS__.'.AdminGroup', 'Administrator');
537
        $allCodes[$adminCategory]['ADMIN'] = array(
538
            'name' => _t(__CLASS__.'.FULLADMINRIGHTS', 'Full administrative rights'),
539
            'help' => _t(
540
                'SilverStripe\\Security\\Permission.FULLADMINRIGHTS_HELP',
541
                'Implies and overrules all other assigned permissions.'
542
            ),
543
            'sort' => 100000
544
        );
545
546
        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...
547
            foreach ($classes as $class) {
548
                $SNG = singleton($class);
549
                if ($SNG instanceof TestOnly) {
550
                    continue;
551
                }
552
553
                $someCodes = $SNG->providePermissions();
554
                if ($someCodes) {
555
                    foreach ($someCodes as $k => $v) {
556
                        if (is_array($v)) {
557
                            // There must be a category and name key.
558
                            if (!isset($v['category'])) {
559
                                user_error(
560
                                    "The permission $k must have a category key",
561
                                    E_USER_WARNING
562
                                );
563
                            }
564
                            if (!isset($v['name'])) {
565
                                user_error(
566
                                    "The permission $k must have a name key",
567
                                    E_USER_WARNING
568
                                );
569
                            }
570
571
                            if (!isset($allCodes[$v['category']])) {
572
                                $allCodes[$v['category']] = array();
573
                            }
574
575
                            $allCodes[$v['category']][$k] = array(
576
                            'name' => $v['name'],
577
                            'help' => isset($v['help']) ? $v['help'] : null,
578
                            'sort' => isset($v['sort']) ? $v['sort'] : 0
579
                            );
580
                        } else {
581
                            $allCodes['Other'][$k] = array(
582
                            'name' => $v,
583
                            'help' => null,
584
                            'sort' => 0
585
                            );
586
                        }
587
                    }
588
                }
589
            }
590
        }
591
592
        $flatCodeArray = array();
593
        foreach ($allCodes as $category) {
594
            foreach ($category as $code => $permission) {
595
                $flatCodeArray[] = $code;
596
            }
597
        }
598
        $otherPerms = DB::query("SELECT DISTINCT \"Code\" From \"Permission\" WHERE \"Code\" != ''")->column();
599
600
        if ($otherPerms) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $otherPerms 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...
601
            foreach ($otherPerms as $otherPerm) {
602
                if (!in_array($otherPerm, $flatCodeArray)) {
603
                    $allCodes['Other'][$otherPerm] = array(
604
                    'name' => $otherPerm,
605
                    'help' => null,
606
                    'sort' => 0
607
                    );
608
                }
609
            }
610
        }
611
612
        // Don't let people hijack ADMIN rights
613
        if (!Permission::check("ADMIN")) {
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
614
            unset($allCodes['ADMIN']);
615
        }
616
617
        ksort($allCodes);
618
619
        $returnCodes = array();
620
        foreach ($allCodes as $category => $permissions) {
621
            if ($grouped) {
622
                uasort($permissions, array(__CLASS__, 'sort_permissions'));
623
                $returnCodes[$category] = $permissions;
624
            } else {
625
                $returnCodes = array_merge($returnCodes, $permissions);
626
            }
627
        }
628
629
        return $returnCodes;
630
    }
631
632
    /**
633
     * Sort permissions based on their sort value, or name
634
     *
635
     * @param array $a
636
     * @param array $b
637
     * @return int
638
     */
639
    public static function sort_permissions($a, $b)
640
    {
641
        if ($a['sort'] == $b['sort']) {
642
            // Same sort value, do alpha instead
643
            return strcmp($a['name'], $b['name']);
644
        } else {
645
            // Just numeric.
646
            return $a['sort'] < $b['sort'] ? -1 : 1;
647
        }
648
    }
649
650
    /**
651
     * Get a linear list of the permissions in the system.
652
     *
653
     * @return array Linear list of declared permissions in the system.
654
     */
655
    public static function get_declared_permissions_list()
656
    {
657
        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...
658
            return null;
659
        }
660
661
        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...
662
            return self::$declared_permissions_list;
663
        }
664
665
        self::$declared_permissions_list = array();
666
667
        self::traverse_declared_permissions(self::$declared_permissions, self::$declared_permissions_list);
668
669
        return self::$declared_permissions_list;
670
    }
671
672
    /**
673
     * Look up the human-readable title for the permission as defined by <code>Permission::declare_permissions</code>
674
     *
675
     * @param string $perm Permission code
676
     * @return string Label for the given permission, or the permission itself if the label doesn't exist
677
     */
678
    public static function get_label_for_permission($perm)
679
    {
680
        $list = self::get_declared_permissions_list();
681
        if (array_key_exists($perm, $list)) {
682
            return $list[$perm];
683
        }
684
        return $perm;
685
    }
686
687
    /**
688
     * Recursively traverse the nested list of declared permissions and create
689
     * a linear list.
690
     *
691
     * @param array $declared Nested structure of permissions.
692
     * @param array $list List of permissions in the structure. The result will be
693
     *              written to this array.
694
     */
695
    protected static function traverse_declared_permissions($declared, &$list)
696
    {
697
        if (!is_array($declared)) {
698
            return;
699
        }
700
701
        foreach ($declared as $perm => $value) {
702
            if ($value instanceof Permission_Group) {
703
                $list[] = $value->getName();
704
                self::traverse_declared_permissions($value->getPermissions(), $list);
705
            } else {
706
                $list[$perm] = $value;
707
            }
708
        }
709
    }
710
711
    public function onBeforeWrite()
712
    {
713
        parent::onBeforeWrite();
714
715
        // Just in case we've altered someone's permissions
716
        Permission::reset();
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
717
    }
718
719
    public static function get_template_global_variables()
720
    {
721
        return array(
722
            'HasPerm' => 'check'
723
        );
724
    }
725
726
    public function provideI18nEntities()
727
    {
728
        $keys = parent::provideI18nEntities(); // TODO: Change the autogenerated stub
729
730
        // Localise all permission categories
731
        $keys[__CLASS__.'.AdminGroup'] = 'Administrator';
732
        $keys[__CLASS__.'.CMS_ACCESS_CATEGORY'] = 'CMS Access';
733
        $keys[__CLASS__.'.CONTENT_CATEGORY'] = 'Content permissions';
734
        $keys[__CLASS__.'.PERMISSIONS_CATEGORY'] = 'Roles and access permissions';
735
        return $keys;
736
    }
737
}
738