r61737 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r61736‎ | r61737 | r61738 >
Date:02:41, 31 January 2010
Author:vasilievvv
Status:resolved (Comments)
Tags:schema 
Comment:
Rewrite CentralAuth admin form and lock/hide mechanism. Changes:
* Special:CentralAuth uses new permissions system. Normal users can use it for
viewing global user info, other actions are regulated by centralauth-unmerge,
centralauth-lock and centralauth-oversight.
* Hidden / not hidden is changed to not hidden / hidden / suppressed. Schema
change is attached. "hidden" means hidden just from lists, while "oversighted"
means user is visible only to stewards and local oversighted. Global oversight
suppresses all local user contributions (via job queue) on all wikis.
* Lock restrictions are weakened. Locked users may now log in and edit pages
specified in $wgCentralAuthLockedCanEdit.
* Special:CentralAuth layout is cleaned up, new unmerge form has hints on what
does "unmerged by whatever" means.

Should fix bugs 18183, 18060, 20571, 18723, partly 15294, maybe 20093, maybe
some others. Since I don't want FIXED-REOPENED mess on all of them, please
don't close then until this revision is reviewed and deployed.
Modified paths:
  • /trunk/extensions/CentralAuth/CentralAuth.i18n.php (modified) (history)
  • /trunk/extensions/CentralAuth/CentralAuth.php (modified) (history)
  • /trunk/extensions/CentralAuth/CentralAuthHooks.php (modified) (history)
  • /trunk/extensions/CentralAuth/CentralAuthUser.php (modified) (history)
  • /trunk/extensions/CentralAuth/SpecialCentralAuth.php (modified) (history)
  • /trunk/extensions/CentralAuth/SpecialGlobalUsers.php (modified) (history)
  • /trunk/extensions/CentralAuth/SuppressUserJob.php (added) (history)
  • /trunk/extensions/CentralAuth/centralauth.css (added) (history)
  • /trunk/extensions/CentralAuth/centralauth.js (added) (history)
  • /trunk/extensions/CentralAuth/db_patches/patch-gu_hidden.sql (added) (history)
  • /trunk/extensions/CentralAuth/icons (added) (history)
  • /trunk/extensions/CentralAuth/icons/LICENSE (added) (history)
  • /trunk/extensions/CentralAuth/icons/README (added) (history)
  • /trunk/extensions/CentralAuth/icons/merged-admin.png (added) (history)
  • /trunk/extensions/CentralAuth/icons/merged-empty.png (added) (history)
  • /trunk/extensions/CentralAuth/icons/merged-login.png (added) (history)
  • /trunk/extensions/CentralAuth/icons/merged-mail.png (added) (history)
  • /trunk/extensions/CentralAuth/icons/merged-new.png (added) (history)
  • /trunk/extensions/CentralAuth/icons/merged-password.png (added) (history)
  • /trunk/extensions/CentralAuth/icons/merged-primary.png (added) (history)

Diff [purge]

Index: trunk/extensions/CentralAuth/centralauth.js
@@ -0,0 +1,29 @@
 2+cursor = { x : 0, y : 0 };
 3+function updateCursorPosition(e) {
 4+ e = e || window.event;
 5+ cursor.x = e.clientX + ( document.documentElement.scrollLeft || document.body.scrollLeft ) - document.documentElement.clientLeft;
 6+ cursor.y = e.clientY + ( document.documentElement.scrollTop || document.body.scrollTop ) - document.documentElement.clientTop;
 7+}
 8+document.onmousemove = updateCursorPosition;
 9+
 10+methodHint = null;
 11+function showMethodHint( methodName ) {
 12+ hideMethodHint();
 13+
 14+ method = wgMergeMethodDescriptions[methodName];
 15+ helpHtml = "<p class='merge-method-help-name'>" + method.short + "</p>" + method.desc;
 16+
 17+ methodHint = document.createElement( 'div' );
 18+ methodHint.innerHTML = helpHtml;
 19+ methodHint.setAttribute( 'class', 'merge-method-help-div' );
 20+ methodHint.style.left = cursor.x + 'px';
 21+ methodHint.style.top = cursor.y + 'px';
 22+ methodHint.setAttribute( 'onclick', 'hideMethodHint()' );
 23+ document.getElementById( 'globalWrapper' ).appendChild( methodHint );
 24+}
 25+function hideMethodHint() {
 26+ if( methodHint ) {
 27+ methodHint.parentNode.removeChild( methodHint );
 28+ methodHint = null;
 29+ }
 30+}
Property changes on: trunk/extensions/CentralAuth/centralauth.js
___________________________________________________________________
Name: svn:eol-style
131 + native
Index: trunk/extensions/CentralAuth/db_patches/patch-gu_hidden.sql
@@ -0,0 +1,17 @@
 2+-- This patch switches gu_hidden from boolean to string (so multiple hiding
 3+-- levels may be applied). It also creates indexes on those columns so we can easily
 4+-- get a list of hidden or locked users.
 5+-- Victor Vasiliev, January 2010.
 6+
 7+ALTER TABLE globaluser
 8+ MODIFY COLUMN gu_hidden VARBINARY(255) NOT NULL DEFAULT '';
 9+
 10+-- Not hidden (was 0)
 11+UPDATE globaluser SET gu_hidden = '' WHERE gu_hidden = '0';
 12+-- Hidden from public lists, but remains visible in Special:CentralSuth log
 13+UPDATE globaluser SET gu_hidden = 'lists' WHERE gu_hidden = '1';
 14+-- There's also "suppressed" level, which wasn't used before this schema change
 15+
 16+ALTER TABLE globaluser
 17+ ADD INDEX gu_locked( gu_locked ),
 18+ ADD INDEX gu_hidden( gu_hidden(255) );
Property changes on: trunk/extensions/CentralAuth/db_patches/patch-gu_hidden.sql
___________________________________________________________________
Name: svn:eol-style
119 + native
Index: trunk/extensions/CentralAuth/CentralAuthUser.php
@@ -18,7 +18,7 @@
1919 */
2020 /*private*/ var $mName;
2121 /*private*/ var $mStateDirty = false;
22 - /*private*/ var $mVersion = 3;
 22+ /*private*/ var $mVersion = 4;
2323 /*private*/ var $mDelayInvalidation = 0;
2424
2525 static $mCacheVars = array(
@@ -42,6 +42,10 @@
4343 'mVersion',
4444 );
4545
 46+ const HIDDEN_NONE = '';
 47+ const HIDDEN_LISTS = 'lists';
 48+ const HIDDEN_OVERSIGHT = 'suppressed';
 49+
4650 function __construct( $username ) {
4751 $this->mName = $username;
4852 $this->resetState();
@@ -216,7 +220,7 @@
217221 $this->mIsAttached = false;
218222 $this->mFromMaster = $fromMaster;
219223 $this->mLocked = false;
220 - $this->mHidden = false;
 224+ $this->mHidden = '';
221225 }
222226 }
223227
@@ -362,6 +366,8 @@
363367 }
364368
365369 /**
 370+ * Returns whether the account is
 371+ * locked.
366372 * @return bool
367373 */
368374 public function isLocked() {
@@ -370,6 +376,8 @@
371377 }
372378
373379 /**
 380+ * Returns whether user name should not
 381+ * be shown in public lists.
374382 * @return bool
375383 */
376384 public function isHidden() {
@@ -378,6 +386,31 @@
379387 }
380388
381389 /**
 390+ * Returns whether user's name should
 391+ * be hidden from all public views because
 392+ * of privacy issues.
 393+ * @return bool
 394+ */
 395+ public function isOversighted() {
 396+ $this->loadState();
 397+ return $this->mHidden == self::HIDDEN_OVERSIGHT;
 398+ }
 399+
 400+ /**
 401+ * Returns the hidden level of
 402+ * the account.
 403+ */
 404+ public function getHiddenLevel() {
 405+ $this->loadState();
 406+
 407+ // b/c-failsafe. Should never happen
 408+ if( $this->mHidden === '0' )
 409+ $this->mHidden = '';
 410+
 411+ return $this->mHidden;
 412+ }
 413+
 414+ /**
382415 * @return string timestamp
383416 */
384417 public function getRegistration() {
@@ -413,7 +446,7 @@
414447 'gu_password' => $hash,
415448
416449 'gu_locked' => 0,
417 - 'gu_hidden' => 0,
 450+ 'gu_hidden' => '',
418451
419452 'gu_registration' => $dbw->timestamp(),
420453 ),
@@ -477,6 +510,8 @@
478511 'gu_email' => $email,
479512 'gu_email_authenticated' => $dbw->timestampOrNull( $emailAuth ),
480513 'gu_registration' => $dbw->timestamp(), // hmmmm
 514+ 'gu_locked' => 0,
 515+ 'gu_hidden' => '',
481516 ),
482517 __METHOD__,
483518 array( 'IGNORE' ) );
@@ -958,43 +993,143 @@
959994
960995 /**
961996 * Hide a global account
 997+ * @deprecated Use adminSetHidden
962998 */
963999 function adminHide() {
964 - $dbw = self::getCentralDB();
965 - $dbw->begin();
966 - $dbw->update( 'globaluser', array( 'gu_hidden' => 1 ),
967 - array( 'gu_name' => $this->mName ), __METHOD__ );
968 - if ( !$dbw->affectedRows() ) {
969 - $dbw->commit();
970 - return Status::newFatal( 'centralauth-admin-hide-nonexistent', $this->mName );
971 - }
972 - $dbw->commit();
973 -
974 - $this->invalidateCache();
975 -
976 - return Status::newGood();
 1000+ $this->adminSetHidden( self::HIDDEN_LISTS );
9771001 }
9781002
9791003 /**
9801004 * Unhide a global account
 1005+ * @deprecated Use adminSetHidden
9811006 */
9821007 function adminUnhide() {
 1008+ $this->adminSetHidden( self::HIDDEN_NONE );
 1009+ }
 1010+
 1011+ /**
 1012+ * Change account hiding level.
 1013+ */
 1014+ function adminSetHidden( $level ) {
9831015 $dbw = self::getCentralDB();
9841016 $dbw->begin();
985 - $dbw->update( 'globaluser', array( 'gu_hidden' => 0 ),
 1017+ $dbw->update( 'globaluser', array( 'gu_hidden' => $level ),
9861018 array( 'gu_name' => $this->mName ), __METHOD__ );
9871019 if ( !$dbw->affectedRows() ) {
9881020 $dbw->commit();
9891021 return Status::newFatal( 'centralauth-admin-unhide-nonexistent', $this->mName );
9901022 }
9911023 $dbw->commit();
992 -
 1024+
9931025 $this->invalidateCache();
994 -
 1026+
9951027 return Status::newGood();
9961028 }
9971029
9981030 /**
 1031+ * Suppresses all user accounts in all wikis.
 1032+ */
 1033+ function suppress( $reason ) {
 1034+ global $wgUser;
 1035+ $this->doCrosswikiSuppression( true, $wgUser->getName(), $reason );
 1036+ }
 1037+
 1038+ /**
 1039+ * Unsuppresses all user accounts in all wikis.
 1040+ */
 1041+ function unsuppress( $reason ) {
 1042+ global $wgUser;
 1043+ $this->doCrosswikiSuppression( false, $wgUser->getName(), $reason );
 1044+ }
 1045+
 1046+ protected function doCrosswikiSuppression( $suppress, $by, $reason ) {
 1047+ global $wgCentralAuthWikisPerSuppressJob;
 1048+ $this->loadAttached();
 1049+ if( count( $this->mAttachedArray ) <= $wgCentralAuthWikisPerSuppressJob ) {
 1050+ foreach( $this->mAttachedArray as $wiki ) {
 1051+ $this->doLocalSuppression( $suppress, $wiki, $by, $reason );
 1052+ }
 1053+ } else {
 1054+ $jobParams = array(
 1055+ 'username' => $this->getName(),
 1056+ 'suppress' => $suppress,
 1057+ 'by' => $by,
 1058+ 'reason' => $reason,
 1059+ );
 1060+ $jobs = array();
 1061+ $step = $wgCentralAuthWikisPerSuppressJob;
 1062+ for( $jobCount = 0; $jobCount < count( $this->mAttachedArray ); $jobCount += $step ) {
 1063+ $length = $jobCount + $step > count( $this->mAttachedArray ) ?
 1064+ $jobCount - $step : $step;
 1065+ $jobParams['wikis'] = array_slice( $this->mAttachedArray, $jobCount, $length );
 1066+ $jobs[] = Job::factory(
 1067+ 'crosswikiSuppressUser',
 1068+ Title::makeTitleSafe( NS_USER, $this->getName() ),
 1069+ $jobParams );
 1070+ }
 1071+ Job::batchInsert( $jobs );
 1072+ }
 1073+ }
 1074+
 1075+ /**
 1076+ * Suppresses a local account of a user.
 1077+ */
 1078+ public function doLocalSuppression( $suppress, $wiki, $by, $reason ) {
 1079+ global $wgConf, $wgLanguageNames;
 1080+
 1081+ $lb = wfGetLB( $wiki );
 1082+ $dbw = $lb->getConnection( DB_MASTER, array(), $wiki );
 1083+ $data = $this->localUserData( $wiki );
 1084+
 1085+ if( $suppress ) {
 1086+ list( $site, $lang ) = $wgConf->siteFromDB( $wiki );
 1087+ $lang = isset( $wgLanguageNames[$lang] ) ? $lang : 'en';
 1088+ $blockReason = wfMsgReal( 'centralauth-admin-suppressreason',
 1089+ array( $by, $reason ), true, $lang );
 1090+
 1091+ $ipbID = $dbw->nextSequenceValue( 'ipblocks_ipb_id_seq' );
 1092+ $dbw->insert( 'ipblocks',
 1093+ array(
 1094+ 'ipb_id' => $ipbID,
 1095+ 'ipb_address' => $this->mName,
 1096+ 'ipb_user' => $data['id'],
 1097+ 'ipb_by' => 0,
 1098+ 'ipb_by_text' => $by,
 1099+ 'ipb_reason' => $blockReason,
 1100+ 'ipb_timestamp' => $dbw->timestamp( wfTimestampNow() ),
 1101+ 'ipb_auto' => false,
 1102+ 'ipb_anon_only' => false,
 1103+ 'ipb_create_account' => true,
 1104+ 'ipb_enable_autoblock' => false,
 1105+ 'ipb_expiry' => Block::infinity(),
 1106+ 'ipb_range_start' => '',
 1107+ 'ipb_range_end' => '',
 1108+ 'ipb_deleted' => true,
 1109+ 'ipb_block_email' => true,
 1110+ 'ipb_allow_usertalk' => false
 1111+ ), __METHOD__, array( 'IGNORE' )
 1112+ );
 1113+
 1114+ IPBlockForm::suppressUserName( $this->mName, $data['id'], $dbw );
 1115+ } else {
 1116+ $dbw->delete(
 1117+ 'ipblocks',
 1118+ array(
 1119+ 'ipb_user' => $data['id'],
 1120+ 'ipb_by' => 0, // Check whether this block was imposed globally
 1121+ 'ipb_deleted' => true,
 1122+ ),
 1123+ __METHOD__
 1124+ );
 1125+
 1126+ // Unsuppress only if unblocked
 1127+ if( $dbw->affectedRows() ) {
 1128+ IPBlockForm::unsuppressUserName( $this->mName, $data['id'], $dbw );
 1129+ }
 1130+ }
 1131+ }
 1132+
 1133+ /**
9991134 * Add a local account record for the given wiki to the central database.
10001135 * @param string $wikiID
10011136 * @param int $localid
@@ -1033,7 +1168,7 @@
10341169 $wgCentralAuthUDPAddress, $wgCentralAuthNew2UDPPrefix );
10351170 }
10361171 }
1037 -
 1172+
10381173 /**
10391174 * Generate an IRC line corresponding to user unification/creation
10401175 * @param Title $userpage
@@ -1065,15 +1200,15 @@
10661201 return "no user";
10671202 }
10681203
1069 - list($salt,$crypt) = $this->getPasswordHash();
1070 - $locked = $this->isLocked();
 1204+ // Don't allow users to autocreate if they are oversighted.
 1205+ // If they do, their name will appear on local user list
 1206+ // (and since it contains private info, its inacceptable).
 1207+ // FIXME: this will give users "password incorrect" error.
 1208+ // Giving correct message requires AuthPlugin and SpecialUserlogin
 1209+ // rewriting.
 1210+ if( !User::idFromName( $this->getName() ) && $this->isOversighted() )
 1211+ return "locked";
10711212
1072 - if( $locked ) {
1073 - wfDebugLog( 'CentralAuth',
1074 - "authentication for '$this->mName' failed due to lock" );
1075 - return "locked";
1076 - }
1077 -
10781213 return true;
10791214 }
10801215
Index: trunk/extensions/CentralAuth/CentralAuth.php
@@ -122,11 +122,29 @@
123123 */
124124 $wgCentralAuthCreateOnView = false;
125125
126 -// UDP logging stuff
 126+/**
 127+ * UPD-transmissed RC settings
 128+ */
127129 $wgCentralAuthUDPAddress = false;
128130 $wgCentralAuthNew2UDPPrefix = '';
129131
130132 /**
 133+ * A CSS file version. Change each time centralauth.css is changed.
 134+ */
 135+$wgCentralAuthStyleVersion = 1;
 136+
 137+/**
 138+ * List of local pages global users may edit while being globally locked.
 139+ */
 140+$wgCentralAuthLockedCanEdit = array();
 141+
 142+/**
 143+ * Size of wikis handled in one suppress user job.
 144+ * Keep in mind that one wiki requires ~10 queries.
 145+ */
 146+$wgCentralAuthWikisPerSuppressJob = 10;
 147+
 148+/**
131149 * Initialization of the autoloaders, and special extension pages.
132150 */
133151 $caBase = dirname( __FILE__ );
@@ -136,6 +154,7 @@
137155 $wgAutoloadClasses['CentralAuthUser'] = "$caBase/CentralAuthUser.php";
138156 $wgAutoloadClasses['CentralAuthPlugin'] = "$caBase/CentralAuthPlugin.php";
139157 $wgAutoloadClasses['CentralAuthHooks'] = "$caBase/CentralAuthHooks.php";
 158+$wgAutoloadClasses['CentralAuthSuppressUserJob'] = "$caBase/SuppressUserJob.php";
140159 $wgAutoloadClasses['WikiSet'] = "$caBase/WikiSet.php";
141160 $wgAutoloadClasses['SpecialAutoLogin'] = "$caBase/SpecialAutoLogin.php";
142161 $wgAutoloadClasses['CentralAuthUserArray'] = "$caBase/CentralAuthUserArray.php";
@@ -149,6 +168,8 @@
150169 $wgExtensionMessagesFiles['SpecialCentralAuth'] = "$caBase/CentralAuth.i18n.php";
151170 $wgExtensionAliasesFiles['SpecialCentralAuth'] = "$caBase/CentralAuth.alias.php";
152171
 172+$wgJobClasses['crosswikiSuppressUser'] = 'CentralAuthSuppressUserJob';
 173+
153174 $wgHooks['AuthPluginSetup'][] = 'CentralAuthHooks::onAuthPluginSetup';
154175 $wgHooks['AddNewAccount'][] = 'CentralAuthHooks::onAddNewAccount';
155176 $wgHooks['GetPreferences'][] = 'CentralAuthHooks::onGetPreferences';
@@ -178,11 +199,15 @@
179200 // For SecurePoll
180201 $wgHooks['SecurePoll_GetUserParams'][] = 'CentralAuthHooks::onSecurePoll_GetUserParams';
181202
182 -$wgAvailableRights[] = 'centralauth-admin';
183203 $wgAvailableRights[] = 'centralauth-merge';
 204+$wgAvailableRights[] = 'centralauth-unmerge';
 205+$wgAvailableRights[] = 'centralauth-lock';
 206+$wgAvailableRights[] = 'centralauth-oversight';
184207 $wgAvailableRights[] = 'globalgrouppermissions';
185208 $wgAvailableRights[] = 'globalgroupmembership';
186 -$wgGroupPermissions['steward']['centralauth-admin'] = true;
 209+$wgGroupPermissions['steward']['centralauth-unmerge'] = true;
 210+$wgGroupPermissions['steward']['centralauth-lock'] = true;
 211+$wgGroupPermissions['steward']['centralauth-oversight'] = true;
187212 $wgGroupPermissions['*']['centralauth-merge'] = true;
188213
189214 $wgSpecialPages['CentralAuth'] = 'SpecialCentralAuth';
@@ -211,6 +236,7 @@
212237 $wgLogActions['globalauth/unhide'] = 'centralauth-log-entry-unhide';
213238 $wgLogActions['globalauth/lockandhid'] = 'centralauth-log-entry-lockandhide';
214239 $wgLogActions['globalauth/setstatus'] = 'centralauth-log-entry-chgstatus';
 240+$wgLogActions['suppress/setstatus'] = 'centralauth-log-entry-chgstatus';
215241
216242 $wgLogTypes[] = 'gblrights';
217243 $wgLogNames['gblrights'] = 'centralauth-rightslog-name';
Index: trunk/extensions/CentralAuth/SpecialCentralAuth.php
@@ -1,36 +1,31 @@
22 <?php
33
44 class SpecialCentralAuth extends SpecialPage {
 5+ var $mUserName, $mCanUnmerge, $mCanLock, $mCanOversight, $mCanEdit;
 6+ var $mGlobalUser, $mAttachedLocalAccounts, $mUnattachedLocalAccounts;
57
68 function __construct() {
79 wfLoadExtensionMessages('SpecialCentralAuth');
8 - parent::__construct( 'CentralAuth', 'centralauth-admin' );
 10+ parent::__construct( 'CentralAuth' );
911 }
1012
1113 function execute( $subpage ) {
1214 global $wgOut, $wgRequest, $wgUser;
 15+ global $wgExtensionAssetsPath, $wgCentralAuthStyleVersion;
 16+ global $wgUser, $wgRequest;
1317 $this->setHeaders();
1418
15 - if( !$wgUser->isLoggedIn() ) {
16 - $wgOut->addWikiText(
17 - wfMsg( 'centralauth-merge-notlogged' ) .
18 - "\n\n" .
19 - wfMsg( 'centralauth-readmore-text' ) );
 19+ $this->mCanUnmerge = $wgUser->isAllowed( 'centralauth-unmerge' );
 20+ $this->mCanLock = $wgUser->isAllowed( 'centralauth-lock' );
 21+ $this->mCanOversight = $wgUser->isAllowed( 'centralauth-oversight' );
 22+ $this->mCanEdit = $this->mCanUnmerge || $this->mCanLock || $this->mCanOversight;
2023
21 - return;
22 - }
 24+ $wgOut->addExtensionStyle( "{$wgExtensionAssetsPath}/CentralAuth/centralauth.css?" .
 25+ $wgCentralAuthStyleVersion );
 26+ $wgOut->addScriptFile( "{$wgExtensionAssetsPath}/CentralAuth/centralauth.js?" .
 27+ $wgCentralAuthStyleVersion );
 28+ $this->addMergeMethodDescriptions();
2329
24 - global $wgUser, $wgRequest;
25 - $this->mUserName = $wgUser->getName();
26 -
27 - if( !$wgUser->isAllowed( 'centralauth-admin' ) ) {
28 - $wgOut->addWikiText(
29 - wfMsg( 'centralauth-admin-permission' ) .
30 - "\n\n" .
31 - wfMsg( 'centralauth-readmore-text' ) );
32 - return;
33 - }
34 -
3530 $this->mUserName =
3631 trim(
3732 str_replace( '_', ' ',
@@ -38,7 +33,6 @@
3934
4035 $this->mPosted = $wgRequest->wasPosted();
4136 $this->mMethod = $wgRequest->getVal( 'wpMethod' );
42 - $this->mPassword = $wgRequest->getVal( 'wpPassword' );
4337 $this->mWikis = (array)$wgRequest->getArray( 'wpWikis' );
4438
4539 // Possible demo states
@@ -61,22 +55,30 @@
6256
6357 $this->mGlobalUser = $globalUser = new CentralAuthUser( $this->mUserName );
6458
65 - if ( !$globalUser->exists() ) {
 59+ if ( !$globalUser->exists() ||
 60+ ( $globalUser->isOversighted() && !$this->mCanOversight ) ) {
6661 $this->showError( 'centralauth-admin-nonexistent', $this->mUserName );
6762 $this->showUsernameForm();
6863 return;
6964 }
7065
7166 $continue = true;
72 - if( $this->mPosted ) {
 67+ if( $this->mCanEdit && $this->mPosted ) {
7368 $continue = $this->doSubmit();
7469 }
7570
 71+ $this->mAttachedLocalAccounts = $this->mGlobalUser->queryAttached();
 72+ $this->mUnattachedLocalAccounts = $this->mGlobalUser->queryUnattached();
 73+
7674 $this->showUsernameForm();
7775 if ( $continue ) {
7876 $this->showInfo();
79 - $this->showStatusForm();
80 - $this->showActionForm( 'delete' );
 77+ if( $this->mCanLock )
 78+ $this->showStatusForm();
 79+ if( $this->mCanUnmerge )
 80+ $this->showActionForm( 'delete' );
 81+ if( $this->mCanEdit )
 82+ $this->showLogExtract();
8183 $this->showWikiLists();
8284 }
8385 }
@@ -88,7 +90,7 @@
8991 global $wgUser, $wgOut, $wgRequest;
9092 if ( !$wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) {
9193 $this->showError( 'centralauth-token-mismatch' );
92 - } elseif( $this->mMethod == 'unmerge' ) {
 94+ } elseif( $this->mMethod == 'unmerge' && $this->mCanUnmerge ) {
9395 $status = $globalUser->adminUnattach( $this->mWikis );
9496 if ( !$status->isGood() ) {
9597 $this->showStatusError( $status->getWikiText() );
@@ -98,7 +100,7 @@
99101 $wgLang->formatNum( $status->successCount ),
100102 /* deprecated */ $status->successCount );
101103 }
102 - } elseif ( $this->mMethod == 'delete' ) {
 104+ } elseif ( $this->mMethod == 'delete' && $this->mCanUnmerge ) {
103105 $status = $globalUser->adminDelete();
104106 if ( !$status->isGood() ) {
105107 $this->showStatusError( $status->getWikiText() );
@@ -108,15 +110,23 @@
109111 $deleted = true;
110112 $this->logAction( 'delete', $this->mUserName, $wgRequest->getVal( 'reason' ) );
111113 }
112 - } elseif ( $this->mMethod == 'set-status' ) {
 114+ } elseif ( $this->mMethod == 'set-status' && $this->mCanLock ) {
113115 $setLocked = $wgRequest->getBool( 'wpStatusLocked' );
114 - $setHidden = $wgRequest->getBool( 'wpStatusHidden' );
 116+ $setHidden = $wgRequest->getVal( 'wpStatusHidden' );
115117 $isLocked = $globalUser->isLocked();
116 - $isHidden = $globalUser->isHidden();
 118+ $oldHiddenLevel = $globalUser->getHiddenLevel();
117119 $lockStatus = $hideStatus = null;
118120 $added = array();
119121 $removed = array();
120 -
 122+
 123+ // Sanitizing input value...
 124+ $hiddenLevels = array(
 125+ CentralAuthUser::HIDDEN_NONE,
 126+ CentralAuthUser::HIDDEN_LISTS,
 127+ CentralAuthUser::HIDDEN_OVERSIGHT );
 128+ if( !in_array( $setHidden, $hiddenLevels ) )
 129+ $setHidden = '';
 130+
121131 if ( !$isLocked && $setLocked ) {
122132 $lockStatus = $globalUser->adminLock();
123133 $added[] = wfMsgForContent( 'centralauth-log-status-locked' );
@@ -124,13 +134,39 @@
125135 $lockStatus = $globalUser->adminUnlock();
126136 $removed[] = wfMsgForContent( 'centralauth-log-status-locked' );
127137 }
 138+
 139+ $reason = $wgRequest->getText( 'wpReasonList' );
 140+ $reasonDetail = $wgRequest->getText( 'wpReason' );
 141+ if( $reason == 'other' ) {
 142+ $reason = $reasonDetail;
 143+ } elseif( $reasonDetail ) {
 144+ $reason .= wfMsgForContent( 'colon-separator' ) . $reasonDetail;
 145+ }
128146
129 - if ( !$isHidden && $setHidden ) {
130 - $hideStatus = $globalUser->adminHide();
131 - $added[] = wfMsgForContent( 'centralauth-log-status-hidden' );
132 - } elseif( $isHidden && !$setHidden ) {
133 - $hideStatus = $globalUser->adminUnhide();
134 - $removed[] = wfMsgForContent( 'centralauth-log-status-hidden' );
 147+ if ( $oldHiddenLevel != $setHidden ) {
 148+ $hideStatus = $globalUser->adminSetHidden( $setHidden );
 149+ switch( $setHidden ) {
 150+ case CentralAuthUser::HIDDEN_NONE:
 151+ $removed[] = $oldHiddenLevel == CentralAuthUser::HIDDEN_OVERSIGHT ?
 152+ wfMsgForContent( 'centralauth-log-status-oversighted' ) :
 153+ wfMsgForContent( 'centralauth-log-status-hidden' );
 154+ break;
 155+ case CentralAuthUser::HIDDEN_LISTS:
 156+ $added[] = wfMsgForContent( 'centralauth-log-status-hidden' );
 157+ if( $oldHiddenLevel == CentralAuthUser::HIDDEN_OVERSIGHT )
 158+ $removed[] = wfMsgForContent( 'centralauth-log-status-oversighted' );
 159+ break;
 160+ case CentralAuthUser::HIDDEN_OVERSIGHT:
 161+ $added[] = wfMsgForContent( 'centralauth-log-status-oversighted' );
 162+ if( $oldHiddenLevel == CentralAuthUser::HIDDEN_LISTS )
 163+ $removed[] = wfMsgForContent( 'centralauth-log-status-hidden' );
 164+ break;
 165+ }
 166+
 167+ if( $setHidden == CentralAuthUser::HIDDEN_OVERSIGHT )
 168+ $globalUser->suppress( $reason );
 169+ elseif( $oldHiddenLevel == CentralAuthUser::HIDDEN_OVERSIGHT )
 170+ $globalUser->unsuppress( $reason );
135171 }
136172
137173 $good =
@@ -143,13 +179,13 @@
144180 implode( ', ', $added ) : wfMsgForContent( 'centralauth-log-status-none' );
145181 $removed = count($removed) ?
146182 implode( ', ', $removed ) : wfMsgForContent( 'centralauth-log-status-none' );
147 -
148 - $reason = $wgRequest->getText( 'wpReason' );
 183+
149184 $this->logAction(
150185 'setstatus',
151186 $this->mUserName,
152187 $reason,
153 - array( $added, $removed )
 188+ array( $added, $removed ),
 189+ $setHidden == CentralAuthUser::HIDDEN_OVERSIGHT
154190 );
155191 $this->showSuccess( 'centralauth-admin-setstatus-success', $this->mUserName );
156192 } elseif (!$good) {
@@ -187,6 +223,9 @@
188224
189225 function showUsernameForm() {
190226 global $wgOut, $wgScript;
 227+ $lookup = $this->mCanEdit ?
 228+ wfMsg( 'centralauth-admin-lookup-rw' ) :
 229+ wfMsg( 'centralauth-admin-lookup-ro' );
191230 $wgOut->addHTML(
192231 Xml::openElement( 'form', array(
193232 'method' => 'get',
@@ -199,7 +238,7 @@
200239 'target', 'target', 25, $this->mUserName ) .
201240 '</p>' .
202241 '<p>' .
203 - Xml::submitButton( wfMsg( 'centralauth-admin-lookup' ) ) .
 242+ Xml::submitButton( $lookup ) .
204243 '</p>' .
205244 '</fieldset>' .
206245 '</form>'
@@ -222,71 +261,48 @@
223262 }
224263 return wfMsgExt( "centralauth-$unit-ago", 'parsemag', $span );
225264 }
226 -
227 - function showWikiLists() {
228 - global $wgOut;
229 - $merged = $this->mGlobalUser->queryAttached();
230 - $remainder = $this->mGlobalUser->queryUnattached();
231 -
232 - $wgOut->addHTML(
233 - Xml::tags( 'h2', null, wfMsgExt( 'centralauth-admin-attached', 'parseinline' ) )
234 - );
235 - $wgOut->addHTML( $this->listMerged( $merged ) );
236265
237 - $wgOut->addHTML(
238 - Xml::tags( 'h2', null, wfMsgExt( 'centralauth-admin-unattached', 'parseinline' ) )
239 - );
240 -
241 - if( $remainder ) {
242 - $wgOut->addHTML( $this->listRemainder( $remainder ) );
243 - } else {
244 - $wgOut->addWikiMsg( 'centralauth-admin-no-unattached' );
245 - }
246 - }
247 -
248266 function showInfo() {
249267 $globalUser = $this->mGlobalUser;
250268
251 - $id = $globalUser->exists() ? $globalUser->getId() : "unified account not registered";
252 -
253269 global $wgOut, $wgLang;
254 - if( $globalUser->exists() ) {
255 - $reg = $globalUser->getRegistration();
256 - $age = $this->prettyTimespan( wfTimestamp( TS_UNIX ) - wfTimestamp( TS_UNIX, $reg ) );
257 - $attribs = array(
258 - 'id' => $globalUser->getId(),
259 - 'registered' => $wgLang->timeanddate( $reg ) . " ($age)",
260 - 'locked' => $globalUser->isLocked() ? wfMsg( 'centralauth-admin-yes' ) : wfMsg( 'centralauth-admin-no' ),
261 - 'hidden' => $globalUser->isHidden() ? wfMsg( 'centralauth-admin-yes' ) : wfMsg( 'centralauth-admin-no' ) );
262 - $out = '<ul id="mw-centralauth-info">';
263 - foreach( $attribs as $tag => $data ) {
264 - $out .= Xml::element( 'li', array(), wfMsg( "centralauth-admin-info-$tag" ) . ' ' . $data );
265 - }
266 - $out .= '</ul>';
267 - $wgOut->addHTML( $out );
268 - } else {
269 - $wgOut->addWikiText( wfMsg( 'centralauth-admin-no-unified' ) );
 270+ $reg = $globalUser->getRegistration();
 271+ $age = $this->prettyTimespan( wfTimestamp( TS_UNIX ) - wfTimestamp( TS_UNIX, $reg ) );
 272+ $attribs = array(
 273+ 'id' => $globalUser->getId(),
 274+ 'registered' => $wgLang->timeanddate( $reg ) . " ($age)",
 275+ 'home' => $this->determineHomeWiki(),
 276+ 'editcount' => $this->evaluateTotalEditcount(),
 277+ 'locked' => $globalUser->isLocked() ? wfMsg( 'centralauth-admin-yes' ) : wfMsg( 'centralauth-admin-no' ),
 278+ 'hidden' => $this->formatHiddenLevel( $globalUser->getHiddenLevel() ) );
 279+ $out = '<fieldset id="mw-centralauth-info">';
 280+ $out .= '<legend>' . wfMsgHtml( 'centralauth-admin-info-header' ) . '</legend>';
 281+ foreach( $attribs as $tag => $data ) {
 282+ $out .= '<p><strong>' . wfMsg( "centralauth-admin-info-$tag" ) . '</strong> ' . $data . '</p>';
270283 }
 284+ $out .= '</fieldset>';
 285+ $wgOut->addHTML( $out );
271286 }
272287
273 - function listMerged( $list ) {
274 - return $this->listForm( $list, 'listMergedWikiItem',
275 - 'unmerge', wfMsg( 'centralauth-admin-unmerge' ) );
276 - }
 288+ function showWikiLists() {
 289+ global $wgOut;
 290+ $merged = $this->mAttachedLocalAccounts;
 291+ $remainder = $this->mUnattachedLocalAccounts;
277292
278 - function listRemainder( $list ) {
279 - ksort( $list );
280 - $s = '<ul id="mw-centralauth-unattached">';
281 - foreach ( $list as $row ) {
282 - $s .= '<li>' . $this->foreignUserLink( $row['wiki'] ) . "</li>\n";
283 - }
284 - $s .= '</ul>';
285 - return $s;
 293+ $legend = $this->mCanUnmerge ?
 294+ wfMsg( 'centralauth-admin-list-legend-rw' ) :
 295+ wfMsg( 'centralauth-admin-list-legend-ro' );
 296+
 297+ $wgOut->addHTML( "<fieldset><legend>{$legend}</legend>" );
 298+ $wgOut->addHTML( $this->listHeader() );
 299+ $wgOut->addHTML( $this->listMerged( $merged ) );
 300+ if( $remainder )
 301+ $wgOut->addHTML( $this->listRemainder( $remainder ) );
 302+ $wgOut->addHTML( $this->listFooter() );
286303 }
287304
288 - function listForm( $list, $listMethod, $action, $buttonText ) {
 305+ function listHeader() {
289306 global $wgUser;
290 - ksort( $list );
291307 return
292308 Xml::openElement( 'form',
293309 array(
@@ -294,53 +310,76 @@
295311 'action' =>
296312 $this->getTitle( $this->mUserName )->getLocalUrl( 'action=submit' ),
297313 'id' => 'mw-centralauth-merged' ) ) .
298 - Xml::hidden( 'wpMethod', $action ) .
 314+ Xml::hidden( 'wpMethod', 'unmerge' ) .
299315 Xml::hidden( 'wpEditToken', $wgUser->editToken() ) .
300 - '<table>' .
301 - '<thead>' .
302 - $this->tableRow( 'th',
303 - array(
304 - '',
305 - wfMsgHtml( 'centralauth-admin-list-localwiki' ),
306 - wfMsgHtml( 'centralauth-admin-list-attached-on' ),
307 - wfMsgHtml( 'centralauth-admin-list-method' ),
308 - wfMsgHtml( 'centralauth-admin-list-blocked' ),
309 - wfMsgHtml( 'centralauth-admin-list-editcount' ),
310 - )
311 - ) .
312 - '</thead>' .
313 - '<tbody>' .
314 - implode( "\n",
315 - array_map( array( $this, $listMethod ),
316 - $list ) ) .
317 - '<tr>' .
318 - '<td></td>' .
319 - '<td>' .
320 - Xml::submitButton( $buttonText ) .
321 - '</td>' .
322 - '</tr>' .
323 - '</tbody>' .
324 - '</table>' .
325 - Xml::closeElement( 'form' );
 316+ '<table id="mw-wikis-list">' .
 317+ '<thead><tr>' .
 318+ ( $this->mCanUnmerge ? '<th></th>' : '' ) .
 319+ '<th>' . wfMsgHtml( 'centralauth-admin-list-localwiki' ) . '</th>'.
 320+ '<th>' . wfMsgHtml( 'centralauth-admin-list-attached-on' ) . '</th>' .
 321+ '<th>' . wfMsgHtml( 'centralauth-admin-list-method' ) . '</th>' .
 322+ '<th>' . wfMsgHtml( 'centralauth-admin-list-blocked' ) . '</th>' .
 323+ '<th>' . wfMsgHtml( 'centralauth-admin-list-editcount' ) . '</th>'.
 324+ '</tr></thead>' .
 325+ '<tbody>';
326326 }
327327
 328+ function listFooter() {
 329+ $footer = '';
 330+ if( $this->mCanUnmerge )
 331+ $footer .=
 332+ '<tr>' .
 333+ '<td style="border-right: none"></td>' .
 334+ '<td style="border-left: none" colspan="5">' .
 335+ Xml::submitButton( wfMsg( 'centralauth-admin-unmerge' ) ) .
 336+ '</td>' .
 337+ '</tr>';
 338+ $footer .= '</tbody></table></form>';
 339+ return $footer;
 340+ }
 341+
 342+ function listMerged( $list ) {
 343+ ksort( $list );
 344+ return implode( "\n", array_map( array( $this, 'listMergedWikiItem' ), $list ) );
 345+ }
 346+
 347+ function listRemainder( $list ) {
 348+ ksort( $list );
 349+ $notMerged = wfMsg( 'centralauth-admin-unattached' );
 350+ $rows = array();
 351+ foreach ( $list as $row ) {
 352+ $rows[] = '<tr class="unattached-row"><td>' .
 353+ ( $this->mCanUnmerge ? '</td><td>' : '' ) .
 354+ $this->foreignUserLink( $row['wiki'] ) .
 355+ "</td><td colspan='4'>{$notMerged}</td></tr>\n";
 356+ }
 357+ return implode( $rows );
 358+ }
 359+
328360 function listMergedWikiItem( $row ) {
329361 global $wgLang;
330 - return $this->tableRow( 'td',
331 - array(
332 - $this->adminCheck( $row['wiki'] ),
333 - $this->foreignUserLink( $row['wiki'] ),
334 - htmlspecialchars( $wgLang->timeanddate( $row['attachedTimestamp'] ) ),
335 - htmlspecialchars( wfMsg( 'centralauth-merge-method-' . $row['attachedMethod'] ) ),
336 - $this->showBlockStatus( $row ),
337 - intval( $row['editCount'] )
338 - )
339 - );
 362+ return '<tr>' .
 363+ ( $this->mCanUnmerge ? '<td>' . $this->adminCheck( $row['wiki'] ) . '</td>' : '' ).
 364+ '<td>' . $this->foreignUserLink( $row['wiki'] ) . '</td>' .
 365+ '<td>' . htmlspecialchars( $wgLang->timeanddate( $row['attachedTimestamp'] ) ) . '</td>' .
 366+ '<td style="text-align: center">' . $this->formatMergeMethod( $row['attachedMethod'] ) . '</td>' .
 367+ '<td>' . $this->formatBlockStatus( $row ) . '</td>' .
 368+ '<td>' . $this->formatEditcount( $row ) . '</td>' .
 369+ '</tr>';
340370 }
341 -
342 - function showBlockStatus( $row ) {
343 - if ($row['blocked']) {
344 - if ($row['block-expiry'] == 'infinity') {
 371+
 372+ function formatMergeMethod( $method ) {
 373+ global $wgExtensionAssetsPath;
 374+
 375+ $img = "{$wgExtensionAssetsPath}/CentralAuth/icons/merged-{$method}.png";
 376+ $brief = wfMsgHtml( "centralauth-merge-method-{$method}" );
 377+ return "<img src=\"{$img}\" alt=\"{$brief}\" />" .
 378+ "<span class=\"merge-method-help\" title=\"{$brief}\" onclick=\"showMethodHint('{$method}')\">(?)</span>";
 379+ }
 380+
 381+ function formatBlockStatus( $row ) {
 382+ if( $row['blocked'] ) {
 383+ if( $row['block-expiry'] == 'infinity' ) {
345384 $reason = $row['block-reason'];
346385 return wfMsgExt( 'centralauth-admin-blocked-indef', 'parseinline', array( $reason ) );
347386 } else {
@@ -350,38 +389,73 @@
351390 $expiryt = $wgLang->time( $row['block-expiry'] );
352391 $reason = $row['block-reason'];
353392
354 - return wfMsgExt( 'centralauth-admin-blocked', 'parseinline', array( $expiry, $reason, $expiryd, $expiryt ) );
 393+ $text = wfMsgExt( 'centralauth-admin-blocked', 'parseinline', array( $expiry, $reason, $expiryd, $expiryt ) );
355394 }
356395 } else {
357 - return wfMsgExt( 'centralauth-admin-notblocked', 'parseinline' );
 396+ $text = wfMsgExt( 'centralauth-admin-notblocked', 'parseinline' );
358397 }
 398+
 399+ return $this->foreignLink(
 400+ $row['wiki'],
 401+ 'Special:Log/block',
 402+ $text,
 403+ wfMsg( 'centralauth-admin-blocklog' ),
 404+ 'page=User:' . urlencode( $this->mUserName ) );
359405 }
360406
 407+ function formatEditcount( $row ) {
 408+ return $this->foreignLink(
 409+ $row['wiki'],
 410+ 'Special:Contributions/' . $this->mUserName,
 411+ intval( $row['editCount'] ),
 412+ 'contributions' );
 413+ }
 414+
 415+ function formatHiddenLevel( $level ) {
 416+ switch( $level ) {
 417+ case CentralAuthUser::HIDDEN_NONE:
 418+ return wfMsg( 'centralauth-admin-no' );
 419+ case CentralAuthUser::HIDDEN_LISTS:
 420+ return wfMsg( 'centralauth-admin-hidden-list' );
 421+ case CentralAuthUser::HIDDEN_OVERSIGHT:
 422+ return wfMsg( 'centralauth-admin-hidden-oversight' );
 423+ }
 424+ }
 425+
361426 function tableRow( $element, $cols ) {
362427 return "<tr><$element>" .
363428 implode( "</$element><$element>", $cols ) .
364429 "</$element></tr>";
365430 }
366431
367 - function foreignUserLink( $wikiID ) {
 432+ function foreignLink( $wikiID, $title, $text, $hint = '', $params = '' ) {
368433 $wiki = WikiMap::getWiki( $wikiID );
369434 if( !$wiki ) {
370 - throw new MWException( "no wiki for $wikiID" );
 435+ throw new MWException( "Invalid wiki: $wikiID" );
371436 }
372437
373 - $hostname = $wiki->getDisplayName();
374 - $userPageName = 'User:' . $this->mUserName;
375 - $url = $wiki->getUrl( $userPageName );
 438+ $url = $wiki->getUrl( $title );
 439+ if( $params )
 440+ $url .= '?' . $params;
376441 return Xml::element( 'a',
377442 array(
378443 'href' => $url,
379 - 'title' => wfMsg( 'centralauth-foreign-link',
380 - $this->mUserName,
381 - $hostname ),
 444+ 'title' => $hint,
382445 ),
383 - $hostname );
 446+ $text );
384447 }
385448
 449+ function foreignUserLink( $wikiID ) {
 450+ $wiki = WikiMap::getWiki( $wikiID );
 451+ $wikiname = $wiki->getDisplayName();
 452+ return $this->foreignLink(
 453+ $wikiID,
 454+ 'User:' . $this->mUserName,
 455+ $wikiname,
 456+ wfMsg( 'centralauth-foreign-link', $this->mUserName, $wikiname ) );
 457+
 458+ }
 459+
386460 function adminCheck( $wikiID ) {
387461 return
388462 Xml::check( 'wpWikis[]', false, array( 'value' => $wikiID ) );
@@ -390,7 +464,7 @@
391465 function showActionForm( $action ) {
392466 global $wgOut, $wgUser;
393467 $wgOut->addHTML(
394 - Xml::element( 'h2', array(), wfMsg( "centralauth-admin-{$action}-title" ) ) .
 468+ Xml::fieldset( wfMsg( "centralauth-admin-{$action}-title" ) ) .
395469 Xml::openElement( 'form', array(
396470 'method' => 'POST',
397471 'action' => $this->getTitle()->getFullUrl( 'target=' . urlencode( $this->mUserName ) ),
@@ -398,51 +472,76 @@
399473 Xml::hidden( 'wpMethod', $action ) .
400474 Xml::hidden( 'wpEditToken', $wgUser->editToken() ) .
401475 wfMsgExt( "centralauth-admin-{$action}-description", 'parse' ) .
402 - '<p>' .
403 - Xml::label( wfMsgHtml( 'centralauth-admin-reason' ), "{$action}-reason" ) . ' ' .
404 - Xml::input( 'reason', false, false, array( 'id' => "{$action}-reason" ) ) .
405 - '</p>' .
406 - '<p>' .
407 - Xml::submitButton( wfMsg( "centralauth-admin-{$action}-button" ) ) .
408 - '</p>' .
409 - '</form>' );
 476+ Xml::buildForm(
 477+ array( 'centralauth-admin-reason' => Xml::input( 'reason',
 478+ false, false, array( 'id' => "{$action}-reason" ) ) ),
 479+ "centralauth-admin-{$action}-button"
 480+ ) .
 481+ '</form></fieldset>' );
410482 }
411483
412484 function showStatusForm() {
413485 // Allows locking, hiding, locking and hiding.
414486 global $wgUser, $wgOut;
415487 $form = '';
416 - $form .= Xml::element( 'h2', array(), wfMsg( 'centralauth-admin-status' ) );
 488+ $form .= Xml::fieldset( wfMsg( 'centralauth-admin-status' ) );
417489 $form .= Xml::hidden( 'wpMethod', 'set-status' );
418490 $form .= Xml::hidden( 'wpEditToken', $wgUser->editToken() );
419491 $form .= wfMsgExt( 'centralauth-admin-status-intro', 'parse' );
420492
421 - // Checkboxen
422 - $lockedLabel = wfMsg( 'centralauth-admin-status-locked' );
423 - $checkLocked = Xml::checkLabel( $lockedLabel,
424 - 'wpStatusLocked',
425 - 'mw-centralauth-status-locked',
426 - $this->mGlobalUser->isLocked() );
427 - $hiddenLabel = wfMsg( 'centralauth-admin-status-hidden' );
428 - $checkHidden = Xml::checkLabel( $hiddenLabel,
429 - 'wpStatusHidden',
430 - 'mw-centralauth-status-hidden',
431 - $this->mGlobalUser->isHidden() );
432 -
433 - $form .= Xml::tags( 'p', null, $checkLocked ) .
434 - Xml::tags( 'p', null, $checkHidden );
435 -
 493+ // Radio buttons
 494+ $radioLocked =
 495+ Xml::radioLabel(
 496+ wfMsg( 'centralauth-admin-status-locked-no' ),
 497+ 'wpStatusLocked',
 498+ '0',
 499+ 'mw-centralauth-status-locked-no',
 500+ !$this->mGlobalUser->isLocked() ) .
 501+ '<br/>' .
 502+ Xml::radioLabel(
 503+ wfMsg( 'centralauth-admin-status-locked-yes' ),
 504+ 'wpStatusLocked',
 505+ '1',
 506+ 'mw-centralauth-status-locked-yes',
 507+ $this->mGlobalUser->isLocked() );
 508+ $radioHidden =
 509+ Xml::radioLabel(
 510+ wfMsg( 'centralauth-admin-status-hidden-no' ),
 511+ 'wpStatusHidden',
 512+ CentralAuthUser::HIDDEN_NONE,
 513+ 'mw-centralauth-status-hidden-no',
 514+ $this->mGlobalUser->getHiddenLevel() == CentralAuthUser::HIDDEN_NONE ) .
 515+ '<br/>' .
 516+ Xml::radioLabel(
 517+ wfMsg( 'centralauth-admin-status-hidden-list' ),
 518+ 'wpStatusHidden',
 519+ CentralAuthUser::HIDDEN_LISTS,
 520+ 'mw-centralauth-status-hidden-list',
 521+ $this->mGlobalUser->getHiddenLevel() == CentralAuthUser::HIDDEN_LISTS ) .
 522+ '<br/>';
 523+ if( $this->mCanOversight )
 524+ $radioHidden .= Xml::radioLabel(
 525+ wfMsg( 'centralauth-admin-status-hidden-oversight' ),
 526+ 'wpStatusHidden',
 527+ CentralAuthUser::HIDDEN_OVERSIGHT,
 528+ 'mw-centralauth-status-hidden-oversight',
 529+ $this->mGlobalUser->getHiddenLevel() == CentralAuthUser::HIDDEN_OVERSIGHT );
 530+
436531 // Reason
437 - $reasonLabel = wfMsg( 'centralauth-admin-reason' );
438 - $reasonField = Xml::inputLabel( $reasonLabel,
439 - 'wpReason',
440 - 'mw-centralauth-reason',
441 - 45 );
442 - $form .= Xml::tags( 'p', null, $reasonField );
443 -
444 - $form .= Xml::tags( 'p', null,
445 - Xml::submitButton( wfMsg( 'centralauth-admin-status-submit' ) )
446 - );
 532+ $reasonList = Xml::listDropDown( 'wpReasonList',
 533+ wfMsgForContent( 'centralauth-admin-status-reasons' ),
 534+ wfMsgForContent( 'ipbreasonotherlist' ) );
 535+ $reasonField = Xml::input( 'wpReason', 45, false );
 536+
 537+ $form .= Xml::buildForm(
 538+ array(
 539+ 'centralauth-admin-status-locked' => $radioLocked,
 540+ 'centralauth-admin-status-hidden' => $radioHidden,
 541+ 'centralauth-admin-reason' => $reasonList,
 542+ 'centralauth-admin-reason-other' => $reasonField ),
 543+ 'centralauth-admin-status-submit' );
 544+
 545+ $form .= '</fieldset>';
447546 $form = Xml::tags( 'form',
448547 array( 'method' => 'POST',
449548 'action' =>
@@ -455,8 +554,55 @@
456555 $wgOut->addHTML( $form );
457556 }
458557
459 - function logAction( $action, $target, $reason = '', $params = array() ) {
460 - $log = new LogPage( 'globalauth' ); //Not centralauth because of some weird length limitiations
 558+ function showLogExtract() {
 559+ global $wgOut;
 560+ $user = $this->mGlobalUser->getName();
 561+ $text = '';
 562+ $numRows = LogEventsList::showLogExtract(
 563+ $text,
 564+ array( 'globalauth', 'suppress' ),
 565+ Title::newFromText( "User:{$user}@global" )->getPrefixedText(),
 566+ '',
 567+ array( 'showIfEmpty' => true ) );
 568+ if( $numRows ) {
 569+ $wgOut->addHTML( Xml::fieldset( wfMsg( 'centralauth-admin-logsnippet' ), $text ) );
 570+ }
 571+ }
 572+
 573+ function determineHomeWiki() {
 574+ foreach( $this->mAttachedLocalAccounts as $wiki => $acc ) {
 575+ if( $acc['attachedMethod'] == 'primary' || $acc['attachedMethod'] == 'new' ) {
 576+ return $wiki;
 577+ }
 578+ }
 579+
 580+ // Should not happen.
 581+ return '';
 582+ }
 583+
 584+ function evaluateTotalEditcount() {
 585+ $total = 0;
 586+ foreach( $this->mAttachedLocalAccounts as $acc ) {
 587+ $total += $acc['editCount'];
 588+ }
 589+ return $total;
 590+ }
 591+
 592+ function addMergeMethodDescriptions() {
 593+ global $wgOut, $wgLang;
 594+ $js = "wgMergeMethodDescriptions = {\n";
 595+ foreach( array( 'primary', 'new', 'empty', 'password', 'mail', 'admin', 'login' ) as $method ) {
 596+ $short = Xml::encodeJsVar( $wgLang->ucFirst( wfMsgHtml( "centralauth-merge-method-{$method}" ) ) );
 597+ $desc = Xml::encodeJsVar( wfMsgWikiHtml( "centralauth-merge-method-{$method}-desc" ) );
 598+ $js .= "\t'{$method}' : { 'short' : {$short}, 'desc' : {$desc} },\n";
 599+ }
 600+ $js .= "}";
 601+ $wgOut->addInlineScript( $js );
 602+ }
 603+
 604+ function logAction( $action, $target, $reason = '', $params = array(), $suppressLog = false ) {
 605+ $logType = $suppressLog ? 'suppress' : 'globalauth'; // Not centralauth because of some weird length limitiations
 606+ $log = new LogPage( $logType );
461607 $log->addEntry( $action, Title::newFromText( "User:{$target}@global" ), $reason, $params );
462608 }
463609 }
Index: trunk/extensions/CentralAuth/centralauth.css
@@ -0,0 +1,36 @@
 2+#mw-wikis-list {
 3+ margin: 1em 1em 1em 0;
 4+ background: #f9f9f9;
 5+ border: 1px #aaa solid;
 6+ border-collapse: collapse;
 7+}
 8+#mw-wikis-list td {
 9+ border: 1px #aaa solid;
 10+ padding: 0.2em;
 11+}
 12+#mw-wikis-list th {
 13+ background: #f2f2f2;
 14+ text-align: center;
 15+}
 16+
 17+.merge-method-help {
 18+ color: #aaa;
 19+ cursor: pointer;
 20+ font-size: 7pt;
 21+ padding: 3px;
 22+}
 23+
 24+.merge-method-help-div {
 25+ position: absolute;
 26+ z-index: 3;
 27+ cursor: pointer;
 28+
 29+ width: 15em;
 30+ border: 1px solid black;
 31+ background-color: #ff9;
 32+ padding: 0.5em;
 33+}
 34+
 35+.merge-method-help-name {
 36+ font-weight: bold;
 37+}
Property changes on: trunk/extensions/CentralAuth/centralauth.css
___________________________________________________________________
Name: svn:eol-style
138 + native
Index: trunk/extensions/CentralAuth/CentralAuthHooks.php
@@ -444,7 +444,6 @@
445445 return false;
446446 }
447447
448 -
449448 // Checks passed, create the user
450449 wfDebug( __METHOD__.": creating new user\n" );
451450 $user->loadDefaults( $userName );
@@ -565,6 +564,8 @@
566565 }
567566
568567 static function onGetUserPermissionsErrorsExpensive( $title, $user, $action, &$result ) {
 568+ global $wgCentralAuthLockedCanEdit;
 569+
569570 if( $action == 'read' || $user->isAnon() ) {
570571 return true;
571572 }
@@ -572,7 +573,10 @@
573574 if( !($centralUser->exists() && $centralUser->isAttached()) ) {
574575 return true;
575576 }
576 - if( $centralUser->isLocked() ) {
 577+ if(
 578+ $centralUser->isOversighted() || // Oversighted users should *never* be able to edit
 579+ ( $centralUser->isLocked() && !in_array( $title->getPrefixedText(), $wgCentralAuthLockedCanEdit ) )
 580+ ) {
577581 $result = 'centralauth-error-locked';
578582 return false;
579583 }
Index: trunk/extensions/CentralAuth/SuppressUserJob.php
@@ -0,0 +1,37 @@
 2+<?php
 3+
 4+/**
 5+ * A job to do crosswiki suppression in batches, rather than
 6+ * in one request. Size of batch is changed by changing
 7+ * $wgCentralAuthWikisPerSuppressJob.
 8+ */
 9+class CentralAuthSuppressUserJob extends Job {
 10+ /**
 11+ * Constructor
 12+ *
 13+ * @param Title $title Associated title
 14+ * @param array $params Job parameters
 15+ */
 16+ public function __construct( $title, $params ) {
 17+ parent::__construct( 'crosswikiSuppressUser', $title, $params );
 18+ }
 19+
 20+ /**
 21+ * Execute the job
 22+ *
 23+ * @return bool
 24+ */
 25+ public function run() {
 26+ extract( $this->params );
 27+ $user = new CentralAuthUser( $username );
 28+ if( !$user->exists() ) {
 29+ wfDebugLog( 'suppressjob', "Requested to suppress non-existent user {$username} by {$by}." );
 30+ }
 31+
 32+ foreach( $wikis as $wiki ) {
 33+ $user->doLocalSuppression( $suppress, $wiki, $by, $reason );
 34+ wfDebugLog( 'suppressjob', ( $suppress ? 'S' : 'Uns' ) . "uppressed {$username} at {$wiki} by {$by} via job queue." );
 35+ }
 36+ return true;
 37+ }
 38+}
Property changes on: trunk/extensions/CentralAuth/SuppressUserJob.php
___________________________________________________________________
Name: svn:eol-style
139 + native
Index: trunk/extensions/CentralAuth/SpecialGlobalUsers.php
@@ -73,7 +73,7 @@
7474
7575 function getQueryInfo() {
7676 $localwiki = wfWikiID();
77 - $conds = array( 'gu_hidden' => 0 );
 77+ $conds = array( 'gu_hidden' => CentralAuthUser::HIDDEN_NONE );
7878
7979 if( $this->mGroup )
8080 $conds['gug_group'] = $this->mGroup;
Index: trunk/extensions/CentralAuth/icons/LICENSE
@@ -0,0 +1,165 @@
 2+ GNU LESSER GENERAL PUBLIC LICENSE
 3+ Version 3, 29 June 2007
 4+
 5+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
 6+ Everyone is permitted to copy and distribute verbatim copies
 7+ of this license document, but changing it is not allowed.
 8+
 9+
 10+ This version of the GNU Lesser General Public License incorporates
 11+the terms and conditions of version 3 of the GNU General Public
 12+License, supplemented by the additional permissions listed below.
 13+
 14+ 0. Additional Definitions.
 15+
 16+ As used herein, "this License" refers to version 3 of the GNU Lesser
 17+General Public License, and the "GNU GPL" refers to version 3 of the GNU
 18+General Public License.
 19+
 20+ "The Library" refers to a covered work governed by this License,
 21+other than an Application or a Combined Work as defined below.
 22+
 23+ An "Application" is any work that makes use of an interface provided
 24+by the Library, but which is not otherwise based on the Library.
 25+Defining a subclass of a class defined by the Library is deemed a mode
 26+of using an interface provided by the Library.
 27+
 28+ A "Combined Work" is a work produced by combining or linking an
 29+Application with the Library. The particular version of the Library
 30+with which the Combined Work was made is also called the "Linked
 31+Version".
 32+
 33+ The "Minimal Corresponding Source" for a Combined Work means the
 34+Corresponding Source for the Combined Work, excluding any source code
 35+for portions of the Combined Work that, considered in isolation, are
 36+based on the Application, and not on the Linked Version.
 37+
 38+ The "Corresponding Application Code" for a Combined Work means the
 39+object code and/or source code for the Application, including any data
 40+and utility programs needed for reproducing the Combined Work from the
 41+Application, but excluding the System Libraries of the Combined Work.
 42+
 43+ 1. Exception to Section 3 of the GNU GPL.
 44+
 45+ You may convey a covered work under sections 3 and 4 of this License
 46+without being bound by section 3 of the GNU GPL.
 47+
 48+ 2. Conveying Modified Versions.
 49+
 50+ If you modify a copy of the Library, and, in your modifications, a
 51+facility refers to a function or data to be supplied by an Application
 52+that uses the facility (other than as an argument passed when the
 53+facility is invoked), then you may convey a copy of the modified
 54+version:
 55+
 56+ a) under this License, provided that you make a good faith effort to
 57+ ensure that, in the event an Application does not supply the
 58+ function or data, the facility still operates, and performs
 59+ whatever part of its purpose remains meaningful, or
 60+
 61+ b) under the GNU GPL, with none of the additional permissions of
 62+ this License applicable to that copy.
 63+
 64+ 3. Object Code Incorporating Material from Library Header Files.
 65+
 66+ The object code form of an Application may incorporate material from
 67+a header file that is part of the Library. You may convey such object
 68+code under terms of your choice, provided that, if the incorporated
 69+material is not limited to numerical parameters, data structure
 70+layouts and accessors, or small macros, inline functions and templates
 71+(ten or fewer lines in length), you do both of the following:
 72+
 73+ a) Give prominent notice with each copy of the object code that the
 74+ Library is used in it and that the Library and its use are
 75+ covered by this License.
 76+
 77+ b) Accompany the object code with a copy of the GNU GPL and this license
 78+ document.
 79+
 80+ 4. Combined Works.
 81+
 82+ You may convey a Combined Work under terms of your choice that,
 83+taken together, effectively do not restrict modification of the
 84+portions of the Library contained in the Combined Work and reverse
 85+engineering for debugging such modifications, if you also do each of
 86+the following:
 87+
 88+ a) Give prominent notice with each copy of the Combined Work that
 89+ the Library is used in it and that the Library and its use are
 90+ covered by this License.
 91+
 92+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
 93+ document.
 94+
 95+ c) For a Combined Work that displays copyright notices during
 96+ execution, include the copyright notice for the Library among
 97+ these notices, as well as a reference directing the user to the
 98+ copies of the GNU GPL and this license document.
 99+
 100+ d) Do one of the following:
 101+
 102+ 0) Convey the Minimal Corresponding Source under the terms of this
 103+ License, and the Corresponding Application Code in a form
 104+ suitable for, and under terms that permit, the user to
 105+ recombine or relink the Application with a modified version of
 106+ the Linked Version to produce a modified Combined Work, in the
 107+ manner specified by section 6 of the GNU GPL for conveying
 108+ Corresponding Source.
 109+
 110+ 1) Use a suitable shared library mechanism for linking with the
 111+ Library. A suitable mechanism is one that (a) uses at run time
 112+ a copy of the Library already present on the user's computer
 113+ system, and (b) will operate properly with a modified version
 114+ of the Library that is interface-compatible with the Linked
 115+ Version.
 116+
 117+ e) Provide Installation Information, but only if you would otherwise
 118+ be required to provide such information under section 6 of the
 119+ GNU GPL, and only to the extent that such information is
 120+ necessary to install and execute a modified version of the
 121+ Combined Work produced by recombining or relinking the
 122+ Application with a modified version of the Linked Version. (If
 123+ you use option 4d0, the Installation Information must accompany
 124+ the Minimal Corresponding Source and Corresponding Application
 125+ Code. If you use option 4d1, you must provide the Installation
 126+ Information in the manner specified by section 6 of the GNU GPL
 127+ for conveying Corresponding Source.)
 128+
 129+ 5. Combined Libraries.
 130+
 131+ You may place library facilities that are a work based on the
 132+Library side by side in a single library together with other library
 133+facilities that are not Applications and are not covered by this
 134+License, and convey such a combined library under terms of your
 135+choice, if you do both of the following:
 136+
 137+ a) Accompany the combined library with a copy of the same work based
 138+ on the Library, uncombined with any other library facilities,
 139+ conveyed under the terms of this License.
 140+
 141+ b) Give prominent notice with the combined library that part of it
 142+ is a work based on the Library, and explaining where to find the
 143+ accompanying uncombined form of the same work.
 144+
 145+ 6. Revised Versions of the GNU Lesser General Public License.
 146+
 147+ The Free Software Foundation may publish revised and/or new versions
 148+of the GNU Lesser General Public License from time to time. Such new
 149+versions will be similar in spirit to the present version, but may
 150+differ in detail to address new problems or concerns.
 151+
 152+ Each version is given a distinguishing version number. If the
 153+Library as you received it specifies that a certain numbered version
 154+of the GNU Lesser General Public License "or any later version"
 155+applies to it, you have the option of following the terms and
 156+conditions either of that published version or of any later version
 157+published by the Free Software Foundation. If the Library as you
 158+received it does not specify a version number of the GNU Lesser
 159+General Public License, you may choose any version of the GNU Lesser
 160+General Public License ever published by the Free Software Foundation.
 161+
 162+ If the Library as you received it specifies that a proxy can decide
 163+whether future versions of the GNU Lesser General Public License shall
 164+apply, that proxy's public statement of acceptance of any version is
 165+permanent authorization for you to choose that version for the
 166+Library.
Index: trunk/extensions/CentralAuth/icons/merged-empty.png
Cannot display: file marked as a binary type.
svn:mime-type = image/png
Property changes on: trunk/extensions/CentralAuth/icons/merged-empty.png
___________________________________________________________________
Name: svn:mime-type
1167 + image/png
Index: trunk/extensions/CentralAuth/icons/merged-mail.png
Cannot display: file marked as a binary type.
svn:mime-type = image/png
Property changes on: trunk/extensions/CentralAuth/icons/merged-mail.png
___________________________________________________________________
Name: svn:mime-type
2168 + image/png
Index: trunk/extensions/CentralAuth/icons/merged-password.png
Cannot display: file marked as a binary type.
svn:mime-type = image/png
Property changes on: trunk/extensions/CentralAuth/icons/merged-password.png
___________________________________________________________________
Name: svn:mime-type
3169 + image/png
Index: trunk/extensions/CentralAuth/icons/merged-primary.png
Cannot display: file marked as a binary type.
svn:mime-type = image/png
Property changes on: trunk/extensions/CentralAuth/icons/merged-primary.png
___________________________________________________________________
Name: svn:mime-type
4170 + image/png
Index: trunk/extensions/CentralAuth/icons/merged-admin.png
Cannot display: file marked as a binary type.
svn:mime-type = image/png
Property changes on: trunk/extensions/CentralAuth/icons/merged-admin.png
___________________________________________________________________
Name: svn:mime-type
5171 + image/png
Index: trunk/extensions/CentralAuth/icons/merged-login.png
Cannot display: file marked as a binary type.
svn:mime-type = image/png
Property changes on: trunk/extensions/CentralAuth/icons/merged-login.png
___________________________________________________________________
Name: svn:mime-type
6172 + image/png
Index: trunk/extensions/CentralAuth/icons/README
@@ -0,0 +1,11 @@
 2+Icons present in this folder are mostly icons from Nuvola icons set by David Vignoni:
 3+* Nuvola_apps_agent.png
 4+* Nuvola_apps_kgpg.png
 5+* Nuvola_filesystems_folder_home.png
 6+* Nuvola_apps_email.png
 7+Those files are distributed under GNU Lesser General Public License. Its text may be found in LICENSE file.
 8+
 9+Other files included are:
 10+* Recycling_symbol.svg (by Krdan Ielalir, public domain)
 11+* Symbol_support_vote.svg (by Zachary Harden, public domain)
 12+
Index: trunk/extensions/CentralAuth/icons/merged-new.png
Cannot display: file marked as a binary type.
svn:mime-type = image/png
Property changes on: trunk/extensions/CentralAuth/icons/merged-new.png
___________________________________________________________________
Name: svn:mime-type
113 + image/png
Name: svn:executable
214 + *
Index: trunk/extensions/CentralAuth/CentralAuth.i18n.php
@@ -95,9 +95,21 @@
9696 'centralauth-merge-method-empty' => 'no contributions',
9797 'centralauth-merge-method-mail' => 'confirmed by e-mail',
9898 'centralauth-merge-method-password' => 'confirmed by password',
99 - 'centralauth-merge-method-admin' => 'admin merged account',
 99+ 'centralauth-merge-method-admin' => 'admin-merged account',
100100 'centralauth-merge-method-new' => 'new account',
101 - 'centralauth-merge-method-login' => 'confirmed by login',
 101+ 'centralauth-merge-method-login' => 'created on login',
 102+ 'centralauth-merge-method-primary-desc' => 'This is the wiki at which the account was initially merged.
 103+Note that it may differ from the real home wiki of that user.',
 104+ 'centralauth-merge-method-empty-desc' => 'Indicates that the local account was merged because it had no edits.',
 105+ 'centralauth-merge-method-mail-desc' => 'Indicates that the local account was merged because its email address matched
 106+the email address of the main account',
 107+ 'centralauth-merge-method-password-desc' => 'Indicates that the local account was merged because user specified a valid
 108+password for it.',
 109+ 'centralauth-merge-method-admin-desc' => 'Indicates that the local account was merged manually by stewards. That feature
 110+is disabled now because of security reasons.',
 111+ 'centralauth-merge-method-new-desc' => 'Indicates that the global account was created automatically when that local
 112+account was created.',
 113+ 'centralauth-merge-method-login-desc' => 'Indicates that the local account was created automatically when user logged in.',
102114
103115 // When not complete, offer to finish...
104116 'centralauth-finish-title' => 'Finish merge',
@@ -122,30 +134,37 @@
123135 'centralauth-attach-success' => 'The account was migrated to the unified account.',
124136
125137 // Administrator's console
126 - 'centralauth' => 'Unified login administration',
 138+ 'centralauth' => 'Global user manager',
127139 'centralauth-admin-intro' => 'This interface can be used for administration of global accounts.',
128140 'centralauth-admin-manage' => 'Manage user data',
129141 'centralauth-admin-username' => 'User name:',
130 - 'centralauth-admin-lookup' => 'View or edit user data',
 142+ 'centralauth-admin-lookup-ro' => 'View user info',
 143+ 'centralauth-admin-lookup-rw' => 'Manage user account',
131144 'centralauth-admin-permission' => "Only stewards may merge other people's accounts for them.",
132145 'centralauth-admin-no-unified' => 'No unified account for this username.',
 146+ 'centralauth-admin-info-header' => 'Global user info',
133147 'centralauth-admin-info-id' => 'User ID:',
134148 'centralauth-admin-info-registered' => 'Registered:',
 149+ 'centralauth-admin-info-home' => 'Home wiki:',
 150+ 'centralauth-admin-info-editcount' => 'Total editcount:',
135151 'centralauth-admin-info-locked' => 'Locked:',
136 - 'centralauth-admin-info-hidden' => 'Hidden:',
 152+ 'centralauth-admin-info-hidden' => 'Hidden level:',
137153 'centralauth-admin-yes' => 'yes',
138154 'centralauth-admin-no' => 'no',
139 - 'centralauth-admin-attached' => 'Fully merged accounts',
140 - 'centralauth-admin-unattached' => 'Unattached accounts',
141 - 'centralauth-admin-no-unattached' => 'No unmerged accounts remain.',
142 - 'centralauth-admin-notblocked' => 'Not blocked',
 155+ 'centralauth-admin-hidden-list' => 'from public lists',
 156+ 'centralauth-admin-hidden-oversight' => 'oversighted',
 157+ 'centralauth-admin-list-legend-ro' => 'List of local accounts',
 158+ 'centralauth-admin-list-legend-rw' => 'Unmerge local accounts',
 159+ 'centralauth-admin-unattached' => 'not attached',
 160+ 'centralauth-admin-notblocked' => '—',
143161 'centralauth-admin-blocked' => 'Blocked, expires $3 at $4. Reason: $2',
144162 'centralauth-admin-blocked-indef' => 'Blocked indefinitely. Reason: $1',
 163+ 'centralauth-admin-blocklog' => 'block log',
145164 'centralauth-admin-list-localwiki' => 'Local wiki',
146165 'centralauth-admin-list-attached-on' => 'Attached on',
147166 'centralauth-admin-list-method' => 'Method',
148167 'centralauth-admin-list-editcount' => 'Edit count',
149 - 'centralauth-admin-list-blocked' => 'Blocked status',
 168+ 'centralauth-admin-list-blocked' => 'Blocked',
150169 'centralauth-admin-unmerge' => 'Unmerge selected',
151170 'centralauth-admin-merge' => 'Merge selected',
152171 'centralauth-admin-bad-input' => 'Invalid merge selection',
@@ -162,15 +181,29 @@
163182 'centralauth-admin-delete-nonexistent' => 'Error: the global account "<nowiki>$1</nowiki>" does not exist.',
164183 'centralauth-token-mismatch' => 'Sorry, we could not process your form submission due to a loss of session data.',
165184 'centralauth-admin-reason' => 'Reason:',
 185+ 'centralauth-admin-reason-other' => 'Other/additional reason:',
166186 'centralauth-admin-status' => 'Set global account status',
167 - 'centralauth-admin-status-intro' => 'You can use this form to change the status of this global account',
168 - 'centralauth-admin-status-locked' => 'Locked',
169 - 'centralauth-admin-status-hidden' => 'Hidden',
 187+ 'centralauth-admin-status-intro' => 'You can use this form to change the status of this global account.',
 188+ 'centralauth-admin-status-locked' => 'Locked:',
 189+ 'centralauth-admin-status-locked-no' => 'Account is not locked',
 190+ 'centralauth-admin-status-locked-yes' => 'Account is locked from editing',
 191+ 'centralauth-admin-status-hidden' => 'Hidden:',
 192+ 'centralauth-admin-status-hidden-no' => 'Account is not hidden',
 193+ 'centralauth-admin-status-hidden-list' => 'Account is hidden from public lists',
 194+ 'centralauth-admin-status-hidden-oversight' => 'Account is hidden completely',
170195 'centralauth-admin-status-submit' => 'Set status',
171196 'centralauth-admin-status-nonexistent' => 'Error: the global account "<nowiki>$1</nowiki>" does not exist.',
172197 'centralauth-admin-setstatus-success' => 'You have successfully changed the status of this global account.',
173 -
 198+ 'centralauth-admin-status-reasons' => '* Common lock reasons
 199+** vandalism-only account
 200+** spam-only account
 201+* Common lock-and-hide reasons
 202+** abusive user name
 203+** inappropriate personal information',
 204+ 'centralauth-admin-logsnippet' => 'Previous global account changes',
 205+ 'centralauth-admin-suppressreason' => 'Globally suppressed by $1 for following reason: $2',
174206
 207+
175208 // List of global users
176209 'globalusers' => 'Global user list',
177210 'centralauth-listusers-locked' => 'locked',
@@ -208,19 +241,21 @@
209242 'centralauth-logout-progress' => 'Logging you out from other projects of {{int:Centralauth-groupname}}:',
210243 'centralauth-login-no-others' => 'You have been automatically logged into other projects of {{int:Centralauth-groupname}}.',
211244 'centralauth-logout-no-others' => 'You have been automatically logged out of other projects of {{int:Centralauth-groupname}}.',
 245+ 'centralauth-hidden-blockreason' => 'globally hidden by $1 at $2 with following reason: $3',
212246
213247 // Logging
214248 'centralauth-log-name' => 'Global account log',
215249 'centralauth-log-header' => 'This log contains operations under global accounts: deletions, locking and unlocking.',
216 - 'centralauth-log-entry-delete' => 'deleted global account "<nowiki>$1</nowiki>"',
217 - 'centralauth-log-entry-lock' => 'locked global account "<nowiki>$1</nowiki>"',
218 - 'centralauth-log-entry-unlock' => 'unlocked global account "<nowiki>$1</nowiki>"',
219 - 'centralauth-log-entry-hide' => 'hid global account "<nowiki>$1</nowiki>"',
220 - 'centralauth-log-entry-unhide' => 'unhid global account "<nowiki>$1</nowiki>"',
221 - 'centralauth-log-entry-lockandhide' => 'locked and hid global account "<nowiki>$1</nowiki>"',
222 - 'centralauth-log-entry-chgstatus' => 'changed status for global account "<nowiki>$1</nowiki>": Set $2; Unset $3',
223 - 'centralauth-log-status-locked' => 'locked',
224 - 'centralauth-log-status-hidden' => 'hidden',
 250+ 'centralauth-log-entry-delete' => 'deleted global account "$1"',
 251+ 'centralauth-log-entry-lock' => 'locked global account "$1"',
 252+ 'centralauth-log-entry-unlock' => 'unlocked global account "$1"',
 253+ 'centralauth-log-entry-hide' => 'hid global account "$1"',
 254+ 'centralauth-log-entry-unhide' => 'unhid global account "$1"',
 255+ 'centralauth-log-entry-lockandhide' => 'locked and hid global account "$1"',
 256+ 'centralauth-log-entry-chgstatus' => 'changed status for global account "$1": Set $2; Unset $3',
 257+ 'centralauth-log-status-locked' => 'locked',
 258+ 'centralauth-log-status-hidden' => 'hidden',
 259+ 'centralauth-log-status-oversighted' => 'oversighted',
225260 'centralauth-log-status-none' => '(none)',
226261
227262 'centralauth-rightslog-name' => 'Global rights log',
@@ -313,7 +348,9 @@
314349
315350 // User rights
316351 'right-globalgroupmembership' => 'Edit membership to global groups',
317 - 'right-centralauth-admin' => 'Administrate global accounts',
 352+ 'right-centralauth-unmerge' => 'Unmerge global account',
 353+ 'right-centralauth-lock' => 'Lock or hide global account',
 354+ 'right-centralauth-oversight' => 'Suppress global account',
318355 'right-centralauth-merge' => 'Merge their account',
319356 'right-globalgrouppermissions' => 'Manage global groups',
320357 );
@@ -15853,6 +15890,7 @@
1585415891 'centralauth-admin-status-submit' => 'Установить статус',
1585515892 'centralauth-admin-status-nonexistent' => 'Ошибка. Глобальной учётной записи «<nowiki>$1</nowiki>» не существует.',
1585615893 'centralauth-admin-setstatus-success' => 'Вы успешно изменили статус этой глобальной учётной записи.',
 15894+ 'centralauth-admin-suppressreason' => 'Глобально скрыт $1 со следующим обоснованием: $2',
1585715895 'globalusers' => 'Глобальный список участников',
1585815896 'centralauth-listusers-locked' => 'заморозить',
1585915897 'centralauth-listusers-attached' => 'существует локально',

Follow-up revisions

RevisionCommit summaryAuthorDate
r61740Follow-up r61737: Make 'centralauth-admin-notblocked' optional for translatewikiraymond07:15, 31 January 2010
r62827Fix r61737 (mostly per Tim Starling's comments):...vasilievvv13:06, 22 February 2010
r64771Make Special:CentralAuth work with the pre-r61737 schema.tstarling02:30, 9 April 2010
r64772MFT r64771: Make Special:CentralAuth work with the pre-r61737 schema.tstarling02:32, 9 April 2010

Comments

#Comment by Tim Starling (talk | contribs)   05:00, 15 February 2010

Please add a title attribute to the icon images, so that an explanatory tooltip will pop up when the user hovers the cursor over it. This matches normal expectations.

I wonder if you considered using the Crystal icons. I think they are nice. I've used them for a few things in MediaWiki.


centralauth.js

Pollutes the global namespace with unprefixed names like "cursor". Probably breaks other extensions. Lines go out to column 133, they need to be broken. It looks like you've copied this code from some JS snippet website, they aren't known for quality software engineering.

patch-gu_hidden.sql

+	ADD INDEX gu_locked( gu_locked ),
+	ADD INDEX gu_hidden( gu_hidden(255) );

These indexes cannot efficiently give you a list of locked or hidden users, since they don't contain anything to sort on. This means that if you have a long list, with thousands of entries, paging cannot be efficiently implemented. You probably want to append gu_name.

CentralAuthUser.php

for( $jobCount = 0; $jobCount < count( $this->mAttachedArray ); $jobCount += $step ) {
	$length = $jobCount + $step > count( $this->mAttachedArray ) ?
		$jobCount - $step : $step;
	$jobParams['wikis'] = array_slice( $this->mAttachedArray, $jobCount, $length );

If I were you I'd just use array_chunk(). Simulating array_chunk() with DIY code tends to be easy to get wrong, there are lots of edge cases.

// FIXME: this will give users "password incorrect" error.
// Giving correct message requires AuthPlugin and SpecialUserlogin
// rewriting.

A fine idea. Maybe you can fix bug 12206 while you're at it.

CentralAuth.php

-// UDP logging stuff
+/**
+ * UPD-transmissed RC settings
+ */

I suggest "Settings for sending the CentralAuth log to the RC-to-UDP system"

SpecialCentralAuth.php

+		foreach( $attribs as $tag => $data ) {
+			$out .= '<p><strong>' . wfMsg( "centralauth-admin-info-$tag" ) . '</strong> ' . $data . '</p>';
 		}

It's missing htmlspecialchars() around $data. The old code used Xml::element() which was fine. You've effectively changed these messages and the raw data from plain text to HTML with this change.

+		$legend = $this->mCanUnmerge ?
+			wfMsg( 'centralauth-admin-list-legend-rw' ) :
+			wfMsg( 'centralauth-admin-list-legend-ro' );

Again, these messages should be plain text not HTML. Legends are usually plain text.

-			$wgOut->addWikiMsg( 'centralauth-admin-no-unattached' );
...
+		$notMerged = wfMsg( 'centralauth-admin-unattached' );
...
+				"</td><td colspan='4'>{$notMerged}</td></tr>\n";

This changes centralauth-admin-no-unattached from wikitext to HTML.

+		$img = "{$wgExtensionAssetsPath}/CentralAuth/icons/merged-{$method}.png";
+		$brief = wfMsgHtml( "centralauth-merge-method-{$method}" );
+		return "<img src=\"{$img}\" alt=\"{$brief}\" />" .
+			"<span class=\"merge-method-help\" title=\"{$brief}\" onclick=\"showMethodHint('{$method}')\">(?)</span>";

Lacks escaping for $img. The question mark should be localisable.

To be continued.

#Comment by VasilievVV (talk | contribs)   16:08, 22 February 2010

Most of issues mentioned there are fixed in r62827.

I'm aware of Crysral icons. I used Nuvola icons just because I like them more: they look less saturated (not sure if it's correct word to use here). Feel free to change them if you believe they should be changed.

I have no ideas how to place div at the current cursor's position other way than determining its position (should I have reinvented the wheel there?) and placing div there using position:absolute.

#Comment by Tim Starling (talk | contribs)   07:52, 15 February 2010

+ // Should not happen. + return ;

If it shouldn't happen, then you should at least send something to the debug log when it does, so that whoever hits this subtle bug will be able to work out what's going on. Alternatively you could throw an exception.

+ $short = Xml::encodeJsVar( $wgLang->ucFirst( wfMsgHtml( "centralauth-merge-method-{$method}" ) ) );

ucfirst is spelt wrong (with a capital F).

If account locking doesn't prevent autocreate, then I'm not really sure what the point of it is. It falls short as a blocking feature, since it's missing many features that blocking has. On Wikimedia we have GlobalBlocking, and I think the way that hooks in is much better, the only problem with it is that it only blocks by IP address. Have you considered patching GlobalBlocking to allow it to block global names rather than just IP addresses? Or have you looked at it for ideas?

#Comment by VasilievVV (talk | contribs)   15:55, 22 February 2010

"Should not happen" code throws exception as for r62827. ucfirst spelling is fixed there as well.

Account locking was (and is) supposed to be a temporary substitude for blocking. I may cleanup blocking mechanism from r35928 and commit it again; am I allowed to do it during current pre-release freeze?

#Comment by Tim Starling (talk | contribs)   02:25, 23 February 2010

We are not in pre-release freeze, you can commit new features if you want.

#Comment by Tim Starling (talk | contribs)   23:35, 25 March 2010

This is a particularly tricky deployment. The table has 5.5M rows, so we can send it down via the master, but it might take 10 minutes or so.

The b/c case:

+ // b/c-failsafe. Should never happen + if( $this->mHidden === '0' ) + $this->mHidden = ;

will in fact happen quite a lot. Not only do we need this, we will also need handling for $this->mHidden == '1', to be interpreted as HIDDEN_LISTS, which will also happen quite a lot. Then the following will be possible:

  1. Switch to read-only mode on all wikis
  2. Switch the code
  3. Run the SQL patch file
  4. Switch back to read/write

It's not possible to run the old code while the SQL patch file is running, because post-upgrade, the SELECT conditions in the old SpecialCentralAuth will treat all users as hidden.

Because of the complexity and thus the risks involved, it would be best if we do this separately to the rest of the 1.16wmf4 code update.

#Comment by Tim Starling (talk | contribs)   02:42, 9 April 2010

Running the new code on the old schema in r/w mode for a short time should be possible. I've tested the queries used for CentralAuthUser::register() and CentralAuthUser::saveSettings(), attempting to set gu_hidden= leads to conversion to 0. Saving settings with gu_hidden='lists' also leads to conversion to zero so hidden users will become unhidden if they change their settings during this time.

We can run like this for half an hour or so if it's necessary to fix any higher priority bugs that come up during deployment.

#Comment by Platonides (talk | contribs)   14:38, 3 May 2010

I don't think merged-login.png should be an empty png.

#Comment by VasilievVV (talk | contribs)   04:01, 4 May 2010

Bugzilla is waiting for your suggestions.

Status & tagging log