* (bug 17014) Blocked users can no longer use Special:UserRights if they do
authorRyan Schmidt <skizzerz@users.mediawiki.org>
Thu, 18 Jun 2009 02:13:42 +0000 (02:13 +0000)
committerRyan Schmidt <skizzerz@users.mediawiki.org>
Thu, 18 Jun 2009 02:13:42 +0000 (02:13 +0000)
  not have the 'userrights' permission.
* Add hook 'UserrightsGetCheckboxes' to give extensions the ability to modify
  the arrangement of checkboxes on the Special:UserRights form
* Add hook 'UserrightsSaveUserGroups' to give extensions the ability to modify
  the groups being added and removed last-minute.

RELEASE-NOTES
docs/hooks.txt
includes/specials/SpecialUserrights.php

index 90c9b60..f58cabf 100644 (file)
@@ -83,6 +83,10 @@ it from source control: http://www.mediawiki.org/wiki/Download_from_SVN
 * DISPLAYTITLE now accepts a limited amount of wiki markup (the single-quote items)
 * Special:Search now could search terms in all variant-forms. ONLY apply on
   wikis with LanguageConverter
+* Add hook 'UserrightsGetCheckboxes' to give extensions the ability to modify
+  the arrangement of checkboxes on the Special:UserRights form
+* Add hook 'UserrightsSaveUserGroups' to give extensions the ability to modify
+  the groups being added and removed last-minute.
 
 === Bug fixes in 1.16 ===
 
@@ -187,6 +191,8 @@ it from source control: http://www.mediawiki.org/wiki/Download_from_SVN
 * (bug 19160) maintenance/purgeOldText.inc is now compatible with PostgreSQL
 * Fixed performance regression in "bad image list" feature
 * Show user preference 'Use live preview' if $wgLivePreview is enabled only
+* (bug 17014) Blocked users can no longer use Special:UserRights if they do
+  not have the 'userrights' permission.
 
 == API changes in 1.16 ==
 
index 9e6203a..7980db0 100644 (file)
@@ -16,10 +16,10 @@ event
 hook
      A clump of code and data that should be run when an event happens. This can
      be either a function and a chunk of data, or an object and a method.
-     
+
 hook function
      The function part of a hook.
-     
+
 ==Rationale==
 
 Hooks allow us to decouple optionally-run code from code that is run for
@@ -54,17 +54,17 @@ email notification when an article is shown may add:
 
     function showAnArticle($article) {
         global $wgReverseTitle, $wgCapitalizeTitle, $wgNotifyArticle;
-       
+
                if ($wgReverseTitle) {
                        wfReverseTitle($article);
                }
-       
+
                if ($wgCapitalizeTitle) {
                        wfCapitalizeTitle($article);
                }
 
                # code to actually show the article goes here
-       
+
                if ($wgNotifyArticle) {
                        wfNotifyArticleShow($article));
                }
@@ -87,7 +87,7 @@ We've cleaned up the code here by removing clumps of weird, infrequently used
 code and moving them off somewhere else. It's much easier for someone working
 with this code to see what's _really_ going on, and make changes or fix bugs.
 
-In addition, we can take all the code that deals with the little-used 
+In addition, we can take all the code that deals with the little-used
 title-reversing options (say) and put it in one place. Instead of having little
 title-reversing if-blocks spread all over the codebase in showAnArticle,
 deleteAnArticle, exportArticle, etc., we can concentrate it all in an extension
@@ -116,8 +116,8 @@ Having all this code related to the title-reversion option in one place means
 that it's easier to read and understand; you don't have to do a grep-find to see
 where the $wgReverseTitle variable is used, say.
 
-If the code is well enough isolated, it can even be excluded when not used -- 
-making for some slight savings in memory and load-up performance at runtime. 
+If the code is well enough isolated, it can even be excluded when not used --
+making for some slight savings in memory and load-up performance at runtime.
 Admins who want to have all the reversed titles can add:
 
        require_once('extensions/ReverseTitle.php');
@@ -162,7 +162,7 @@ would result in the following code being executed when 'EventName' happened:
        $object->someMethod($param1, $param2)
        # object with method and data
        $object->someMethod($someData, $param1, $param2)
-      
+
 Note that when an object is the hook, and there's no specified method, the
 default method called is 'onEventName'. For different events this would be
 different: 'onArticleSave', 'onUserLogin', etc.
@@ -183,13 +183,13 @@ Hooks can return three possible values:
                    should be shown to the user
   * false: the hook has successfully done the work necessary and the calling
            function should skip
-       
+
 The last result would be for cases where the hook function replaces the main
 functionality. For example, if you wanted to authenticate users to a custom
 system (LDAP, another PHP program, whatever), you could do:
 
        $wgHooks['UserLogin'][] = array('ldapLogin', $ldapServer);
-  
+
        function ldapLogin($username, $password) {
                # log user into LDAP
                return false;
@@ -199,7 +199,7 @@ Returning false makes less sense for events where the action is complete, and
 will normally be ignored.
 
 Note that none of the examples made use of create_function() as a way to
-attach a function to a hook. This is known to cause problems (notably with 
+attach a function to a hook. This is known to cause problems (notably with
 Special:Version), and should be avoided when at all possible.
 
 ==Using hooks==
@@ -207,7 +207,7 @@ Special:Version), and should be avoided when at all possible.
 A calling function or method uses the wfRunHooks() function to run the hooks
 related to a particular event, like so:
 
-       class Article { 
+       class Article {
                # ...
                function protect() {
                        global $wgUser;
@@ -217,7 +217,7 @@ related to a particular event, like so:
                        }
                }
        }
-                                                   
+
 wfRunHooks() returns true if the calling function should continue processing
 (the hooks ran OK, or there are no hooks to run), or false if it shouldn't (an
 error occurred, or one of the hooks handled the action already). Checking the
@@ -396,7 +396,7 @@ $revision: New Revision of the article
 
 'ArticleMergeComplete': after merging to article using Special:Mergehistory
 $targetTitle: target title (object)
-$destTitle: destination title (object) 
+$destTitle: destination title (object)
 
 'ArticlePageDataAfter': after loading data of an article from the database
 $article: article (object) whose data were loaded
@@ -420,7 +420,7 @@ $protect: boolean whether it was a protect or an unprotect
 $reason: Reason for protect
 $moveonly: boolean whether it was for move only or not
 
-'ArticlePurge': before executing "&action=purge" 
+'ArticlePurge': before executing "&action=purge"
 $article: article (object) to purge
 
 'ArticleRevisionVisiblitySet': called when changing visibility of one or more
@@ -503,7 +503,7 @@ rendered inline in wiki pages or galleries in category pages.
 
 'BeforeGalleryFindFile': before an image is fetched for a gallery
 &$gallery,: the gallery object
-&$nt: the image title 
+&$nt: the image title
 &$time: image timestamp
 
 'BeforePageDisplay': Prior to outputting a page
@@ -924,7 +924,7 @@ $paramArray: Array of parameters that corresponds to logging.log_params field.
 &$comment: string that corresponds to logging.log_comment database field, and
        which is displayed in the UI.
 &$revert: string that is displayed in the UI, similar to $comment.
-$time: timestamp of the log entry (added in 1.12) 
+$time: timestamp of the log entry (added in 1.12)
 
 'LogPageValidTypes': action being logged.
 DEPRECATED: Use $wgLogTypes
@@ -950,8 +950,8 @@ $magicWords: array of strings
 $variableIDs: array of strings
 
 'MakeGlobalVariablesScript': called right before Skin::makeVariablesScript
-is executed   
-&$vars: variable (or multiple variables) to be added into the output   
+is executed
+&$vars: variable (or multiple variables) to be added into the output
        of Skin::makeVariablesScript
 
 'MarkPatrolled': before an edit is marked patrolled
@@ -1032,8 +1032,8 @@ Hooks can alter or append to the array of URLs for search & suggestion formats.
 &$urls: array of associative arrays with Url element attributes
 
 'OutputPageBeforeHTML': a page has been processed by the parser and
-the resulting HTML is about to be displayed.  
-$parserOutput: the parserOutput (object) that corresponds to the page 
+the resulting HTML is about to be displayed.
+$parserOutput: the parserOutput (object) that corresponds to the page
 $text: the text that will be displayed, in HTML (string)
 
 'OutputPageCheckLastModified': when checking if the page has been modified
@@ -1074,7 +1074,7 @@ $hash: reference to a hash key string which can be modified
 'ParserAfterStrip': Same as ParserBeforeStrip
 
 'ParserAfterTidy': Called after Parser::tidy() in Parser::parse()
-$parser: Parser object being used 
+$parser: Parser object being used
 $text: text that'll be returned
 
 'ParserBeforeInternalParse': called at the beginning of Parser::internalParse()
@@ -1089,7 +1089,7 @@ $text: text being parsed
 $stripState: stripState used (object)
 
 'ParserBeforeTidy': called before tidy and custom tags replacements
-$parser: Parser object being used 
+$parser: Parser object being used
 $text: actual text
 
 'ParserClearState': called at the end of Parser::clearState()
@@ -1393,7 +1393,7 @@ $article: article object that was watched
 
 'UploadForm:initial': before the upload form is generated
 $form: UploadForm object
-You might set the member-variables $uploadFormTextTop and 
+You might set the member-variables $uploadFormTextTop and
 $uploadFormTextAfterSummary to inject text (HTML) either before
 or after the editform.
 
@@ -1517,7 +1517,7 @@ $user: User object
 'UserLoginComplete': after a user has logged in
 $user: the user object that was created on login
 $inject_html: Any HTML to inject after the "logged in" message.
-                   
+
 'UserLoginForm': change to manipulate the login form
 $template: SimpleTemplate instance for the form
 
@@ -1527,7 +1527,7 @@ $name: the username to email the password of.
 
 'UserLogout': before a user logs out
 $user: the user object that is about to be logged out
-       
+
 'UserLogoutComplete': after a user has logged out
 $user: the user object _after_ logout (won't have name, ID, etc.)
 $inject_html: Any HTML to inject after the "logged out" message.
@@ -1544,13 +1544,30 @@ $userrights  : UserrightsPage object
 $user        : User object of the current user
 $addergroups : Array of groups that the user is in
 &$groups     : Array of groups that can be added or removed. In format of
-                               array( 
-                                       'add'         => array( addablegroups ), 
-                                       'remove'      => array( removablegroups ), 
+                               array(
+                                       'add'         => array( addablegroups ),
+                                       'remove'      => array( removablegroups ),
                                        'add-self'    => array( addablegroups to self ),
                                        'remove-self' => array( removable groups from self )
                                )
 
+'UserrightsGroupCheckboxes': allows modification of the display of
+checkboxes in the Special:UserRights interface.
+$usergroups : Array of groups that the target user belongs to
+&$columns   : Array of checkboxes, in the form of
+                               $columns['column name']['group name'] = array(
+                                       'set'          => is this checkbox checked by default?
+                                       'disabled'     => is this checkbox disabled?
+                                       'irreversible' => can this action not be reversed?
+                               );
+
+'UserrightsSaveUserGroups': allow extensions to modify the added/removed groups
+&$user     : User object of the user being altered
+$oldGroups : Array of groups that the user is currently in
+&$add      : Array of groups to add
+&$remove   : Array of groups to remove
+$reason    : Summary provided by user on the form
+
 'UserRetrieveNewTalks': called when retrieving "You have new messages!"
 message(s)
 $user: user retrieving new talks messages
index c95f8bf..7e2c51b 100644 (file)
@@ -55,6 +55,16 @@ class UserrightsPage extends SpecialPage {
                        $this->mTarget = $wgRequest->getVal( 'user' );
                }
 
+               /*
+                * If the user is blocked and they only have "partial" access
+                * (e.g. they don't have the userrights permission), then don't
+                * allow them to use Special:UserRights.
+                */
+               if( $wgUser->isBlocked() && !$wgUser->isAllowed( 'userrights' ) ) {
+                       $wgOut->blockedPage();
+                       return;
+               }
+
                if (!$this->mTarget) {
                        /*
                         * If the user specified no target, and they can only
@@ -102,9 +112,9 @@ class UserrightsPage extends SpecialPage {
                                                $this->mTarget,
                                                $reason
                                        );
-                                       
+
                                        global $wgOut;
-                                       
+
                                        $url = $this->getSuccessURL();
                                        $wgOut->redirect( $url );
                                        return;
@@ -117,7 +127,7 @@ class UserrightsPage extends SpecialPage {
                        $this->editUserGroupsForm( $this->mTarget );
                }
        }
-       
+
        function getSuccessURL() {
                return $this->getTitle( $this->mTarget )->getFullURL();
        }
@@ -157,7 +167,7 @@ class UserrightsPage extends SpecialPage {
                
                $this->doSaveUserGroups( $user, $addgroup, $removegroup, $reason );
        }
-       
+
        /**
         * Save user groups changes in the database.
         *
@@ -185,6 +195,10 @@ class UserrightsPage extends SpecialPage {
 
                $oldGroups = $user->getGroups();
                $newGroups = $oldGroups;
+
+               // Run a hook beforehand to allow extensions to modify the added/removed groups
+               wfRunHook( 'UserrightsSaveUserGroups', array( &$user, $oldGroups, &$add, &$remove, $reason ) );
+
                // remove then add groups
                if( $remove ) {
                        $newGroups = array_diff($newGroups, $remove);
@@ -213,7 +227,7 @@ class UserrightsPage extends SpecialPage {
                return array( $add, $remove );
        }
 
-       
+
        /**
         * Add a rights log entry for an action.
         */
@@ -425,7 +439,7 @@ class UserrightsPage extends SpecialPage {
                        $cache[$group] = User::makeGroupLinkHtml( $group, htmlspecialchars( User::getGroupName( $group ) ) );
                return $cache[$group];
        }
-       
+
        /**
         * Returns an array of all groups that may be edited
         * @return array Array of groups that may be edited.
@@ -444,11 +458,11 @@ class UserrightsPage extends SpecialPage {
                $allgroups = $this->getAllGroups();
                $ret = '';
 
-               $column = 1;
-               $settable_col = '';
-               $unsettable_col = '';
+               # Put all column info into an associative array so that extensions can
+               # more easily manage it.
+               $columns = array( 'unchangeable' => array(), 'changeable' => array() );
 
-               foreach ($allgroups as $group) {
+               foreach( $allgroups as $group ) {
                        $set = in_array( $group, $usergroups );
                        # Should the checkbox be disabled?
                        $disabled = !(
@@ -459,50 +473,50 @@ class UserrightsPage extends SpecialPage {
                                ($set && !$this->canAdd( $group )) ||
                                (!$set && !$this->canRemove( $group ) ) );
 
-                       $attr = $disabled ? array( 'disabled' => 'disabled' ) : array();
-                       $text = $irreversible
-                               ? wfMsgHtml( 'userrights-irreversible-marker', User::getGroupMember( $group ) )
-                               : User::getGroupMember( $group );
-                       $checkbox = Xml::checkLabel( $text, "wpGroup-$group",
-                               "wpGroup-$group", $set, $attr );
-                       $checkbox = $disabled ? Xml::tags( 'span', array( 'class' => 'mw-userrights-disabled' ), $checkbox ) : $checkbox;
+                       $checkbox = array(
+                               'set' => $set,
+                               'disabled' => $disabled,
+                               'irreversible' => $irreversible
+                       );
 
-                       if ($disabled) {
-                               $unsettable_col .= "$checkbox<br />\n";
+                       if( $disabled ) {
+                               $columns['unchangeable'][$group] = $checkbox;
                        } else {
-                               $settable_col .= "$checkbox<br />\n";
+                               $columns['changeable'][$group] = $checkbox;
                        }
                }
 
-               if ($column) {
-                       $ret .= Xml::openElement( 'table', array( 'border' => '0', 'class' => 'mw-userrights-groups' ) ) .
-                               "<tr>
-";
-                       if( $settable_col !== '' ) {
-                               $ret .= xml::element( 'th', null, wfMsg( 'userrights-changeable-col' ) );
-                       }
-                       if( $unsettable_col !== '' ) {
-                               $ret .= xml::element( 'th', null, wfMsg( 'userrights-unchangeable-col' ) );
-                       }
-                       $ret.= "</tr>
-                               <tr>
-";
-                       if( $settable_col !== '' ) {
-                               $ret .=
-"                                      <td style='vertical-align:top;'>
-                                               $settable_col
-                                       </td>
-";
-                       }
-                       if( $unsettable_col !== '' ) {
-                               $ret .=
-"                                      <td style='vertical-align:top;'>
-                                               $unsettable_col
-                                       </td>
-";
+               # Run a hook to allow extensions to modify the column listing
+               wfRunHooks( 'UserrightsGroupCheckboxes', array( $usergroups, &$columns ) );
+
+               # Build the HTML table
+               $ret .= Xml::openElement( 'table', array( 'border' => '0', 'class' => 'mw-userrights-groups' ) ) .
+                       "<tr>\n";
+               foreach( $columns as $name => $column ) {
+                       if( $column === array() )
+                               continue;
+                       $ret .= xml::element( 'th', null, wfMsg( 'userrights-' . $name . '-col' ) );
+               }
+               $ret.= "</tr>\n<tr>\n";
+               foreach( $columns as $column ) {
+                       if( $column === array() )
+                               continue;
+                       $ret .= "\t<td style='vertical-align:top;'>\n";
+                       foreach( $column as $group => $checkbox ) {
+                               $attr = $checkbox['disabled'] ? array( 'disabled' => 'disabled' ) : array();
+                               $text = $checkbox['irreversible']
+                                       ? wfMsgHtml( 'userrights-irreversible-marker', User::getGroupMember( $group ) )
+                                       : User::getGroupMember( $group );
+                               $checkboxHtml = Xml::checkLabel( $text, "wpGroup-" . $group,
+                                       "wpGroup-" . $group, $checkbox['set'], $attr );
+                               $ret .= "\t\t" . ( $checkbox['disabled']
+                                       ? Xml::tags( 'span', array( 'class' => 'mw-userrights-disabled' ), $checkboxHtml )
+                                       : $checkboxHtml
+                               ) . "<br />\n";
                        }
-                       $ret .= Xml::closeElement( 'tr' ) . Xml::closeElement( 'table' );
+                       $ret .= "\t</td>\n";
                }
+               $ret .= Xml::closeElement( 'tr' ) . Xml::closeElement( 'table' );
 
                return $ret;
        }