Completed
Pull Request — master (#5741)
by Damian
12:40
created

Permission::get_label_for_permission()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 4
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 5
rs 9.4285
1
<?php
2
3
namespace SilverStripe\Security;
4
5
6
use SilverStripe\ORM\DB;
7
use SilverStripe\ORM\DataObject;
8
use SilverStripe\ORM\ArrayList;
9
use SilverStripe\ORM\SS_List;
10
use TemplateGlobalProvider;
11
use ClassInfo;
12
use TestOnly;
13
14
/**
15
 * Represents a permission assigned to a group.
16
 * @package framework
17
 * @subpackage security
18
 *
19
 * @property string Code
20
 * @property int Arg
21
 * @property int Type
22
 *
23
 * @property int GroupID
24
 *
25
 * @method Group Group()
26
 */
27
class Permission extends DataObject implements TemplateGlobalProvider {
28
29
	// the (1) after Type specifies the DB default value which is needed for
30
	// upgrades from older SilverStripe versions
31
	private static $db = array(
32
		"Code" => "Varchar(255)",
33
		"Arg" => "Int",
34
		"Type" => "Int(1)"
35
	);
36
37
	private static $has_one = array(
38
		"Group" => "SilverStripe\\Security\\Group"
39
	);
40
41
	private static $indexes = array(
42
		"Code" => true
43
	);
44
45
	private static $defaults = array(
46
		"Type" => 1
47
	);
48
49
	private static $table_name = "Permission";
50
51
	/**
52
	 * This is the value to use for the "Type" field if a permission should be
53
	 * granted.
54
	 */
55
	const GRANT_PERMISSION = 1;
56
57
	/**
58
	 * This is the value to use for the "Type" field if a permission should be
59
	 * denied.
60
	 */
61
	const DENY_PERMISSION = -1;
62
63
	/**
64
	 * This is the value to use for the "Type" field if a permission should be
65
	 * inherited.
66
	 */
67
	const INHERIT_PERMISSION = 0;
68
69
70
	/**
71
	 * Method to globally disable "strict" checking, which means a permission
72
	 * will be granted if the key does not exist at all.
73
	 *
74
	 * @var array
75
	 */
76
	private static $declared_permissions = null;
77
78
	/**
79
	 * Linear list of declared permissions in the system.
80
	 *
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,
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();
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(
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
		if(!$member) {
135
			if(!Member::currentUserID()) {
136
				return false;
137
			}
138
			$member = Member::currentUserID();
139
		}
140
141
		return self::checkMember($member, $code, $arg, $strict);
142
	}
143
144
	/**
145
	 * Permissions cache.  The format is a map, where the keys are member IDs, and the values are
146
	 * arrays of permission codes.
147
	 */
148
	private static $cache_permissions = array();
149
150
	/**
151
	 * Flush the permission cache, for example if you have edited group membership or a permission record.
152
	 * @todo Call this whenever Group_Members is added to or removed from
153
	 */
154
	public static function flush_permission_cache() {
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
		if(!$member) {
173
			$memberID = $member = Member::currentUserID();
174
		} else {
175
			$memberID = (is_object($member)) ? $member->ID : $member;
176
		}
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;
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
				}
204
				elseif (substr($permCode, 0, 11) === 'CMS_ACCESS_') {
205
					//cms_access_leftandmain means access to all CMS areas
206
					$code[] = 'CMS_ACCESS_LeftAndMain';
207
					break;
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);
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)) return false;
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) return $permission;
271
272
		// Strict checking disabled?
273
		if(!static::config()->strict_checking || !$strict) {
274
			$hasPermission = DB::prepared_query(
275
				"SELECT COUNT(*)
276
				FROM \"Permission\"
277
				WHERE (
278
					\"Code\" IN ($codeClause) AND
279
					\"Type\" = ?
280
				)",
281
				array_merge($codeParams, array(self::GRANT_PERMISSION))
282
			)->value();
283
284
			if(!$hasPermission) return false;
285
		}
286
287
		return false;
288
	}
289
290
	/**
291
	 * Get all the 'any' permission codes available to the given member.
292
	 *
293
	 * @param int $memberID
294
	 * @return array
295
	 */
296
	public static function permissions_for_member($memberID) {
297
		$groupList = self::groupList($memberID);
298
299
		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...
300
			$groupCSV = implode(", ", $groupList);
301
302
			$allowed = array_unique(DB::query("
303
				SELECT \"Code\"
304
				FROM \"Permission\"
305
				WHERE \"Type\" = " . self::GRANT_PERMISSION . " AND \"GroupID\" IN ($groupCSV)
306
307
				UNION
308
309
				SELECT \"Code\"
310
				FROM \"PermissionRoleCode\" PRC
311
				INNER JOIN \"PermissionRole\" PR ON PRC.\"RoleID\" = PR.\"ID\"
312
				INNER JOIN \"Group_Roles\" GR ON GR.\"PermissionRoleID\" = PR.\"ID\"
313
				WHERE \"GroupID\" IN ($groupCSV)
314
			")->column());
315
316
			$denied = array_unique(DB::query("
317
				SELECT \"Code\"
318
				FROM \"Permission\"
319
				WHERE \"Type\" = " . self::DENY_PERMISSION . " AND \"GroupID\" IN ($groupCSV)
320
			")->column());
321
322
			return array_diff($allowed, $denied);
323
		}
324
325
		return array();
326
	}
327
328
329
	/**
330
	 * Get the list of groups that the given member belongs to.
331
	 *
332
	 * Call without an argument to get the groups that the current member
333
	 * belongs to. In this case, the results will be session-cached.
334
	 *
335
	 * @param int $memberID The ID of the member. Leave blank for the current
336
	 *                      member.
337
	 * @return array Returns a list of group IDs to which the member belongs
338
	 *               to or NULL.
339
	 */
340
	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...
341
		// Default to current member, with session-caching
342
		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...
343
			$member = Member::currentUser();
344
			if($member && isset($_SESSION['Permission_groupList'][$member->ID]))
345
				return $_SESSION['Permission_groupList'][$member->ID];
346
		} else {
347
			$member = DataObject::get_by_id("SilverStripe\\Security\\Member", $memberID);
348
		}
349
350
		if($member) {
351
			// Build a list of the IDs of the groups.  Most of the heavy lifting
352
			// is done by Member::Groups
353
			// NOTE: This isn't effecient; but it's called once per session so
354
			// it's a low priority to fix.
355
			$groups = $member->Groups();
356
			$groupList = array();
357
358
			if($groups) {
359
				foreach($groups as $group)
360
					$groupList[] = $group->ID;
361
			}
362
363
364
			// Session caching
365
			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...
366
				$_SESSION['Permission_groupList'][$member->ID] = $groupList;
367
			}
368
369
			return isset($groupList) ? $groupList : null;
370
		}
371
	}
372
373
374
	/**
375
	 * Grant the given permission code/arg to the given group
376
	 *
377
	 * @param int $groupID The ID of the group
378
	 * @param string $code The permission code
379
	 * @param string $arg Optional: The permission argument (e.g. a page ID).
380
	 * @returns Permission Returns the new permission object.
381
	 */
382
	public static function grant($groupID, $code, $arg = "any") {
383
		$perm = new Permission();
384
		$perm->GroupID = $groupID;
385
		$perm->Code = $code;
386
		$perm->Type = self::GRANT_PERMISSION;
387
388
		// Arg component
389
		switch($arg) {
390
			case "any":
391
				break;
392
			case "all":
393
				$perm->Arg = -1;
394
				break;
395
			default:
396
				if(is_numeric($arg)) {
397
					$perm->Arg = $arg;
398
				} else {
399
					user_error("Permission::checkMember: bad arg '$arg'",
400
										E_USER_ERROR);
401
				}
402
		}
403
404
		$perm->write();
405
		return $perm;
406
	}
407
408
409
	/**
410
	 * Deny the given permission code/arg to the given group
411
	 *
412
	 * @param int $groupID The ID of the group
413
	 * @param string $code The permission code
414
	 * @param string $arg Optional: The permission argument (e.g. a page ID).
415
	 * @returns Permission Returns the new permission object.
416
	 */
417
	public static function deny($groupID, $code, $arg = "any") {
418
		$perm = new Permission();
419
		$perm->GroupID = $groupID;
420
		$perm->Code = $code;
421
		$perm->Type = self::DENY_PERMISSION;
422
423
		// Arg component
424
		switch($arg) {
425
			case "any":
426
				break;
427
			case "all":
428
				$perm->Arg = -1;
429
				break;
430
			default:
431
				if(is_numeric($arg)) {
432
					$perm->Arg = $arg;
433
				} else {
434
					user_error("Permission::checkMember: bad arg '$arg'",
435
										E_USER_ERROR);
436
				}
437
		}
438
439
		$perm->write();
440
		return $perm;
441
	}
442
443
	/**
444
	 * Returns all members for a specific permission.
445
	 *
446
	 * @param $code String|array Either a single permission code, or a list of permission codes
447
	 * @return SS_List Returns a set of member that have the specified
448
	 *                       permission.
449
	 */
450
	public static function get_members_by_permission($code) {
451
		$toplevelGroups = self::get_groups_by_permission($code);
452
		if (!$toplevelGroups) return new ArrayList();
453
454
		$groupIDs = array();
455
		foreach($toplevelGroups as $group) {
456
			$familyIDs = $group->collateFamilyIDs();
457
			if(is_array($familyIDs)) {
458
				$groupIDs = array_merge($groupIDs, array_values($familyIDs));
459
			}
460
		}
461
462
		if(empty($groupIDs)) return new ArrayList();
463
464
		$groupClause = DB::placeholders($groupIDs);
465
		/** @skipUpgrade */
466
		$members = Member::get()
467
			->where(array("\"Group\".\"ID\" IN ($groupClause)" => $groupIDs))
468
			->leftJoin("Group_Members", '"Member"."ID" = "Group_Members"."MemberID"')
469
			->leftJoin("Group", '"Group_Members"."GroupID" = "Group"."ID"');
470
471
		return $members;
472
	}
473
474
	/**
475
	 * Return all of the groups that have one of the given permission codes
476
	 * @param array|string $codes Either a single permission code, or an array of permission codes
477
	 * @return SS_List The matching group objects
478
	 */
479
	public static function get_groups_by_permission($codes) {
480
		$codeParams = is_array($codes) ? $codes : array($codes);
481
		$codeClause = DB::placeholders($codeParams);
482
483
		// Via Roles are groups that have the permission via a role
484
		/** @skipUpgrade */
485
		return Group::get()
486
			->where(array(
487
				"\"PermissionRoleCode\".\"Code\" IN ($codeClause) OR \"Permission\".\"Code\" IN ($codeClause)"
488
				=> array_merge($codeParams, $codeParams)
489
			))
490
			->leftJoin('Permission', "\"Permission\".\"GroupID\" = \"Group\".\"ID\"")
491
			->leftJoin('Group_Roles', "\"Group_Roles\".\"GroupID\" = \"Group\".\"ID\"")
492
			->leftJoin('PermissionRole', "\"Group_Roles\".\"PermissionRoleID\" = \"PermissionRole\".\"ID\"")
493
			->leftJoin('PermissionRoleCode', "\"PermissionRoleCode\".\"RoleID\" = \"PermissionRole\".\"ID\"");
494
	}
495
496
497
	/**
498
	 * Get a list of all available permission codes, both defined through the
499
	 * {@link PermissionProvider} interface, and all not explicitly defined codes existing
500
	 * as a {@link Permission} database record. By default, the results are
501
	 * grouped as denoted by {@link Permission_Group}.
502
	 *
503
	 * @param bool $grouped Group results into an array of permission groups.
504
	 * @return array Returns an array of all available permission codes. The
505
	 *  array indicies are the permission codes as used in
506
	 *  {@link Permission::check()}. The value is a description
507
	 *  suitable for using in an interface.
508
	 */
509
	public static function get_codes($grouped = true) {
510
		$classes = ClassInfo::implementorsOf('SilverStripe\\Security\\PermissionProvider');
511
512
		$allCodes = array();
513
		$adminCategory = _t('Permission.AdminGroup', 'Administrator');
514
		$allCodes[$adminCategory]['ADMIN'] = array(
515
			'name' => _t('Permission.FULLADMINRIGHTS', 'Full administrative rights'),
516
			'help' => _t(
517
				'Permission.FULLADMINRIGHTS_HELP',
518
				'Implies and overrules all other assigned permissions.'
519
			),
520
			'sort' => 100000
521
		);
522
523
		if($classes) foreach($classes as $class) {
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...
524
			$SNG = singleton($class);
525
			if($SNG instanceof TestOnly) continue;
526
527
			$someCodes = $SNG->providePermissions();
528
			if($someCodes) {
529
				foreach($someCodes as $k => $v) {
530
					if (is_array($v)) {
531
						// There must be a category and name key.
532
						if (!isset($v['category'])) user_error("The permission $k must have a category key",
533
							E_USER_WARNING);
534
						if (!isset($v['name'])) user_error("The permission $k must have a name key",
535
							E_USER_WARNING);
536
537
						if (!isset($allCodes[$v['category']])) $allCodes[$v['category']] = array();
538
539
						$allCodes[$v['category']][$k] = array(
540
							'name' => $v['name'],
541
							'help' => isset($v['help']) ? $v['help'] : null,
542
							'sort' => isset($v['sort']) ? $v['sort'] : 0
543
						);
544
545
					} else {
546
						$allCodes['Other'][$k] = array(
547
							'name' => $v,
548
							'help' => null,
549
							'sort' => 0
550
						);
551
					}
552
				}
553
			}
554
		}
555
556
		$flatCodeArray = array();
557
		foreach($allCodes as $category) foreach($category as $code => $permission) $flatCodeArray[] = $code;
558
		$otherPerms = DB::query("SELECT DISTINCT \"Code\" From \"Permission\" WHERE \"Code\" != ''")->column();
559
560
		if($otherPerms) foreach($otherPerms as $otherPerm) {
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...
561
			if(!in_array($otherPerm, $flatCodeArray))
562
				$allCodes['Other'][$otherPerm] = array(
563
					'name' => $otherPerm,
564
					'help' => null,
565
					'sort' => 0
566
				);
567
		}
568
569
		// Don't let people hijack ADMIN rights
570
		if(!Permission::check("ADMIN")) unset($allCodes['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...
571
572
		ksort($allCodes);
573
574
		$returnCodes = array();
575
		foreach($allCodes as $category => $permissions) {
576
			if($grouped) {
577
				uasort($permissions, array(__CLASS__, 'sort_permissions'));
578
				$returnCodes[$category] = $permissions;
579
			} else {
580
				$returnCodes = array_merge($returnCodes, $permissions);
581
			}
582
		}
583
584
		return $returnCodes;
585
	}
586
587
	/**
588
	 * Sort permissions based on their sort value, or name
589
	 *
590
	 * @param array $a
591
	 * @param array $b
592
	 * @return int
593
	 */
594
	public static function sort_permissions($a, $b) {
595
		if ($a['sort'] == $b['sort']) {
596
			// Same sort value, do alpha instead
597
			return strcmp($a['name'], $b['name']);
598
		} else {
599
			// Just numeric.
600
			return $a['sort'] < $b['sort'] ? -1 : 1;
601
		}
602
	}
603
604
	/**
605
	 * Get a linear list of the permissions in the system.
606
	 *
607
	 * @return array Linear list of declared permissions in the system.
608
	 */
609
	public static function get_declared_permissions_list() {
610
		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...
611
			return null;
612
613
		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...
614
			return self::$declared_permissions_list;
615
616
		self::$declared_permissions_list = array();
617
618
		self::traverse_declared_permissions(self::$declared_permissions, self::$declared_permissions_list);
619
620
		return self::$declared_permissions_list;
621
	}
622
623
	/**
624
	 * Look up the human-readable title for the permission as defined by <code>Permission::declare_permissions</code>
625
	 *
626
	 * @param string $perm Permission code
627
	 * @return string Label for the given permission, or the permission itself if the label doesn't exist
628
	 */
629
	public static function get_label_for_permission($perm) {
630
		$list = self::get_declared_permissions_list();
631
		if(array_key_exists($perm, $list)) return $list[$perm];
632
		return $perm;
633
	}
634
635
	/**
636
	 * Recursively traverse the nested list of declared permissions and create
637
	 * a linear list.
638
	 *
639
	 * @param array $declared Nested structure of permissions.
640
	 * @param array $list List of permissions in the structure. The result will be
641
	 *              written to this array.
642
	 */
643
	protected static function traverse_declared_permissions($declared, &$list) {
644
		if(!is_array($declared))
645
			return;
646
647
		foreach($declared as $perm => $value) {
648
			if($value instanceof Permission_Group) {
649
				$list[] = $value->getName();
650
				self::traverse_declared_permissions($value->getPermissions(), $list);
651
			} else {
652
				$list[$perm] = $value;
653
			}
654
		}
655
	}
656
657
	public function onBeforeWrite() {
658
		parent::onBeforeWrite();
659
660
		// Just in case we've altered someone's permissions
661
		Permission::flush_permission_cache();
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...
662
	}
663
664
	public static function get_template_global_variables() {
665
		return array(
666
			'HasPerm' => 'check'
667
		);
668
	}
669
}
670
671
672
/**
673
 * Permission_Group class
674
 *
675
 * This class is used to group permissions together for showing on an
676
 * interface.
677
 * @package framework
678
 * @subpackage security
679
 */
680
class Permission_Group {
681
682
	/**
683
	 * Name of the permission group (can be used as label in an interface)
684
	 * @var string
685
	 */
686
	protected $name;
687
688
	/**
689
	 * Associative array of permissions in this permission group. The array
690
	 * indicies are the permission codes as used in
691
	 * {@link Permission::check()}. The value is suitable for using in an
692
	 * interface.
693
	 * @var string
694
	 */
695
	protected $permissions = array();
696
697
698
	/**
699
	 * Constructor
700
	 *
701
	 * @param string $name Text that could be used as label used in an
702
	 *                     interface
703
	 * @param array $permissions Associative array of permissions in this
704
	 *                           permission group. The array indicies are the
705
	 *                           permission codes as used in
706
	 *                           {@link Permission::check()}. The value is
707
	 *                           suitable for using in an interface.
708
	 */
709
	public function __construct($name, $permissions) {
710
		$this->name = $name;
711
		$this->permissions = $permissions;
0 ignored issues
show
Documentation Bug introduced by
It seems like $permissions of type array is incompatible with the declared type string of property $permissions.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
712
	}
713
714
	/**
715
	 * Get the name of the permission group
716
	 *
717
	 * @return string Name (label) of the permission group
718
	 */
719
	public function getName() {
720
		return $this->name;
721
	}
722
723
724
	/**
725
	 * Get permissions
726
	 *
727
	 * @return array Associative array of permissions in this permission
728
	 *               group. The array indicies are the permission codes as
729
	 *               used in {@link Permission::check()}. The value is
730
	 *               suitable for using in an interface.
731
	 */
732
	public function getPermissions() {
733
		return $this->permissions;
734
	}
735
}
736
737
738