Improve UI for page protection with $wgNamespaceProtection
authorBrad Jorsch <bjorsch@wikimedia.org>
Fri, 28 Jun 2013 20:49:47 +0000 (16:49 -0400)
committerTim Starling <tstarling@wikimedia.org>
Tue, 3 Dec 2013 22:27:28 +0000 (22:27 +0000)
Right now, if you set $wgNamespaceProtection, the protection interface
is confusing: it will allow you to apply "useless" protection levels for
any namespace except MediaWiki, where it will refuse to let you set any
protection at all.

The fix is to find which restriction levels are more restrictive than
the $wgNamespaceProtection restriction (i.e. where there is at least one
group that can pass $wgNamespaceProtection but not the level from
$wgRestrictionLevels), and use only those in the protection form. If
there are no such levels, we can skip showing the "protect" tab
entirely.

Change-Id: I9e2b29ade566abcd008ea2ad1e2f9818e315bb32

RELEASE-NOTES-1.23
includes/EditPage.php
includes/Namespace.php
includes/ProtectionForm.php
includes/SkinTemplate.php

index cf1678e..4697b4d 100644 (file)
@@ -39,6 +39,11 @@ production.
   hide their (unrelated) log entries.
 * Added $wgOpenSearchDefaultLimit defining the default number of entries to show
   on action=opensearch API call.
+* For namespaces with $wgNamespaceProtection (including the MediaWiki
+  namespace), the "protect" tab will be shown only if there are restriction
+  levels available that would restrict editing beyond what
+  $wgNamespaceProtection already applies. The protection form will offer only
+  those protection levels.
 
 === Bug fixes in 1.23 ===
 * (bug 41759) The "updated since last visit" markers (on history pages, recent
index c0e7ed2..f86cae4 100644 (file)
@@ -2476,7 +2476,9 @@ class EditPage {
                        }
                }
 
-               if ( $this->mTitle->getNamespace() != NS_MEDIAWIKI && $this->mTitle->isProtected( 'edit' ) ) {
+               if ( $this->mTitle->isProtected( 'edit' ) &&
+                       MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace() ) !== array( '' )
+               ) {
                        # Is the title semi-protected?
                        if ( $this->mTitle->isSemiProtected() ) {
                                $noticeMsg = 'semiprotectedpagewarning';
@@ -2678,7 +2680,9 @@ HTML
                        $attribs = array( 'style' => 'display:none;' );
                } else {
                        $classes = array(); // Textarea CSS
-                       if ( $this->mTitle->getNamespace() != NS_MEDIAWIKI && $this->mTitle->isProtected( 'edit' ) ) {
+                       if ( $this->mTitle->isProtected( 'edit' ) &&
+                               MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace() ) !== array( '' )
+                       ) {
                                # Is the title semi-protected?
                                if ( $this->mTitle->isSemiProtected() ) {
                                        $classes[] = 'mw-textarea-sprotected';
index 5c8e63b..ce585ce 100644 (file)
@@ -433,4 +433,74 @@ class MWNamespace {
                        ? $wgNamespaceContentModels[$index]
                        : null;
        }
+
+       /**
+        * Determine which restriction levels it makes sense to use in a namespace,
+        * optionally filtered by a user's rights.
+        *
+        * @since 1.23
+        * @param int $index Index to check
+        * @param User $user User to check
+        * @return array
+        */
+       public static function getRestrictionLevels( $index, User $user = null ) {
+               global $wgNamespaceProtection, $wgRestrictionLevels;
+
+               if ( !isset( $wgNamespaceProtection[$index] ) ) {
+                       // All levels are valid if there's no namespace restriction.
+                       // But still filter by user, if necessary
+                       $levels = $wgRestrictionLevels;
+                       if ( $user ) {
+                               $levels = array_values( array_filter( $levels, function ( $level ) use ( $user ) {
+                                       $right = $level;
+                                       if ( $right == 'sysop' ) {
+                                               $right = 'editprotected'; // BC
+                                       }
+                                       if ( $right == 'autoconfirmed' ) {
+                                               $right = 'editsemiprotected'; // BC
+                                       }
+                                       return ( $right == '' || $user->isAllowed( $right ) );
+                               } ) );
+                       }
+                       return $levels;
+               }
+
+               // First, get the list of groups that can edit this namespace.
+               $namespaceGroups = array();
+               $combine = 'array_merge';
+               foreach ( (array)$wgNamespaceProtection[$index] as $right ) {
+                       if ( $right == 'sysop' ) {
+                               $right = 'editprotected'; // BC
+                       }
+                       if ( $right == 'autoconfirmed' ) {
+                               $right = 'editsemiprotected'; // BC
+                       }
+                       if ( $right != '' ) {
+                               $namespaceGroups = call_user_func( $combine, $namespaceGroups,
+                                       User::getGroupsWithPermission( $right ) );
+                               $combine = 'array_intersect';
+                       }
+               }
+
+               // Now, keep only those restriction levels where there is at least one
+               // group that can edit the namespace but would be blocked by the
+               // restriction.
+               $usableLevels = array( '' );
+               foreach ( $wgRestrictionLevels as $level ) {
+                       $right = $level;
+                       if ( $right == 'sysop' ) {
+                               $right = 'editprotected'; // BC
+                       }
+                       if ( $right == 'autoconfirmed' ) {
+                               $right = 'editsemiprotected'; // BC
+                       }
+                       if ( $right != '' && ( !$user || $user->isAllowed( $right ) ) &&
+                               array_diff( $namespaceGroups, User::getGroupsWithPermission( $right ) )
+                       ) {
+                               $usableLevels[] = $level;
+                       }
+               }
+
+               return $usableLevels;
+       }
 }
index f10317a..d6cc8a3 100644 (file)
@@ -83,8 +83,8 @@ class ProtectionForm {
         */
        function loadData() {
                global $wgRequest, $wgUser;
-               global $wgRestrictionLevels;
 
+               $levels = MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace(), $wgUser );
                $this->mCascade = $this->mTitle->areRestrictionsCascading();
 
                $this->mReason = $wgRequest->getText( 'mwProtect-reason' );
@@ -132,21 +132,7 @@ class ProtectionForm {
                        }
 
                        $val = $wgRequest->getVal( "mwProtect-level-$action" );
-                       if ( isset( $val ) && in_array( $val, $wgRestrictionLevels ) ) {
-                               // Prevent users from setting levels that they cannot later unset
-                               if ( $val == 'sysop' ) {
-                                       // Special case, rewrite sysop to editprotected
-                                       if ( !$wgUser->isAllowed( 'editprotected' ) ) {
-                                               continue;
-                                       }
-                               } elseif ( $val == 'autoconfirmed' ) {
-                                       // Special case, rewrite autoconfirmed to editsemiprotected
-                                       if ( !$wgUser->isAllowed( 'editsemiprotected' ) ) {
-                                               continue;
-                                       }
-                               } elseif ( !$wgUser->isAllowed( $val ) ) {
-                                       continue;
-                               }
+                       if ( isset( $val ) && in_array( $val, $levels ) ) {
                                $this->mRestrictions[$action] = $val;
                        }
                }
@@ -189,7 +175,7 @@ class ProtectionForm {
        function execute() {
                global $wgRequest, $wgOut;
 
-               if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
+               if ( MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace() ) === array( '' ) ) {
                        throw new ErrorPageError( 'protect-badnamespace-title', 'protect-badnamespace-text' );
                }
 
@@ -556,28 +542,13 @@ class ProtectionForm {
         * @return String: HTML fragment
         */
        function buildSelector( $action, $selected ) {
-               global $wgRestrictionLevels, $wgUser;
-
-               $levels = array();
-               foreach ( $wgRestrictionLevels as $key ) {
-                       //don't let them choose levels above their own (aka so they can still unprotect and edit the page). but only when the form isn't disabled
-                       if ( $key == 'sysop' ) {
-                               //special case, rewrite sysop to editprotected
-                               if ( !$wgUser->isAllowed( 'editprotected' ) && !$this->disabled ) {
-                                       continue;
-                               }
-                       } elseif ( $key == 'autoconfirmed' ) {
-                               //special case, rewrite autoconfirmed to editsemiprotected
-                               if ( !$wgUser->isAllowed( 'editsemiprotected' ) && !$this->disabled ) {
-                                       continue;
-                               }
-                       } else {
-                               if ( !$wgUser->isAllowed( $key ) && !$this->disabled ) {
-                                       continue;
-                               }
-                       }
-                       $levels[] = $key;
-               }
+               global $wgUser;
+
+               // If the form is disabled, display all relevant levels. Otherwise,
+               // just show the ones this user can use.
+               $levels = MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace(),
+                       $this->disabled ? null : $wgUser
+               );
 
                $id = 'mwProtect-level-' . $action;
                $attribs = array(
index dde3f37..da4c376 100644 (file)
@@ -1039,7 +1039,9 @@ class SkinTemplate extends Skin {
                                        }
                                }
 
-                               if ( $title->getNamespace() !== NS_MEDIAWIKI && $title->quickUserCan( 'protect', $user ) && $title->getRestrictionTypes() ) {
+                               if ( $title->quickUserCan( 'protect', $user ) && $title->getRestrictionTypes() &&
+                                       MWNamespace::getRestrictionLevels( $title->getNamespace(), $user ) !== array( '' )
+                               ) {
                                        $mode = $title->isProtected() ? 'unprotect' : 'protect';
                                        $content_navigation['actions'][$mode] = array(
                                                'class' => ( $onPage && $action == $mode ) ? 'selected' : false,