Completed
Push — master ( c17796...052b15 )
by Damian
01:29
created

Permission::reset()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
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
     * @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();
0 ignored issues
show
introduced by
The private property $hidden_permissions is not used, and could be removed.
Loading history...
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(
0 ignored issues
show
introduced by
The private property $privileged_permissions is not used, and could be removed.
Loading history...
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 (!Security::getCurrentUser()) {
135
                return false;
136
            }
137
            $member = Security::getCurrentUser();
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.
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...
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
            $member = Security::getCurrentUser();
175
        }
176
        $memberID = ($member instanceof Member) ? $member->ID : $member;
177
178
        if (!$memberID) {
179
            return false;
180
        }
181
182
        // Turn the code into an array as we may need to add other permsissions to the set we check
183
        if (!is_array($code)) {
184
            $code = array($code);
185
        }
186
187
        // Check if admin should be treated as holding all permissions
188
        $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...
189
190
        if ($arg == 'any') {
191
            // Cache the permissions in memory
192
            if (!isset(self::$cache_permissions[$memberID])) {
193
                self::$cache_permissions[$memberID] = self::permissions_for_member($memberID);
194
            }
195
            foreach ($code as $permCode) {
196
                if ($permCode === 'CMS_ACCESS') {
197
                    foreach (self::$cache_permissions[$memberID] as $perm) {
198
                        //if they have admin rights OR they have an explicit access to the CMS then give permission
199
                        if (($adminImpliesAll && $perm == 'ADMIN') || substr($perm, 0, 11) === 'CMS_ACCESS_') {
200
                            return true;
201
                        }
202
                    }
203
                } elseif (substr($permCode, 0, 11) === 'CMS_ACCESS_' && !in_array('CMS_ACCESS_LeftAndMain', $code)) {
204
                    //cms_access_leftandmain means access to all CMS areas
205
                    $code[] = 'CMS_ACCESS_LeftAndMain';
206
                }
207
            }
208
209
            // if ADMIN has all privileges, then we need to push that code in
210
            if ($adminImpliesAll) {
211
                $code[] = "ADMIN";
212
            }
213
214
            // Multiple $code values - return true if at least one matches, ie, intersection exists
215
            return (bool)array_intersect($code, self::$cache_permissions[$memberID]);
216
        }
217
218
        // Code filters
219
        $codeParams = is_array($code) ? $code : array($code);
220
        $codeClause = DB::placeholders($codeParams);
221
        $adminParams = $adminImpliesAll ? array('ADMIN') : array();
222
        $adminClause = $adminImpliesAll ?  ", ?" : '';
223
224
        // The following code should only be used if you're not using the "any" arg.  This is kind
225
        // of obselete functionality and could possibly be deprecated.
226
        $groupParams = self::groupList($memberID);
227
        if (empty($groupParams)) {
228
            return false;
229
        }
230
        $groupClause = DB::placeholders($groupParams);
231
232
        // Arg component
233
        $argClause = "";
234
        $argParams = array();
235
        switch ($arg) {
236
            case "any":
237
                break;
238
            case "all":
239
                $argClause = " AND \"Arg\" = ?";
240
                $argParams = array(-1);
241
                break;
242
            default:
243
                if (is_numeric($arg)) {
244
                    $argClause = "AND \"Arg\" IN (?, ?) ";
245
                    $argParams = array(-1, $arg);
246
                } else {
247
                    user_error("Permission::checkMember: bad arg '$arg'", E_USER_ERROR);
248
                }
249
        }
250
251
        // Raw SQL for efficiency
252
        $permission = DB::prepared_query(
253
            "SELECT \"ID\"
254
			FROM \"Permission\"
255
			WHERE (
256
				\"Code\" IN ($codeClause $adminClause)
257
				AND \"Type\" = ?
258
				AND \"GroupID\" IN ($groupClause)
259
				$argClause
260
			)",
261
            array_merge(
262
                $codeParams,
263
                $adminParams,
264
                array(self::GRANT_PERMISSION),
265
                $groupParams,
266
                $argParams
267
            )
268
        )->value();
269
270
        if ($permission) {
271
            return $permission;
272
        }
273
274
        // Strict checking disabled?
275
        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...
276
            $hasPermission = DB::prepared_query(
277
                "SELECT COUNT(*)
278
				FROM \"Permission\"
279
				WHERE (
280
					\"Code\" IN ($codeClause) AND
281
					\"Type\" = ?
282
				)",
283
                array_merge($codeParams, array(self::GRANT_PERMISSION))
284
            )->value();
285
286
            if (!$hasPermission) {
287
                return false;
288
            }
289
        }
290
291
        return false;
292
    }
293
294
    /**
295
     * Get all the 'any' permission codes available to the given member.
296
     *
297
     * @param int $memberID
298
     * @return array
299
     */
300
    public static function permissions_for_member($memberID)
301
    {
302
        $groupList = self::groupList($memberID);
303
304
        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...
305
            $groupCSV = implode(", ", $groupList);
306
307
            $allowed = array_unique(DB::query("
308
				SELECT \"Code\"
309
				FROM \"Permission\"
310
				WHERE \"Type\" = " . self::GRANT_PERMISSION . " AND \"GroupID\" IN ($groupCSV)
311
312
				UNION
313
314
				SELECT \"Code\"
315
				FROM \"PermissionRoleCode\" PRC
316
				INNER JOIN \"PermissionRole\" PR ON PRC.\"RoleID\" = PR.\"ID\"
317
				INNER JOIN \"Group_Roles\" GR ON GR.\"PermissionRoleID\" = PR.\"ID\"
318
				WHERE \"GroupID\" IN ($groupCSV)
319
			")->column());
320
321
            $denied = array_unique(DB::query("
322
				SELECT \"Code\"
323
				FROM \"Permission\"
324
				WHERE \"Type\" = " . self::DENY_PERMISSION . " AND \"GroupID\" IN ($groupCSV)
325
			")->column());
326
327
            return array_diff($allowed, $denied);
328
        }
329
330
        return array();
331
    }
332
333
334
    /**
335
     * Get the list of groups that the given member belongs to.
336
     *
337
     * Call without an argument to get the groups that the current member
338
     * belongs to. In this case, the results will be session-cached.
339
     *
340
     * @param int $memberID The ID of the member. Leave blank for the current
341
     *                      member.
342
     * @return array Returns a list of group IDs to which the member belongs
343
     *               to or NULL.
344
     */
345
    public static function groupList($memberID = null)
346
    {
347
        // Default to current member, with session-caching
348
        if (!$memberID) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $memberID of type null|integer 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...
349
            $member = Security::getCurrentUser();
350
            if ($member && isset($_SESSION['Permission_groupList'][$member->ID])) {
351
                return $_SESSION['Permission_groupList'][$member->ID];
352
            }
353
        } else {
354
            $member = DataObject::get_by_id("SilverStripe\\Security\\Member", $memberID);
355
        }
356
357
        if ($member) {
358
            // Build a list of the IDs of the groups.  Most of the heavy lifting
359
            // is done by Member::Groups
360
            // NOTE: This isn't effecient; but it's called once per session so
361
            // it's a low priority to fix.
362
            $groups = $member->Groups();
363
            $groupList = array();
364
365
            if ($groups) {
366
                foreach ($groups as $group) {
367
                    $groupList[] = $group->ID;
368
                }
369
            }
370
371
372
            // Session caching
373
            if (!$memberID) {
374
                $_SESSION['Permission_groupList'][$member->ID] = $groupList;
375
            }
376
377
            return isset($groupList) ? $groupList : null;
378
        }
379
        return null;
380
    }
381
382
383
    /**
384
     * Grant the given permission code/arg to the given group
385
     *
386
     * @param int $groupID The ID of the group
387
     * @param string $code The permission code
388
     * @param string $arg Optional: The permission argument (e.g. a page ID).
389
     * @returns Permission Returns the new permission object.
390
     */
391
    public static function grant($groupID, $code, $arg = "any")
392
    {
393
        $perm = new Permission();
394
        $perm->GroupID = $groupID;
395
        $perm->Code = $code;
396
        $perm->Type = self::GRANT_PERMISSION;
397
398
        // Arg component
399
        switch ($arg) {
400
            case "any":
401
                break;
402
            case "all":
403
                $perm->Arg = -1;
404
                break;
405
            default:
406
                if (is_numeric($arg)) {
407
                    $perm->Arg = $arg;
408
                } else {
409
                    user_error(
410
                        "Permission::checkMember: bad arg '$arg'",
411
                        E_USER_ERROR
412
                    );
413
                }
414
        }
415
416
        $perm->write();
417
        return $perm;
418
    }
419
420
421
    /**
422
     * Deny the given permission code/arg to the given group
423
     *
424
     * @param int $groupID The ID of the group
425
     * @param string $code The permission code
426
     * @param string $arg Optional: The permission argument (e.g. a page ID).
427
     * @returns Permission Returns the new permission object.
428
     */
429
    public static function deny($groupID, $code, $arg = "any")
430
    {
431
        $perm = new Permission();
432
        $perm->GroupID = $groupID;
433
        $perm->Code = $code;
434
        $perm->Type = self::DENY_PERMISSION;
435
436
        // Arg component
437
        switch ($arg) {
438
            case "any":
439
                break;
440
            case "all":
441
                $perm->Arg = -1;
442
                break;
443
            default:
444
                if (is_numeric($arg)) {
445
                    $perm->Arg = $arg;
446
                } else {
447
                    user_error(
448
                        "Permission::checkMember: bad arg '$arg'",
449
                        E_USER_ERROR
450
                    );
451
                }
452
        }
453
454
        $perm->write();
455
        return $perm;
456
    }
457
458
    /**
459
     * Returns all members for a specific permission.
460
     *
461
     * @param string|array $code Either a single permission code, or a list of permission codes
462
     * @return SS_List Returns a set of member that have the specified
463
     *                       permission.
464
     */
465
    public static function get_members_by_permission($code)
466
    {
467
        $toplevelGroups = self::get_groups_by_permission($code);
468
        if (!$toplevelGroups) {
469
            return new ArrayList();
470
        }
471
472
        $groupIDs = array();
473
        foreach ($toplevelGroups as $group) {
474
            $familyIDs = $group->collateFamilyIDs();
475
            if (is_array($familyIDs)) {
476
                $groupIDs = array_merge($groupIDs, array_values($familyIDs));
477
            }
478
        }
479
480
        if (empty($groupIDs)) {
481
            return new ArrayList();
482
        }
483
484
        $groupClause = DB::placeholders($groupIDs);
485
        /** @skipUpgrade */
486
        $members = Member::get()
487
            ->where(array("\"Group\".\"ID\" IN ($groupClause)" => $groupIDs))
488
            ->leftJoin("Group_Members", '"Member"."ID" = "Group_Members"."MemberID"')
489
            ->leftJoin("Group", '"Group_Members"."GroupID" = "Group"."ID"');
490
491
        return $members;
492
    }
493
494
    /**
495
     * Return all of the groups that have one of the given permission codes
496
     * @param array|string $codes Either a single permission code, or an array of permission codes
497
     * @return SS_List The matching group objects
498
     */
499
    public static function get_groups_by_permission($codes)
500
    {
501
        $codeParams = is_array($codes) ? $codes : array($codes);
502
        $codeClause = DB::placeholders($codeParams);
503
504
        // Via Roles are groups that have the permission via a role
505
        /** @skipUpgrade */
506
        return Group::get()
507
            ->where(array(
508
                "\"PermissionRoleCode\".\"Code\" IN ($codeClause) OR \"Permission\".\"Code\" IN ($codeClause)"
509
                => array_merge($codeParams, $codeParams)
510
            ))
511
            ->leftJoin('Permission', "\"Permission\".\"GroupID\" = \"Group\".\"ID\"")
512
            ->leftJoin('Group_Roles', "\"Group_Roles\".\"GroupID\" = \"Group\".\"ID\"")
513
            ->leftJoin('PermissionRole', "\"Group_Roles\".\"PermissionRoleID\" = \"PermissionRole\".\"ID\"")
514
            ->leftJoin('PermissionRoleCode', "\"PermissionRoleCode\".\"RoleID\" = \"PermissionRole\".\"ID\"");
515
    }
516
517
518
    /**
519
     * Get a list of all available permission codes, both defined through the
520
     * {@link PermissionProvider} interface, and all not explicitly defined codes existing
521
     * as a {@link Permission} database record. By default, the results are
522
     * grouped as denoted by {@link Permission_Group}.
523
     *
524
     * @param bool $grouped Group results into an array of permission groups.
525
     * @return array Returns an array of all available permission codes. The
526
     *  array indicies are the permission codes as used in
527
     *  {@link Permission::check()}. The value is a description
528
     *  suitable for using in an interface.
529
     */
530
    public static function get_codes($grouped = true)
531
    {
532
        $classes = ClassInfo::implementorsOf('SilverStripe\\Security\\PermissionProvider');
533
534
        $allCodes = array();
535
        $adminCategory = _t(__CLASS__.'.AdminGroup', 'Administrator');
536
        $allCodes[$adminCategory]['ADMIN'] = array(
537
            'name' => _t(__CLASS__.'.FULLADMINRIGHTS', 'Full administrative rights'),
538
            'help' => _t(
539
                'SilverStripe\\Security\\Permission.FULLADMINRIGHTS_HELP',
540
                'Implies and overrules all other assigned permissions.'
541
            ),
542
            'sort' => 100000
543
        );
544
545
        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...
546
            foreach ($classes as $class) {
547
                $SNG = singleton($class);
548
                if ($SNG instanceof TestOnly) {
549
                    continue;
550
                }
551
552
                $someCodes = $SNG->providePermissions();
553
                if ($someCodes) {
554
                    foreach ($someCodes as $k => $v) {
555
                        if (is_array($v)) {
556
                            // There must be a category and name key.
557
                            if (!isset($v['category'])) {
558
                                user_error(
559
                                    "The permission $k must have a category key",
560
                                    E_USER_WARNING
561
                                );
562
                            }
563
                            if (!isset($v['name'])) {
564
                                user_error(
565
                                    "The permission $k must have a name key",
566
                                    E_USER_WARNING
567
                                );
568
                            }
569
570
                            if (!isset($allCodes[$v['category']])) {
571
                                $allCodes[$v['category']] = array();
572
                            }
573
574
                            $allCodes[$v['category']][$k] = array(
575
                            'name' => $v['name'],
576
                            'help' => isset($v['help']) ? $v['help'] : null,
577
                            'sort' => isset($v['sort']) ? $v['sort'] : 0
578
                            );
579
                        } else {
580
                            $allCodes['Other'][$k] = array(
581
                            'name' => $v,
582
                            'help' => null,
583
                            'sort' => 0
584
                            );
585
                        }
586
                    }
587
                }
588
            }
589
        }
590
591
        $flatCodeArray = array();
592
        foreach ($allCodes as $category) {
593
            foreach ($category as $code => $permission) {
594
                $flatCodeArray[] = $code;
595
            }
596
        }
597
        $otherPerms = DB::query("SELECT DISTINCT \"Code\" From \"Permission\" WHERE \"Code\" != ''")->column();
598
599
        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...
600
            foreach ($otherPerms as $otherPerm) {
601
                if (!in_array($otherPerm, $flatCodeArray)) {
602
                    $allCodes['Other'][$otherPerm] = array(
603
                    'name' => $otherPerm,
604
                    'help' => null,
605
                    'sort' => 0
606
                    );
607
                }
608
            }
609
        }
610
611
        // Don't let people hijack ADMIN rights
612
        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...
613
            unset($allCodes['ADMIN']);
614
        }
615
616
        ksort($allCodes);
617
618
        $returnCodes = array();
619
        foreach ($allCodes as $category => $permissions) {
620
            if ($grouped) {
621
                uasort($permissions, array(__CLASS__, 'sort_permissions'));
622
                $returnCodes[$category] = $permissions;
623
            } else {
624
                $returnCodes = array_merge($returnCodes, $permissions);
625
            }
626
        }
627
628
        return $returnCodes;
629
    }
630
631
    /**
632
     * Sort permissions based on their sort value, or name
633
     *
634
     * @param array $a
635
     * @param array $b
636
     * @return int
637
     */
638
    public static function sort_permissions($a, $b)
639
    {
640
        if ($a['sort'] == $b['sort']) {
641
            // Same sort value, do alpha instead
642
            return strcmp($a['name'], $b['name']);
643
        } else {
644
            // Just numeric.
645
            return $a['sort'] < $b['sort'] ? -1 : 1;
646
        }
647
    }
648
649
    /**
650
     * Get a linear list of the permissions in the system.
651
     *
652
     * @return array Linear list of declared permissions in the system.
653
     */
654
    public static function get_declared_permissions_list()
655
    {
656
        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...
657
            return null;
658
        }
659
660
        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...
661
            return self::$declared_permissions_list;
662
        }
663
664
        self::$declared_permissions_list = array();
665
666
        self::traverse_declared_permissions(self::$declared_permissions, self::$declared_permissions_list);
667
668
        return self::$declared_permissions_list;
669
    }
670
671
    /**
672
     * Look up the human-readable title for the permission as defined by <code>Permission::declare_permissions</code>
673
     *
674
     * @param string $perm Permission code
675
     * @return string Label for the given permission, or the permission itself if the label doesn't exist
676
     */
677
    public static function get_label_for_permission($perm)
678
    {
679
        $list = self::get_declared_permissions_list();
680
        if (array_key_exists($perm, $list)) {
681
            return $list[$perm];
682
        }
683
        return $perm;
684
    }
685
686
    /**
687
     * Recursively traverse the nested list of declared permissions and create
688
     * a linear list.
689
     *
690
     * @param array $declared Nested structure of permissions.
691
     * @param array $list List of permissions in the structure. The result will be
692
     *              written to this array.
693
     */
694
    protected static function traverse_declared_permissions($declared, &$list)
695
    {
696
        if (!is_array($declared)) {
697
            return;
698
        }
699
700
        foreach ($declared as $perm => $value) {
701
            if ($value instanceof Permission_Group) {
702
                $list[] = $value->getName();
703
                self::traverse_declared_permissions($value->getPermissions(), $list);
704
            } else {
705
                $list[$perm] = $value;
706
            }
707
        }
708
    }
709
710
    public function onBeforeWrite()
711
    {
712
        parent::onBeforeWrite();
713
714
        // Just in case we've altered someone's permissions
715
        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...
716
    }
717
718
    public static function get_template_global_variables()
719
    {
720
        return array(
721
            'HasPerm' => 'check'
722
        );
723
    }
724
725
    public function provideI18nEntities()
726
    {
727
        $keys = parent::provideI18nEntities(); // TODO: Change the autogenerated stub
728
729
        // Localise all permission categories
730
        $keys[__CLASS__.'.AdminGroup'] = 'Administrator';
731
        $keys[__CLASS__.'.CMS_ACCESS_CATEGORY'] = 'CMS Access';
732
        $keys[__CLASS__.'.CONTENT_CATEGORY'] = 'Content permissions';
733
        $keys[__CLASS__.'.PERMISSIONS_CATEGORY'] = 'Roles and access permissions';
734
        return $keys;
735
    }
736
}
737