Index: trunk/phase3/RELEASE-NOTES-1.19 |
— | — | @@ -29,6 +29,7 @@ |
30 | 30 | hooks have been removed. |
31 | 31 | * New hook "Collation::factory" to allow extensions to create custom |
32 | 32 | category collations. |
| 33 | +* $wgGroupPermissions now supports per namespace permissions. |
33 | 34 | |
34 | 35 | === New features in 1.19 === |
35 | 36 | * BREAKING CHANGE: action=watch / action=unwatch now requires a token. |
Index: trunk/phase3/tests/phpunit/includes/ArticleTablesTest.php |
— | — | @@ -11,7 +11,7 @@ |
12 | 12 | $title = Title::newFromText("Bug 14404"); |
13 | 13 | $article = new Article( $title ); |
14 | 14 | $wgUser = new User(); |
15 | | - $wgUser->mRights = array( 'createpage', 'edit', 'purge' ); |
| 15 | + $wgUser->mRights['*'] = array( 'createpage', 'edit', 'purge' ); |
16 | 16 | $wgLanguageCode = 'es'; |
17 | 17 | $wgContLang = Language::factory( 'es' ); |
18 | 18 | |
Index: trunk/phase3/tests/phpunit/includes/UserTest.php |
— | — | @@ -1,7 +1,11 @@ |
2 | 2 | <?php |
3 | 3 | |
| 4 | +define( 'NS_UNITTEST', 5600 ); |
| 5 | +define( 'NS_UNITTEST_TALK', 5601 ); |
| 6 | + |
4 | 7 | class UserTest extends MediaWikiTestCase { |
5 | 8 | protected $savedGroupPermissions, $savedRevokedPermissions; |
| 9 | + protected $user; |
6 | 10 | |
7 | 11 | public function setUp() { |
8 | 12 | parent::setUp(); |
— | — | @@ -10,24 +14,40 @@ |
11 | 15 | $this->savedRevokedPermissions = $GLOBALS['wgRevokePermissions']; |
12 | 16 | |
13 | 17 | $this->setUpPermissionGlobals(); |
| 18 | + $this->setUpUser(); |
14 | 19 | } |
15 | 20 | private function setUpPermissionGlobals() { |
16 | 21 | global $wgGroupPermissions, $wgRevokePermissions; |
17 | 22 | |
| 23 | + # Data for regular $wgGroupPermissions test |
18 | 24 | $wgGroupPermissions['unittesters'] = array( |
| 25 | + 'test' => true, |
19 | 26 | 'runtest' => true, |
20 | 27 | 'writetest' => false, |
21 | 28 | 'nukeworld' => false, |
22 | 29 | ); |
23 | 30 | $wgGroupPermissions['testwriters'] = array( |
| 31 | + 'test' => true, |
24 | 32 | 'writetest' => true, |
25 | 33 | 'modifytest' => true, |
26 | 34 | ); |
27 | | - |
| 35 | + # Data for regular $wgRevokePermissions test |
28 | 36 | $wgRevokePermissions['formertesters'] = array( |
29 | 37 | 'runtest' => true, |
30 | 38 | ); |
| 39 | + |
| 40 | + # Data for namespace based $wgGroupPermissions test |
| 41 | + $wgGroupPermissions['unittesters']['writedocumentation'] = array( |
| 42 | + NS_MAIN => false, NS_UNITTEST => true, |
| 43 | + ); |
| 44 | + $wgGroupPermissions['testwriters']['writedocumentation'] = true; |
| 45 | + |
31 | 46 | } |
| 47 | + private function setUpUser() { |
| 48 | + $this->user = new User; |
| 49 | + $this->user->addGroup( 'unittesters' ); |
| 50 | + } |
| 51 | + |
32 | 52 | public function tearDown() { |
33 | 53 | parent::tearDown(); |
34 | 54 | |
— | — | @@ -55,4 +75,90 @@ |
56 | 76 | $this->assertNotContains( 'modifytest', $rights ); |
57 | 77 | $this->assertNotContains( 'nukeworld', $rights ); |
58 | 78 | } |
| 79 | + |
| 80 | + public function testNamespaceGroupPermissions() { |
| 81 | + $rights = User::getGroupPermissions( array( 'unittesters' ) ); |
| 82 | + $this->assertNotContains( 'writedocumentation', $rights ); |
| 83 | + |
| 84 | + $rights = User::getGroupPermissions( array( 'unittesters' ) , NS_MAIN ); |
| 85 | + $this->assertNotContains( 'writedocumentation', $rights ); |
| 86 | + $this->assertNotContains( 'modifytest', $rights ); |
| 87 | + |
| 88 | + $rights = User::getGroupPermissions( array( 'unittesters' ), NS_HELP ); |
| 89 | + $this->assertNotContains( 'writedocumentation', $rights ); |
| 90 | + $this->assertNotContains( 'modifytest', $rights ); |
| 91 | + |
| 92 | + $rights = User::getGroupPermissions( array( 'unittesters' ), NS_UNITTEST ); |
| 93 | + $this->assertContains( 'writedocumentation', $rights ); |
| 94 | + |
| 95 | + $rights = User::getGroupPermissions( |
| 96 | + array( 'unittesters', 'testwriters' ), NS_MAIN ); |
| 97 | + $this->assertContains( 'writedocumentation', $rights ); |
| 98 | + } |
| 99 | + |
| 100 | + public function testUserPermissions() { |
| 101 | + $rights = $this->user->getRights(); |
| 102 | + $this->assertContains( 'runtest', $rights ); |
| 103 | + $this->assertNotContains( 'writetest', $rights ); |
| 104 | + $this->assertNotContains( 'modifytest', $rights ); |
| 105 | + $this->assertNotContains( 'nukeworld', $rights ); |
| 106 | + $this->assertNotContains( 'writedocumentation', $rights ); |
| 107 | + |
| 108 | + $rights = $this->user->getRights( NS_MAIN ); |
| 109 | + $this->assertNotContains( 'writedocumentation', $rights ); |
| 110 | + $this->assertNotContains( 'modifytest', $rights ); |
| 111 | + |
| 112 | + $rights = $this->user->getRights( NS_HELP ); |
| 113 | + $this->assertNotContains( 'writedocumentation', $rights ); |
| 114 | + $this->assertNotContains( 'modifytest', $rights ); |
| 115 | + |
| 116 | + $rights = $this->user->getRights( NS_UNITTEST ); |
| 117 | + $this->assertContains( 'writedocumentation', $rights ); |
| 118 | + } |
| 119 | + |
| 120 | + /** |
| 121 | + * @dataProvider provideGetGroupsWithPermission |
| 122 | + */ |
| 123 | + public function testGetGroupsWithPermission( $expected, $right, $ns ) { |
| 124 | + $result = User::getGroupsWithPermission( $right, $ns ); |
| 125 | + sort( $result ); |
| 126 | + sort( $expected ); |
| 127 | + |
| 128 | + $this->assertEquals( $expected, $result, "Groups with permission $right" . |
| 129 | + ( is_null( $ns ) ? '' : "in namespace $ns" ) ); |
| 130 | + } |
| 131 | + public function provideGetGroupsWithPermission() { |
| 132 | + return array( |
| 133 | + array( |
| 134 | + array( 'unittesters', 'testwriters' ), |
| 135 | + 'test', |
| 136 | + null |
| 137 | + ), |
| 138 | + array( |
| 139 | + array( 'unittesters' ), |
| 140 | + 'runtest', |
| 141 | + null |
| 142 | + ), |
| 143 | + array( |
| 144 | + array( 'testwriters' ), |
| 145 | + 'writetest', |
| 146 | + null |
| 147 | + ), |
| 148 | + array( |
| 149 | + array( 'testwriters' ), |
| 150 | + 'modifytest', |
| 151 | + null |
| 152 | + ), |
| 153 | + array( |
| 154 | + array( 'testwriters' ), |
| 155 | + 'writedocumentation', |
| 156 | + NS_MAIN |
| 157 | + ), |
| 158 | + array( |
| 159 | + array( 'unittesters', 'testwriters' ), |
| 160 | + 'writedocumentation', |
| 161 | + NS_UNITTEST |
| 162 | + ), |
| 163 | + ); |
| 164 | + } |
59 | 165 | } |
\ No newline at end of file |
Index: trunk/phase3/tests/phpunit/includes/TitlePermissionTest.php |
— | — | @@ -56,11 +56,17 @@ |
57 | 57 | } |
58 | 58 | |
59 | 59 | function setUserPerm( $perm ) { |
60 | | - if ( is_array( $perm ) ) { |
61 | | - $this->user->mRights = $perm; |
62 | | - } else { |
63 | | - $this->user->mRights = array( $perm ); |
| 60 | + // Setting member variables is evil!!! |
| 61 | + |
| 62 | + if ( !is_array( $perm ) ) { |
| 63 | + $perm = array( $perm ); |
64 | 64 | } |
| 65 | + for ($i = 0; $i < 100; $i++) { |
| 66 | + $this->user->mRights[$i] = $perm; |
| 67 | + } |
| 68 | + |
| 69 | + // Hack, hack hack ... |
| 70 | + $this->user->mRights['*'] = $perm; |
65 | 71 | } |
66 | 72 | |
67 | 73 | function setTitle( $ns, $title = "Main_Page" ) { |
Index: trunk/phase3/includes/User.php |
— | — | @@ -2240,16 +2240,29 @@ |
2241 | 2241 | |
2242 | 2242 | /** |
2243 | 2243 | * Get the permissions this user has. |
| 2244 | + * @param $ns int If numeric, get permissions for this namespace |
2244 | 2245 | * @return Array of String permission names |
2245 | 2246 | */ |
2246 | | - function getRights() { |
| 2247 | + function getRights( $ns = null ) { |
| 2248 | + $key = is_null( $ns ) ? '*' : intval( $ns ); |
| 2249 | + |
2247 | 2250 | if ( is_null( $this->mRights ) ) { |
2248 | | - $this->mRights = self::getGroupPermissions( $this->getEffectiveGroups() ); |
2249 | | - wfRunHooks( 'UserGetRights', array( $this, &$this->mRights ) ); |
| 2251 | + $this->mRights = array(); |
| 2252 | + } |
| 2253 | + |
| 2254 | + if ( !isset( $this->mRights[$key] ) ) { |
| 2255 | + $this->mRights[$key] = self::getGroupPermissions( $this->getEffectiveGroups(), $ns ); |
| 2256 | + wfRunHooks( 'UserGetRights', array( $this, &$this->mRights[$key], $ns ) ); |
2250 | 2257 | // Force reindexation of rights when a hook has unset one of them |
2251 | | - $this->mRights = array_values( $this->mRights ); |
| 2258 | + $this->mRights[$key] = array_values( $this->mRights[$key] ); |
2252 | 2259 | } |
2253 | | - return $this->mRights; |
| 2260 | + if ( is_null( $ns ) ) { |
| 2261 | + return $this->mRights[$key]; |
| 2262 | + } else { |
| 2263 | + // Merge non namespace specific rights |
| 2264 | + return array_merge( $this->mRights[$key], $this->getRights() ); |
| 2265 | + } |
| 2266 | + |
2254 | 2267 | } |
2255 | 2268 | |
2256 | 2269 | /** |
— | — | @@ -2351,7 +2364,7 @@ |
2352 | 2365 | } |
2353 | 2366 | $this->loadGroups(); |
2354 | 2367 | $this->mGroups[] = $group; |
2355 | | - $this->mRights = User::getGroupPermissions( $this->getEffectiveGroups( true ) ); |
| 2368 | + $this->mRights = null; |
2356 | 2369 | |
2357 | 2370 | $this->invalidateCache(); |
2358 | 2371 | } |
— | — | @@ -2381,7 +2394,7 @@ |
2382 | 2395 | } |
2383 | 2396 | $this->loadGroups(); |
2384 | 2397 | $this->mGroups = array_diff( $this->mGroups, array( $group ) ); |
2385 | | - $this->mRights = User::getGroupPermissions( $this->getEffectiveGroups( true ) ); |
| 2398 | + $this->mRights = null; |
2386 | 2399 | |
2387 | 2400 | $this->invalidateCache(); |
2388 | 2401 | } |
— | — | @@ -2434,9 +2447,10 @@ |
2435 | 2448 | /** |
2436 | 2449 | * Internal mechanics of testing a permission |
2437 | 2450 | * @param $action String |
| 2451 | + * @paramn $ns int Namespace optional |
2438 | 2452 | * @return bool |
2439 | 2453 | */ |
2440 | | - public function isAllowed( $action = '' ) { |
| 2454 | + public function isAllowed( $action = '', $ns = null ) { |
2441 | 2455 | if ( $action === '' ) { |
2442 | 2456 | return true; // In the spirit of DWIM |
2443 | 2457 | } |
— | — | @@ -2448,7 +2462,7 @@ |
2449 | 2463 | } |
2450 | 2464 | # Use strict parameter to avoid matching numeric 0 accidentally inserted |
2451 | 2465 | # by misconfiguration: 0 == 'foo' |
2452 | | - return in_array( $action, $this->getRights(), true ); |
| 2466 | + return in_array( $action, $this->getRights( $ns ), true ); |
2453 | 2467 | } |
2454 | 2468 | |
2455 | 2469 | /** |
— | — | @@ -3397,26 +3411,51 @@ |
3398 | 3412 | * @param $groups Array of Strings List of internal group names |
3399 | 3413 | * @return Array of Strings List of permission key names for given groups combined |
3400 | 3414 | */ |
3401 | | - static function getGroupPermissions( $groups ) { |
| 3415 | + static function getGroupPermissions( $groups, $ns = null ) { |
3402 | 3416 | global $wgGroupPermissions, $wgRevokePermissions; |
3403 | 3417 | $rights = array(); |
3404 | | - // grant every granted permission first |
| 3418 | + |
| 3419 | + // Grant every granted permission first |
3405 | 3420 | foreach( $groups as $group ) { |
3406 | 3421 | if( isset( $wgGroupPermissions[$group] ) ) { |
3407 | | - $rights = array_merge( $rights, |
3408 | | - // array_filter removes empty items |
3409 | | - array_keys( array_filter( $wgGroupPermissions[$group] ) ) ); |
| 3422 | + $rights = array_merge( $rights, self::extractRights( |
| 3423 | + $wgGroupPermissions[$group], $ns ) ); |
3410 | 3424 | } |
3411 | 3425 | } |
3412 | | - // now revoke the revoked permissions |
| 3426 | + |
| 3427 | + // Revoke the revoked permissions |
3413 | 3428 | foreach( $groups as $group ) { |
3414 | 3429 | if( isset( $wgRevokePermissions[$group] ) ) { |
3415 | | - $rights = array_diff( $rights, |
3416 | | - array_keys( array_filter( $wgRevokePermissions[$group] ) ) ); |
| 3430 | + $rights = array_diff( $rights, self::extractRights( |
| 3431 | + $wgRevokePermissions[$group], $ns ) ); |
3417 | 3432 | } |
3418 | 3433 | } |
3419 | 3434 | return array_unique( $rights ); |
3420 | 3435 | } |
| 3436 | + |
| 3437 | + /** |
| 3438 | + * Helper for User::getGroupPermissions |
| 3439 | + * @param array $list |
| 3440 | + * @param int $ns |
| 3441 | + * @return array |
| 3442 | + */ |
| 3443 | + private static function extractRights( $list, $ns ) { |
| 3444 | + $rights = array(); |
| 3445 | + foreach( $list as $right => $value ) { |
| 3446 | + if ( is_array( $value ) ) { |
| 3447 | + # This is a list of namespaces where the permission applies |
| 3448 | + if ( !is_null( $ns ) && !empty( $value[$ns] ) ) { |
| 3449 | + $rights[] = $right; |
| 3450 | + } |
| 3451 | + } else { |
| 3452 | + # This is a boolean indicating that the permission applies |
| 3453 | + if ( $value ) { |
| 3454 | + $rights[] = $right; |
| 3455 | + } |
| 3456 | + } |
| 3457 | + } |
| 3458 | + return $rights; |
| 3459 | + } |
3421 | 3460 | |
3422 | 3461 | /** |
3423 | 3462 | * Get all the groups who have a given permission |
— | — | @@ -3424,11 +3463,11 @@ |
3425 | 3464 | * @param $role String Role to check |
3426 | 3465 | * @return Array of Strings List of internal group names with the given permission |
3427 | 3466 | */ |
3428 | | - static function getGroupsWithPermission( $role ) { |
| 3467 | + static function getGroupsWithPermission( $role, $ns = null ) { |
3429 | 3468 | global $wgGroupPermissions; |
3430 | 3469 | $allowedGroups = array(); |
3431 | 3470 | foreach ( $wgGroupPermissions as $group => $rights ) { |
3432 | | - if ( isset( $rights[$role] ) && $rights[$role] ) { |
| 3471 | + if ( in_array( $role, self::getGroupPermissions( array( $group ), $ns ), true ) ) { |
3433 | 3472 | $allowedGroups[] = $group; |
3434 | 3473 | } |
3435 | 3474 | } |
Index: trunk/phase3/includes/Title.php |
— | — | @@ -1239,34 +1239,33 @@ |
1240 | 1240 | * @return Array list of errors |
1241 | 1241 | */ |
1242 | 1242 | private function checkQuickPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) { |
| 1243 | + $ns = $this->getNamespace(); |
| 1244 | + |
1243 | 1245 | if ( $action == 'create' ) { |
1244 | | - if ( ( $this->isTalkPage() && !$user->isAllowed( 'createtalk' ) ) || |
1245 | | - ( !$this->isTalkPage() && !$user->isAllowed( 'createpage' ) ) ) { |
| 1246 | + if ( ( $this->isTalkPage() && !$user->isAllowed( 'createtalk', $ns ) ) || |
| 1247 | + ( !$this->isTalkPage() && !$user->isAllowed( 'createpage', $ns ) ) ) { |
1246 | 1248 | $errors[] = $user->isAnon() ? array( 'nocreatetext' ) : array( 'nocreate-loggedin' ); |
1247 | 1249 | } |
1248 | 1250 | } elseif ( $action == 'move' ) { |
1249 | | - if ( !$user->isAllowed( 'move-rootuserpages' ) |
1250 | | - && $this->mNamespace == NS_USER && !$this->isSubpage() ) { |
| 1251 | + if ( !$user->isAllowed( 'move-rootuserpages', $ns ) |
| 1252 | + && $ns == NS_USER && !$this->isSubpage() ) { |
1251 | 1253 | // Show user page-specific message only if the user can move other pages |
1252 | 1254 | $errors[] = array( 'cant-move-user-page' ); |
1253 | 1255 | } |
1254 | 1256 | |
1255 | 1257 | // Check if user is allowed to move files if it's a file |
1256 | | - if ( $this->mNamespace == NS_FILE && !$user->isAllowed( 'movefile' ) ) { |
| 1258 | + if ( $ns == NS_FILE && !$user->isAllowed( 'movefile', $ns ) ) { |
1257 | 1259 | $errors[] = array( 'movenotallowedfile' ); |
1258 | 1260 | } |
1259 | 1261 | |
1260 | | - if ( !$user->isAllowed( 'move' ) ) { |
| 1262 | + if ( !$user->isAllowed( 'move', $ns) ) { |
1261 | 1263 | // User can't move anything |
1262 | | - global $wgGroupPermissions; |
1263 | | - $userCanMove = false; |
1264 | | - if ( isset( $wgGroupPermissions['user']['move'] ) ) { |
1265 | | - $userCanMove = $wgGroupPermissions['user']['move']; |
1266 | | - } |
1267 | | - $autoconfirmedCanMove = false; |
1268 | | - if ( isset( $wgGroupPermissions['autoconfirmed']['move'] ) ) { |
1269 | | - $autoconfirmedCanMove = $wgGroupPermissions['autoconfirmed']['move']; |
1270 | | - } |
| 1264 | + |
| 1265 | + $userCanMove = in_array( 'move', User::getGroupPermissions( |
| 1266 | + array( 'user' ), $ns ), true ); |
| 1267 | + $autoconfirmedCanMove = in_array( 'move', User::getGroupPermissions( |
| 1268 | + array( 'autoconfirmed' ), $ns ), true ); |
| 1269 | + |
1271 | 1270 | if ( $user->isAnon() && ( $userCanMove || $autoconfirmedCanMove ) ) { |
1272 | 1271 | // custom message if logged-in users without any special rights can move |
1273 | 1272 | $errors[] = array( 'movenologintext' ); |
— | — | @@ -1275,20 +1274,20 @@ |
1276 | 1275 | } |
1277 | 1276 | } |
1278 | 1277 | } elseif ( $action == 'move-target' ) { |
1279 | | - if ( !$user->isAllowed( 'move' ) ) { |
| 1278 | + if ( !$user->isAllowed( 'move', $ns ) ) { |
1280 | 1279 | // User can't move anything |
1281 | 1280 | $errors[] = array( 'movenotallowed' ); |
1282 | | - } elseif ( !$user->isAllowed( 'move-rootuserpages' ) |
1283 | | - && $this->mNamespace == NS_USER && !$this->isSubpage() ) { |
| 1281 | + } elseif ( !$user->isAllowed( 'move-rootuserpages', $ns ) |
| 1282 | + && $ns == NS_USER && !$this->isSubpage() ) { |
1284 | 1283 | // Show user page-specific message only if the user can move other pages |
1285 | 1284 | $errors[] = array( 'cant-move-to-user-page' ); |
1286 | 1285 | } |
1287 | | - } elseif ( !$user->isAllowed( $action ) ) { |
| 1286 | + } elseif ( !$user->isAllowed( $action, $ns ) ) { |
1288 | 1287 | // We avoid expensive display logic for quickUserCan's and such |
1289 | 1288 | $groups = false; |
1290 | 1289 | if ( !$short ) { |
1291 | 1290 | $groups = array_map( array( 'User', 'makeGroupLinkWiki' ), |
1292 | | - User::getGroupsWithPermission( $action ) ); |
| 1291 | + User::getGroupsWithPermission( $action, $ns ) ); |
1293 | 1292 | } |
1294 | 1293 | |
1295 | 1294 | if ( $groups ) { |
— | — | @@ -1440,9 +1439,9 @@ |
1441 | 1440 | if ( $right == 'sysop' ) { |
1442 | 1441 | $right = 'protect'; |
1443 | 1442 | } |
1444 | | - if ( $right != '' && !$user->isAllowed( $right ) ) { |
| 1443 | + if ( $right != '' && !$user->isAllowed( $right, $this->mNamespace ) ) { |
1445 | 1444 | // Users with 'editprotected' permission can edit protected pages |
1446 | | - if ( $action == 'edit' && $user->isAllowed( 'editprotected' ) ) { |
| 1445 | + if ( $action == 'edit' && $user->isAllowed( 'editprotected', $this->mNamespace ) ) { |
1447 | 1446 | // Users with 'editprotected' permission cannot edit protected pages |
1448 | 1447 | // with cascading option turned on. |
1449 | 1448 | if ( $this->mCascadeRestriction ) { |
— | — | @@ -1483,7 +1482,7 @@ |
1484 | 1483 | if ( isset( $restrictions[$action] ) ) { |
1485 | 1484 | foreach ( $restrictions[$action] as $right ) { |
1486 | 1485 | $right = ( $right == 'sysop' ) ? 'protect' : $right; |
1487 | | - if ( $right != '' && !$user->isAllowed( $right ) ) { |
| 1486 | + if ( $right != '' && !$user->isAllowed( $right, $this->mNamespace ) ) { |
1488 | 1487 | $pages = ''; |
1489 | 1488 | foreach ( $cascadingSources as $page ) |
1490 | 1489 | $pages .= '* [[:' . $page->getPrefixedText() . "]]\n"; |
— | — | @@ -1519,7 +1518,9 @@ |
1520 | 1519 | if( $title_protection['pt_create_perm'] == 'sysop' ) { |
1521 | 1520 | $title_protection['pt_create_perm'] = 'protect'; // B/C |
1522 | 1521 | } |
1523 | | - if( $title_protection['pt_create_perm'] == '' || !$user->isAllowed( $title_protection['pt_create_perm'] ) ) { |
| 1522 | + if( $title_protection['pt_create_perm'] == '' || |
| 1523 | + !$user->isAllowed( $title_protection['pt_create_perm'], |
| 1524 | + $this->mNamespace ) ) { |
1524 | 1525 | $errors[] = array( 'titleprotected', User::whoIs( $title_protection['pt_user'] ), $title_protection['pt_reason'] ); |
1525 | 1526 | } |
1526 | 1527 | } |
Index: trunk/phase3/includes/DefaultSettings.php |
— | — | @@ -3304,6 +3304,10 @@ |
3305 | 3305 | * unable to perform certain essential tasks or access new functionality |
3306 | 3306 | * when new permissions are introduced and default grants established. |
3307 | 3307 | * |
| 3308 | + * If set to an array instead of a boolean, it is assumed that the array is in |
| 3309 | + * NS => bool form in order to support per-namespace permissions. Note that |
| 3310 | + * this feature does not fully work for all permission types. |
| 3311 | + * |
3308 | 3312 | * Functionality to make pages inaccessible has not been extensively tested |
3309 | 3313 | * for security. Use at your own risk! |
3310 | 3314 | * |