r52082 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r52081‎ | r52082 | r52083 >
Date:02:13, 18 June 2009
Author:skizzerz
Status:resolved (Comments)
Tags:
Comment:
* (bug 17014) Blocked users can no longer use Special:UserRights if they do
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.
Modified paths:
  • /trunk/phase3/RELEASE-NOTES (modified) (history)
  • /trunk/phase3/docs/hooks.txt (modified) (history)
  • /trunk/phase3/includes/specials/SpecialUserrights.php (modified) (history)

Diff [purge]

Index: trunk/phase3/docs/hooks.txt
@@ -16,10 +16,10 @@
1717 hook
1818 A clump of code and data that should be run when an event happens. This can
1919 be either a function and a chunk of data, or an object and a method.
20 -
 20+
2121 hook function
2222 The function part of a hook.
23 -
 23+
2424 ==Rationale==
2525
2626 Hooks allow us to decouple optionally-run code from code that is run for
@@ -54,17 +54,17 @@
5555
5656 function showAnArticle($article) {
5757 global $wgReverseTitle, $wgCapitalizeTitle, $wgNotifyArticle;
58 -
 58+
5959 if ($wgReverseTitle) {
6060 wfReverseTitle($article);
6161 }
62 -
 62+
6363 if ($wgCapitalizeTitle) {
6464 wfCapitalizeTitle($article);
6565 }
6666
6767 # code to actually show the article goes here
68 -
 68+
6969 if ($wgNotifyArticle) {
7070 wfNotifyArticleShow($article));
7171 }
@@ -87,7 +87,7 @@
8888 code and moving them off somewhere else. It's much easier for someone working
8989 with this code to see what's _really_ going on, and make changes or fix bugs.
9090
91 -In addition, we can take all the code that deals with the little-used
 91+In addition, we can take all the code that deals with the little-used
9292 title-reversing options (say) and put it in one place. Instead of having little
9393 title-reversing if-blocks spread all over the codebase in showAnArticle,
9494 deleteAnArticle, exportArticle, etc., we can concentrate it all in an extension
@@ -116,8 +116,8 @@
117117 that it's easier to read and understand; you don't have to do a grep-find to see
118118 where the $wgReverseTitle variable is used, say.
119119
120 -If the code is well enough isolated, it can even be excluded when not used --
121 -making for some slight savings in memory and load-up performance at runtime.
 120+If the code is well enough isolated, it can even be excluded when not used --
 121+making for some slight savings in memory and load-up performance at runtime.
122122 Admins who want to have all the reversed titles can add:
123123
124124 require_once('extensions/ReverseTitle.php');
@@ -162,7 +162,7 @@
163163 $object->someMethod($param1, $param2)
164164 # object with method and data
165165 $object->someMethod($someData, $param1, $param2)
166 -
 166+
167167 Note that when an object is the hook, and there's no specified method, the
168168 default method called is 'onEventName'. For different events this would be
169169 different: 'onArticleSave', 'onUserLogin', etc.
@@ -183,13 +183,13 @@
184184 should be shown to the user
185185 * false: the hook has successfully done the work necessary and the calling
186186 function should skip
187 -
 187+
188188 The last result would be for cases where the hook function replaces the main
189189 functionality. For example, if you wanted to authenticate users to a custom
190190 system (LDAP, another PHP program, whatever), you could do:
191191
192192 $wgHooks['UserLogin'][] = array('ldapLogin', $ldapServer);
193 -
 193+
194194 function ldapLogin($username, $password) {
195195 # log user into LDAP
196196 return false;
@@ -199,7 +199,7 @@
200200 will normally be ignored.
201201
202202 Note that none of the examples made use of create_function() as a way to
203 -attach a function to a hook. This is known to cause problems (notably with
 203+attach a function to a hook. This is known to cause problems (notably with
204204 Special:Version), and should be avoided when at all possible.
205205
206206 ==Using hooks==
@@ -207,7 +207,7 @@
208208 A calling function or method uses the wfRunHooks() function to run the hooks
209209 related to a particular event, like so:
210210
211 - class Article {
 211+ class Article {
212212 # ...
213213 function protect() {
214214 global $wgUser;
@@ -217,7 +217,7 @@
218218 }
219219 }
220220 }
221 -
 221+
222222 wfRunHooks() returns true if the calling function should continue processing
223223 (the hooks ran OK, or there are no hooks to run), or false if it shouldn't (an
224224 error occurred, or one of the hooks handled the action already). Checking the
@@ -396,7 +396,7 @@
397397
398398 'ArticleMergeComplete': after merging to article using Special:Mergehistory
399399 $targetTitle: target title (object)
400 -$destTitle: destination title (object)
 400+$destTitle: destination title (object)
401401
402402 'ArticlePageDataAfter': after loading data of an article from the database
403403 $article: article (object) whose data were loaded
@@ -420,7 +420,7 @@
421421 $reason: Reason for protect
422422 $moveonly: boolean whether it was for move only or not
423423
424 -'ArticlePurge': before executing "&action=purge"
 424+'ArticlePurge': before executing "&action=purge"
425425 $article: article (object) to purge
426426
427427 'ArticleRevisionVisiblitySet': called when changing visibility of one or more
@@ -503,7 +503,7 @@
504504
505505 'BeforeGalleryFindFile': before an image is fetched for a gallery
506506 &$gallery,: the gallery object
507 -&$nt: the image title
 507+&$nt: the image title
508508 &$time: image timestamp
509509
510510 'BeforePageDisplay': Prior to outputting a page
@@ -924,7 +924,7 @@
925925 &$comment: string that corresponds to logging.log_comment database field, and
926926 which is displayed in the UI.
927927 &$revert: string that is displayed in the UI, similar to $comment.
928 -$time: timestamp of the log entry (added in 1.12)
 928+$time: timestamp of the log entry (added in 1.12)
929929
930930 'LogPageValidTypes': action being logged.
931931 DEPRECATED: Use $wgLogTypes
@@ -950,8 +950,8 @@
951951 $variableIDs: array of strings
952952
953953 'MakeGlobalVariablesScript': called right before Skin::makeVariablesScript
954 -is executed
955 -&$vars: variable (or multiple variables) to be added into the output
 954+is executed
 955+&$vars: variable (or multiple variables) to be added into the output
956956 of Skin::makeVariablesScript
957957
958958 'MarkPatrolled': before an edit is marked patrolled
@@ -1032,8 +1032,8 @@
10331033 &$urls: array of associative arrays with Url element attributes
10341034
10351035 'OutputPageBeforeHTML': a page has been processed by the parser and
1036 -the resulting HTML is about to be displayed.
1037 -$parserOutput: the parserOutput (object) that corresponds to the page
 1036+the resulting HTML is about to be displayed.
 1037+$parserOutput: the parserOutput (object) that corresponds to the page
10381038 $text: the text that will be displayed, in HTML (string)
10391039
10401040 'OutputPageCheckLastModified': when checking if the page has been modified
@@ -1074,7 +1074,7 @@
10751075 'ParserAfterStrip': Same as ParserBeforeStrip
10761076
10771077 'ParserAfterTidy': Called after Parser::tidy() in Parser::parse()
1078 -$parser: Parser object being used
 1078+$parser: Parser object being used
10791079 $text: text that'll be returned
10801080
10811081 'ParserBeforeInternalParse': called at the beginning of Parser::internalParse()
@@ -1089,7 +1089,7 @@
10901090 $stripState: stripState used (object)
10911091
10921092 'ParserBeforeTidy': called before tidy and custom tags replacements
1093 -$parser: Parser object being used
 1093+$parser: Parser object being used
10941094 $text: actual text
10951095
10961096 'ParserClearState': called at the end of Parser::clearState()
@@ -1393,7 +1393,7 @@
13941394
13951395 'UploadForm:initial': before the upload form is generated
13961396 $form: UploadForm object
1397 -You might set the member-variables $uploadFormTextTop and
 1397+You might set the member-variables $uploadFormTextTop and
13981398 $uploadFormTextAfterSummary to inject text (HTML) either before
13991399 or after the editform.
14001400
@@ -1517,7 +1517,7 @@
15181518 'UserLoginComplete': after a user has logged in
15191519 $user: the user object that was created on login
15201520 $inject_html: Any HTML to inject after the "logged in" message.
1521 -
 1521+
15221522 'UserLoginForm': change to manipulate the login form
15231523 $template: SimpleTemplate instance for the form
15241524
@@ -1527,7 +1527,7 @@
15281528
15291529 'UserLogout': before a user logs out
15301530 $user: the user object that is about to be logged out
1531 -
 1531+
15321532 'UserLogoutComplete': after a user has logged out
15331533 $user: the user object _after_ logout (won't have name, ID, etc.)
15341534 $inject_html: Any HTML to inject after the "logged out" message.
@@ -1544,13 +1544,30 @@
15451545 $user : User object of the current user
15461546 $addergroups : Array of groups that the user is in
15471547 &$groups : Array of groups that can be added or removed. In format of
1548 - array(
1549 - 'add' => array( addablegroups ),
1550 - 'remove' => array( removablegroups ),
 1548+ array(
 1549+ 'add' => array( addablegroups ),
 1550+ 'remove' => array( removablegroups ),
15511551 'add-self' => array( addablegroups to self ),
15521552 'remove-self' => array( removable groups from self )
15531553 )
15541554
 1555+'UserrightsGroupCheckboxes': allows modification of the display of
 1556+checkboxes in the Special:UserRights interface.
 1557+$usergroups : Array of groups that the target user belongs to
 1558+&$columns : Array of checkboxes, in the form of
 1559+ $columns['column name']['group name'] = array(
 1560+ 'set' => is this checkbox checked by default?
 1561+ 'disabled' => is this checkbox disabled?
 1562+ 'irreversible' => can this action not be reversed?
 1563+ );
 1564+
 1565+'UserrightsSaveUserGroups': allow extensions to modify the added/removed groups
 1566+&$user : User object of the user being altered
 1567+$oldGroups : Array of groups that the user is currently in
 1568+&$add : Array of groups to add
 1569+&$remove : Array of groups to remove
 1570+$reason : Summary provided by user on the form
 1571+
15551572 'UserRetrieveNewTalks': called when retrieving "You have new messages!"
15561573 message(s)
15571574 $user: user retrieving new talks messages
Index: trunk/phase3/includes/specials/SpecialUserrights.php
@@ -55,6 +55,16 @@
5656 $this->mTarget = $wgRequest->getVal( 'user' );
5757 }
5858
 59+ /*
 60+ * If the user is blocked and they only have "partial" access
 61+ * (e.g. they don't have the userrights permission), then don't
 62+ * allow them to use Special:UserRights.
 63+ */
 64+ if( $wgUser->isBlocked() && !$wgUser->isAllowed( 'userrights' ) ) {
 65+ $wgOut->blockedPage();
 66+ return;
 67+ }
 68+
5969 if (!$this->mTarget) {
6070 /*
6171 * If the user specified no target, and they can only
@@ -102,9 +112,9 @@
103113 $this->mTarget,
104114 $reason
105115 );
106 -
 116+
107117 global $wgOut;
108 -
 118+
109119 $url = $this->getSuccessURL();
110120 $wgOut->redirect( $url );
111121 return;
@@ -117,7 +127,7 @@
118128 $this->editUserGroupsForm( $this->mTarget );
119129 }
120130 }
121 -
 131+
122132 function getSuccessURL() {
123133 return $this->getTitle( $this->mTarget )->getFullURL();
124134 }
@@ -157,7 +167,7 @@
158168
159169 $this->doSaveUserGroups( $user, $addgroup, $removegroup, $reason );
160170 }
161 -
 171+
162172 /**
163173 * Save user groups changes in the database.
164174 *
@@ -185,6 +195,10 @@
186196
187197 $oldGroups = $user->getGroups();
188198 $newGroups = $oldGroups;
 199+
 200+ // Run a hook beforehand to allow extensions to modify the added/removed groups
 201+ wfRunHook( 'UserrightsSaveUserGroups', array( &$user, $oldGroups, &$add, &$remove, $reason ) );
 202+
189203 // remove then add groups
190204 if( $remove ) {
191205 $newGroups = array_diff($newGroups, $remove);
@@ -213,7 +227,7 @@
214228 return array( $add, $remove );
215229 }
216230
217 -
 231+
218232 /**
219233 * Add a rights log entry for an action.
220234 */
@@ -425,7 +439,7 @@
426440 $cache[$group] = User::makeGroupLinkHtml( $group, htmlspecialchars( User::getGroupName( $group ) ) );
427441 return $cache[$group];
428442 }
429 -
 443+
430444 /**
431445 * Returns an array of all groups that may be edited
432446 * @return array Array of groups that may be edited.
@@ -444,11 +458,11 @@
445459 $allgroups = $this->getAllGroups();
446460 $ret = '';
447461
448 - $column = 1;
449 - $settable_col = '';
450 - $unsettable_col = '';
 462+ # Put all column info into an associative array so that extensions can
 463+ # more easily manage it.
 464+ $columns = array( 'unchangeable' => array(), 'changeable' => array() );
451465
452 - foreach ($allgroups as $group) {
 466+ foreach( $allgroups as $group ) {
453467 $set = in_array( $group, $usergroups );
454468 # Should the checkbox be disabled?
455469 $disabled = !(
@@ -459,50 +473,50 @@
460474 ($set && !$this->canAdd( $group )) ||
461475 (!$set && !$this->canRemove( $group ) ) );
462476
463 - $attr = $disabled ? array( 'disabled' => 'disabled' ) : array();
464 - $text = $irreversible
465 - ? wfMsgHtml( 'userrights-irreversible-marker', User::getGroupMember( $group ) )
466 - : User::getGroupMember( $group );
467 - $checkbox = Xml::checkLabel( $text, "wpGroup-$group",
468 - "wpGroup-$group", $set, $attr );
469 - $checkbox = $disabled ? Xml::tags( 'span', array( 'class' => 'mw-userrights-disabled' ), $checkbox ) : $checkbox;
 477+ $checkbox = array(
 478+ 'set' => $set,
 479+ 'disabled' => $disabled,
 480+ 'irreversible' => $irreversible
 481+ );
470482
471 - if ($disabled) {
472 - $unsettable_col .= "$checkbox<br />\n";
 483+ if( $disabled ) {
 484+ $columns['unchangeable'][$group] = $checkbox;
473485 } else {
474 - $settable_col .= "$checkbox<br />\n";
 486+ $columns['changeable'][$group] = $checkbox;
475487 }
476488 }
477489
478 - if ($column) {
479 - $ret .= Xml::openElement( 'table', array( 'border' => '0', 'class' => 'mw-userrights-groups' ) ) .
480 - "<tr>
481 -";
482 - if( $settable_col !== '' ) {
483 - $ret .= xml::element( 'th', null, wfMsg( 'userrights-changeable-col' ) );
 490+ # Run a hook to allow extensions to modify the column listing
 491+ wfRunHooks( 'UserrightsGroupCheckboxes', array( $usergroups, &$columns ) );
 492+
 493+ # Build the HTML table
 494+ $ret .= Xml::openElement( 'table', array( 'border' => '0', 'class' => 'mw-userrights-groups' ) ) .
 495+ "<tr>\n";
 496+ foreach( $columns as $name => $column ) {
 497+ if( $column === array() )
 498+ continue;
 499+ $ret .= xml::element( 'th', null, wfMsg( 'userrights-' . $name . '-col' ) );
 500+ }
 501+ $ret.= "</tr>\n<tr>\n";
 502+ foreach( $columns as $column ) {
 503+ if( $column === array() )
 504+ continue;
 505+ $ret .= "\t<td style='vertical-align:top;'>\n";
 506+ foreach( $column as $group => $checkbox ) {
 507+ $attr = $checkbox['disabled'] ? array( 'disabled' => 'disabled' ) : array();
 508+ $text = $checkbox['irreversible']
 509+ ? wfMsgHtml( 'userrights-irreversible-marker', User::getGroupMember( $group ) )
 510+ : User::getGroupMember( $group );
 511+ $checkboxHtml = Xml::checkLabel( $text, "wpGroup-" . $group,
 512+ "wpGroup-" . $group, $checkbox['set'], $attr );
 513+ $ret .= "\t\t" . ( $checkbox['disabled']
 514+ ? Xml::tags( 'span', array( 'class' => 'mw-userrights-disabled' ), $checkboxHtml )
 515+ : $checkboxHtml
 516+ ) . "<br />\n";
484517 }
485 - if( $unsettable_col !== '' ) {
486 - $ret .= xml::element( 'th', null, wfMsg( 'userrights-unchangeable-col' ) );
487 - }
488 - $ret.= "</tr>
489 - <tr>
490 -";
491 - if( $settable_col !== '' ) {
492 - $ret .=
493 -" <td style='vertical-align:top;'>
494 - $settable_col
495 - </td>
496 -";
497 - }
498 - if( $unsettable_col !== '' ) {
499 - $ret .=
500 -" <td style='vertical-align:top;'>
501 - $unsettable_col
502 - </td>
503 -";
504 - }
505 - $ret .= Xml::closeElement( 'tr' ) . Xml::closeElement( 'table' );
 518+ $ret .= "\t</td>\n";
506519 }
 520+ $ret .= Xml::closeElement( 'tr' ) . Xml::closeElement( 'table' );
507521
508522 return $ret;
509523 }
Index: trunk/phase3/RELEASE-NOTES
@@ -83,6 +83,10 @@
8484 * DISPLAYTITLE now accepts a limited amount of wiki markup (the single-quote items)
8585 * Special:Search now could search terms in all variant-forms. ONLY apply on
8686 wikis with LanguageConverter
 87+* Add hook 'UserrightsGetCheckboxes' to give extensions the ability to modify
 88+ the arrangement of checkboxes on the Special:UserRights form
 89+* Add hook 'UserrightsSaveUserGroups' to give extensions the ability to modify
 90+ the groups being added and removed last-minute.
8791
8892 === Bug fixes in 1.16 ===
8993
@@ -187,6 +191,8 @@
188192 * (bug 19160) maintenance/purgeOldText.inc is now compatible with PostgreSQL
189193 * Fixed performance regression in "bad image list" feature
190194 * Show user preference 'Use live preview' if $wgLivePreview is enabled only
 195+* (bug 17014) Blocked users can no longer use Special:UserRights if they do
 196+ not have the 'userrights' permission.
191197
192198 == API changes in 1.16 ==
193199

Follow-up revisions

RevisionCommit summaryAuthorDate
r52116Fix typo in r52082tstarling14:02, 18 June 2009
r52118* Remove the two hooks introduced in r52082...skizzerz14:47, 18 June 2009
r52184Improve RELEASE-NOTES wording from r52082simetrical22:57, 19 June 2009

Comments

#Comment by Tim Starling (talk | contribs)   07:14, 18 June 2009

Presumably any extension relying on these UI hooks for access control would be very fragile and vulnerable to security policy changes when alternate UIs for user rights are added (such as the API). Don't you think you'd be better off hooking the backend in User.php? I see you added a similarly fragile hook in r39368, my preference would be to remove both of them.

#Comment by Catrope (talk | contribs)   12:12, 18 June 2009

"when" they're added? The API has a userrights module already.

#Comment by Skizzerz (talk | contribs)   13:47, 18 June 2009

The use case for the two hooks introduced in this commit were meant mainly for extending the user rights interface with other things, rather than locking it down. For example, one could add another row of checkboxes for a user's Global Group Membership via the UserrightsGetCheckboxes hook, then filter out those global groups via the UserrightsSaveUserGroups hook and handle them separately.

If you think the filtering of this could be better handled in the backend via User.php, I'll look into it and see where the ideal hook placement there would be so that both the UI and the API could benefit from it.

#Comment by Nikerabbit (talk | contribs)   12:11, 18 June 2009

Fatal error: Call to undefined function wfRunHook() in //w/includes/specials/SpecialUserrights.php on line 200

#Comment by Simetrical (talk | contribs)   22:32, 19 June 2009
+* (bug 17014) Blocked users can no longer use Special:UserRights if they do
+  not have the 'userrights' permission.

I really hope you mean "even if they have" instead of "if they do not have" here?

#Comment by Simetrical (talk | contribs)   22:54, 19 June 2009

Oh, I get it. You mean that previously everyone could use it, and now they only can if they have the 'userrights' right. That's confusing wording. It sounds like you meant that blocked users used to be able to use Special:UserRights without the userrights permission (desysop everyone as soon as you're blocked?), but now they can't anymore.

#Comment by Skizzerz (talk | contribs)   23:00, 19 June 2009

There's still plenty of time to update the wording in RELEASE-NOTES, would you suggest something else? I'm not sure how to explain the fact that it only affects users that can access userrights via $wgAddGroups/$wgRemoveGroups/$wgGroupsAddToSelf/$wgGroupsRemoveFromSelf but not those with full 'userrights' access. Perhaps like so?

Users with partial userrights access are no longer able to use Special:UserRights while blocked.

#Comment by Skizzerz (talk | contribs)   23:02, 19 June 2009

Bah, nevermind. My email notifications were being slow, so I didn't get the followup commit until after I submitted the reply

#Comment by Werdna (talk | contribs)   18:04, 27 August 2009

Hooks reverted in r52118. Rest seems to work OK, I tried to revert this and get a clean slate, but there were complicated dependent commits.

Status & tagging log