Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 658 |
|
0.00% |
0 / 34 |
CRAP | |
0.00% |
0 / 1 |
SpecialCentralAuth | |
0.00% |
0 / 658 |
|
0.00% |
0 / 34 |
19182 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
2 | |||
doesWrites | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
0.00% |
0 / 80 |
|
0.00% |
0 / 1 |
600 | |||
showNonexistentError | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
showRenameInProgressError | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
doSubmit | |
0.00% |
0 / 44 |
|
0.00% |
0 / 1 |
272 | |||
showStatusError | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 | |||
showError | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
showSuccess | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
showUsernameForm | |
0.00% |
0 / 25 |
|
0.00% |
0 / 1 |
12 | |||
showInfo | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
12 | |||
getInfoFields | |
0.00% |
0 / 78 |
|
0.00% |
0 / 1 |
380 | |||
showWikiLists | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
20 | |||
listHeader | |
0.00% |
0 / 29 |
|
0.00% |
0 / 1 |
20 | |||
listFooter | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
listAccounts | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
listWikiItem | |
0.00% |
0 / 28 |
|
0.00% |
0 / 1 |
30 | |||
getAttachedTimestampField | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
6 | |||
formatMergeMethod | |
0.00% |
0 / 19 |
|
0.00% |
0 / 1 |
2 | |||
formatBlockStatus | |
0.00% |
0 / 30 |
|
0.00% |
0 / 1 |
30 | |||
formatBlockParams | |
0.00% |
0 / 31 |
|
0.00% |
0 / 1 |
72 | |||
getRestrictionListHTML | |
0.00% |
0 / 28 |
|
0.00% |
0 / 1 |
20 | |||
formatEditcount | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
6 | |||
formatGroups | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
20 | |||
foreignLink | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
20 | |||
foreignUserLink | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
6 | |||
adminCheck | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
showActionForm | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
2 | |||
showStatusForm | |
0.00% |
0 / 71 |
|
0.00% |
0 / 1 |
6 | |||
showLogExtract | |
0.00% |
0 / 31 |
|
0.00% |
0 / 1 |
42 | |||
evaluateTotalEditcount | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
getMergeMethodDescriptions | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
6 | |||
prefixSearchSubpages | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
6 | |||
getGroupName | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\CentralAuth\Special; |
4 | |
5 | use DateInterval; |
6 | use DerivativeContext; |
7 | use Exception; |
8 | use HTMLForm; |
9 | use InvalidArgumentException; |
10 | use LogEventsList; |
11 | use MediaWiki\Block\Restriction\ActionRestriction; |
12 | use MediaWiki\Block\Restriction\NamespaceRestriction; |
13 | use MediaWiki\Block\Restriction\PageRestriction; |
14 | use MediaWiki\CommentFormatter\CommentFormatter; |
15 | use MediaWiki\Extension\CentralAuth\CentralAuthDatabaseManager; |
16 | use MediaWiki\Extension\CentralAuth\CentralAuthUIService; |
17 | use MediaWiki\Extension\CentralAuth\GlobalRename\GlobalRenameFactory; |
18 | use MediaWiki\Extension\CentralAuth\User\CentralAuthGlobalRegistrationProvider; |
19 | use MediaWiki\Extension\CentralAuth\User\CentralAuthUser; |
20 | use MediaWiki\Extension\CentralAuth\Widget\HTMLGlobalUserTextField; |
21 | use MediaWiki\Html\Html; |
22 | use MediaWiki\Parser\Sanitizer; |
23 | use MediaWiki\SpecialPage\SpecialPage; |
24 | use MediaWiki\Title\NamespaceInfo; |
25 | use MediaWiki\Title\Title; |
26 | use MediaWiki\User\Registration\UserRegistrationLookup; |
27 | use MediaWiki\User\TempUser\TempUserConfig; |
28 | use MediaWiki\User\User; |
29 | use MediaWiki\User\UserFactory; |
30 | use MediaWiki\User\UserGroupMembership; |
31 | use MediaWiki\User\UserNameUtils; |
32 | use MediaWiki\Utils\MWTimestamp; |
33 | use MediaWiki\WikiMap\WikiMap; |
34 | use MediaWiki\WikiMap\WikiReference; |
35 | use Wikimedia\Rdbms\IExpression; |
36 | use Wikimedia\Rdbms\LikeValue; |
37 | use Wikimedia\Rdbms\ReadOnlyMode; |
38 | use Xml; |
39 | |
40 | class SpecialCentralAuth extends SpecialPage { |
41 | /** @var string */ |
42 | private $mUserName; |
43 | /** @var bool */ |
44 | private $mCanUnmerge; |
45 | /** @var bool */ |
46 | private $mCanLock; |
47 | /** @var bool */ |
48 | private $mCanSuppress; |
49 | /** @var bool */ |
50 | private $mCanEdit; |
51 | /** @var bool */ |
52 | private $mCanChangeGroups; |
53 | |
54 | /** |
55 | * @var CentralAuthUser |
56 | */ |
57 | private $mGlobalUser; |
58 | |
59 | /** |
60 | * @var array[] |
61 | */ |
62 | private $mAttachedLocalAccounts; |
63 | |
64 | /** |
65 | * @var array |
66 | */ |
67 | private $mUnattachedLocalAccounts; |
68 | |
69 | /** @var string */ |
70 | private $mMethod; |
71 | |
72 | /** @var bool */ |
73 | private $mPosted; |
74 | |
75 | /** @var string[] */ |
76 | private $mWikis; |
77 | |
78 | private CommentFormatter $commentFormatter; |
79 | private NamespaceInfo $namespaceInfo; |
80 | private ReadOnlyMode $readOnlyMode; |
81 | private TempUserConfig $tempUserConfig; |
82 | private UserFactory $userFactory; |
83 | private UserNameUtils $userNameUtils; |
84 | private UserRegistrationLookup $userRegistrationLookup; |
85 | private CentralAuthDatabaseManager $databaseManager; |
86 | private CentralAuthUIService $uiService; |
87 | private GlobalRenameFactory $globalRenameFactory; |
88 | |
89 | /** |
90 | * @param CommentFormatter $commentFormatter |
91 | * @param NamespaceInfo $namespaceInfo |
92 | * @param ReadOnlyMode $readOnlyMode |
93 | * @param TempUserConfig $tempUserConfig |
94 | * @param UserFactory $userFactory |
95 | * @param UserNameUtils $userNameUtils |
96 | * @param UserRegistrationLookup $userRegistrationLookup |
97 | * @param CentralAuthDatabaseManager $databaseManager |
98 | * @param CentralAuthUIService $uiService |
99 | * @param GlobalRenameFactory $globalRenameFactory |
100 | */ |
101 | public function __construct( |
102 | CommentFormatter $commentFormatter, |
103 | NamespaceInfo $namespaceInfo, |
104 | ReadOnlyMode $readOnlyMode, |
105 | TempUserConfig $tempUserConfig, |
106 | UserFactory $userFactory, |
107 | UserNameUtils $userNameUtils, |
108 | UserRegistrationLookup $userRegistrationLookup, |
109 | CentralAuthDatabaseManager $databaseManager, |
110 | CentralAuthUIService $uiService, |
111 | GlobalRenameFactory $globalRenameFactory |
112 | ) { |
113 | parent::__construct( 'CentralAuth' ); |
114 | $this->commentFormatter = $commentFormatter; |
115 | $this->namespaceInfo = $namespaceInfo; |
116 | $this->readOnlyMode = $readOnlyMode; |
117 | $this->tempUserConfig = $tempUserConfig; |
118 | $this->userFactory = $userFactory; |
119 | $this->userNameUtils = $userNameUtils; |
120 | $this->userRegistrationLookup = $userRegistrationLookup; |
121 | $this->databaseManager = $databaseManager; |
122 | $this->uiService = $uiService; |
123 | $this->globalRenameFactory = $globalRenameFactory; |
124 | } |
125 | |
126 | public function doesWrites() { |
127 | return true; |
128 | } |
129 | |
130 | /** @inheritDoc */ |
131 | public function execute( $subpage ) { |
132 | $this->setHeaders(); |
133 | $this->addHelpLink( 'Extension:CentralAuth' ); |
134 | |
135 | $authority = $this->getContext()->getAuthority(); |
136 | $this->mCanUnmerge = $authority->isAllowed( 'centralauth-unmerge' ); |
137 | $this->mCanLock = $authority->isAllowed( 'centralauth-lock' ); |
138 | $this->mCanSuppress = $authority->isAllowed( 'centralauth-suppress' ); |
139 | $this->mCanEdit = $this->mCanUnmerge || $this->mCanLock || $this->mCanSuppress; |
140 | $this->mCanChangeGroups = $authority->isAllowed( 'globalgroupmembership' ); |
141 | |
142 | $this->getOutput()->setPageTitleMsg( |
143 | $this->msg( $this->mCanEdit ? 'centralauth' : 'centralauth-ro' ) |
144 | ); |
145 | $this->getOutput()->addModules( 'ext.centralauth' ); |
146 | $this->getOutput()->addModuleStyles( 'ext.centralauth.misc.styles' ); |
147 | $this->getOutput()->addJsConfigVars( |
148 | 'wgMergeMethodDescriptions', $this->getMergeMethodDescriptions() |
149 | ); |
150 | |
151 | $this->mUserName = trim( |
152 | str_replace( |
153 | '_', |
154 | ' ', |
155 | $this->getRequest()->getText( 'target', $subpage ?? '' ) |
156 | ) |
157 | ); |
158 | |
159 | $this->mUserName = $this->getContentLanguage()->ucfirst( $this->mUserName ); |
160 | |
161 | $this->mPosted = $this->getRequest()->wasPosted(); |
162 | $this->mMethod = $this->getRequest()->getVal( 'wpMethod' ); |
163 | $this->mWikis = (array)$this->getRequest()->getArray( 'wpWikis' ); |
164 | |
165 | // Possible demo states |
166 | |
167 | // success, all accounts merged |
168 | // successful login, some accounts merged, others left |
169 | // successful login, others left |
170 | // not account owner, others left |
171 | |
172 | // is owner / is not owner |
173 | // did / did not merge some accounts |
174 | // do / don't have more accounts to merge |
175 | |
176 | if ( $this->mUserName === '' ) { |
177 | # First time through |
178 | $this->getOutput()->addWikiMsg( 'centralauth-admin-intro' ); |
179 | $this->showUsernameForm(); |
180 | return; |
181 | } |
182 | |
183 | $userPage = Title::makeTitleSafe( NS_USER, $this->mUserName ); |
184 | if ( $userPage ) { |
185 | $localUser = User::newFromName( $userPage->getText(), false ); |
186 | $this->getSkin()->setRelevantUser( $localUser ); |
187 | } |
188 | |
189 | // per T49991 |
190 | $this->getOutput()->setHTMLTitle( $this->msg( |
191 | 'pagetitle', |
192 | $this->msg( |
193 | $this->mCanEdit ? 'centralauth-admin-title' : 'centralauth-admin-title-ro', |
194 | $this->mUserName |
195 | )->plain() |
196 | ) ); |
197 | |
198 | $canonUsername = $this->userNameUtils->getCanonical( $this->mUserName ); |
199 | if ( $canonUsername === false ) { |
200 | $this->showNonexistentError(); |
201 | return; |
202 | } |
203 | |
204 | $globalUser = $this->getRequest()->wasPosted() |
205 | ? CentralAuthUser::getPrimaryInstanceByName( $this->mUserName ) |
206 | : CentralAuthUser::getInstanceByName( $this->mUserName ); |
207 | $this->mGlobalUser = $globalUser; |
208 | |
209 | if ( ( $globalUser->isSuppressed() || $globalUser->isHidden() ) && |
210 | !$this->mCanSuppress |
211 | ) { |
212 | // Claim that there's nothing if the global account is hidden and the user is not |
213 | // allowed to see it. |
214 | $this->showNonexistentError(); |
215 | return; |
216 | } |
217 | |
218 | $continue = true; |
219 | if ( $this->mCanEdit && $this->mPosted ) { |
220 | $this->databaseManager->assertNotReadOnly(); |
221 | $continue = $this->doSubmit(); |
222 | } |
223 | |
224 | // Show just a user friendly message when a rename is in progress |
225 | try { |
226 | $this->mAttachedLocalAccounts = $globalUser->queryAttached(); |
227 | } catch ( Exception $e ) { |
228 | if ( $globalUser->renameInProgress() ) { |
229 | $this->showRenameInProgressError(); |
230 | return; |
231 | } |
232 | // Rethrow |
233 | throw $e; |
234 | } |
235 | |
236 | $this->mUnattachedLocalAccounts = $globalUser->queryUnattached(); |
237 | |
238 | if ( !$globalUser->exists() && !count( $this->mUnattachedLocalAccounts ) ) { |
239 | // Nothing to see here |
240 | $this->showNonexistentError(); |
241 | return; |
242 | } |
243 | |
244 | $this->showUsernameForm(); |
245 | if ( $continue && $globalUser->exists() ) { |
246 | $this->showInfo(); |
247 | if ( $this->mCanLock ) { |
248 | $this->showStatusForm(); |
249 | } |
250 | if ( $this->mCanUnmerge ) { |
251 | $this->showActionForm( 'delete' ); |
252 | } |
253 | $this->showLogExtract(); |
254 | $this->showWikiLists(); |
255 | } elseif ( $continue && !$globalUser->exists() ) { |
256 | // No global account, but we can still list the local ones |
257 | $this->showError( 'centralauth-admin-nonexistent', $this->mUserName ); |
258 | $this->showWikiLists(); |
259 | } |
260 | } |
261 | |
262 | private function showNonexistentError() { |
263 | $this->showError( 'centralauth-admin-nonexistent', $this->mUserName ); |
264 | $this->showUsernameForm(); |
265 | } |
266 | |
267 | private function showRenameInProgressError() { |
268 | $this->showError( 'centralauth-admin-rename-in-progress', $this->mUserName ); |
269 | $renameStatus = $this->globalRenameFactory->newGlobalRenameUserStatus( $this->mUserName ); |
270 | $names = $renameStatus->getNames(); |
271 | $this->uiService->showRenameLogExtract( $this->getContext(), $names[1] ); |
272 | } |
273 | |
274 | /** |
275 | * @return bool Returns true if the normal form should be displayed |
276 | */ |
277 | public function doSubmit() { |
278 | $deleted = false; |
279 | $globalUser = $this->mGlobalUser; |
280 | $request = $this->getRequest(); |
281 | |
282 | $givenState = $request->getVal( 'wpUserState' ); |
283 | $stateCheck = $givenState === $globalUser->getStateHash( true ); |
284 | |
285 | if ( !$this->getUser()->matchEditToken( $request->getVal( 'wpEditToken' ) ) ) { |
286 | $this->showError( 'centralauth-token-mismatch' ); |
287 | } elseif ( $this->mMethod == 'unmerge' && $this->mCanUnmerge ) { |
288 | $status = $globalUser->adminUnattach( $this->mWikis ); |
289 | if ( !$status->isGood() ) { |
290 | $this->showStatusError( $status->getWikiText() ); |
291 | } else { |
292 | $this->showSuccess( 'centralauth-admin-unmerge-success', |
293 | $this->getLanguage()->formatNum( $status->successCount ), |
294 | /* deprecated */ $status->successCount ); |
295 | } |
296 | } elseif ( $this->mMethod == 'delete' && $this->mCanUnmerge ) { |
297 | $status = $globalUser->adminDelete( $request->getVal( 'reason' ), $this->getUser() ); |
298 | if ( !$status->isGood() ) { |
299 | $this->showStatusError( $status->getWikiText() ); |
300 | } else { |
301 | $this->showSuccess( 'centralauth-admin-delete-success', $this->mUserName ); |
302 | $deleted = true; |
303 | } |
304 | } elseif ( $this->mMethod == 'set-status' && !$stateCheck ) { |
305 | $this->showError( 'centralauth-state-mismatch' ); |
306 | } elseif ( $this->mMethod == 'set-status' && $this->mCanLock ) { |
307 | $setLocked = $request->getBool( 'wpStatusLocked' ); |
308 | $setHidden = $request->getInt( 'wpStatusHidden', -1 ); |
309 | $reason = $request->getText( 'wpReasonList' ); |
310 | $reasonDetail = $request->getText( 'wpReason' ); |
311 | |
312 | if ( $reason == 'other' ) { |
313 | $reason = $reasonDetail; |
314 | } elseif ( $reasonDetail ) { |
315 | $reason .= $this->msg( 'colon-separator' )->inContentLanguage()->text() . |
316 | $reasonDetail; |
317 | } |
318 | |
319 | $status = $globalUser->adminLockHide( |
320 | $setLocked, |
321 | $setHidden, |
322 | $reason, |
323 | $this->getContext() |
324 | ); |
325 | |
326 | // Tell the user what happened |
327 | if ( !$status->isGood() ) { |
328 | $this->showStatusError( $status->getWikiText() ); |
329 | } elseif ( $status->successCount > 0 ) { |
330 | $this->showSuccess( 'centralauth-admin-setstatus-success', $this->mUserName ); |
331 | } |
332 | } else { |
333 | $this->showError( 'centralauth-admin-bad-input' ); |
334 | } |
335 | return !$deleted; |
336 | } |
337 | |
338 | /** |
339 | * @param string $wikitext |
340 | */ |
341 | private function showStatusError( $wikitext ) { |
342 | $out = $this->getOutput(); |
343 | $out->addHTML( |
344 | Html::errorBox( |
345 | $out->parseAsInterface( $wikitext ) |
346 | ) |
347 | ); |
348 | } |
349 | |
350 | /** |
351 | * @param string $key |
352 | * @param mixed ...$params |
353 | */ |
354 | private function showError( $key, ...$params ) { |
355 | $this->getOutput()->addHTML( Html::errorBox( $this->msg( $key, ...$params )->parse() ) ); |
356 | } |
357 | |
358 | /** |
359 | * @param string $key |
360 | * @param mixed ...$params |
361 | */ |
362 | private function showSuccess( $key, ...$params ) { |
363 | $this->getOutput()->addHTML( Html::successBox( $this->msg( $key, ...$params )->parse() ) ); |
364 | } |
365 | |
366 | private function showUsernameForm() { |
367 | $lookup = $this->msg( |
368 | $this->mCanEdit ? 'centralauth-admin-lookup-rw' : 'centralauth-admin-lookup-ro' |
369 | )->text(); |
370 | |
371 | $formDescriptor = [ |
372 | 'user' => [ |
373 | 'class' => HTMLGlobalUserTextField::class, |
374 | 'name' => 'target', |
375 | 'label-message' => 'centralauth-admin-username', |
376 | 'size' => 25, |
377 | 'id' => 'target', |
378 | 'default' => $this->mUserName, |
379 | 'required' => true |
380 | ] |
381 | ]; |
382 | |
383 | $legend = $this->msg( $this->mCanEdit ? 'centralauth-admin-manage' : 'centralauth-admin-view' )->text(); |
384 | |
385 | $context = new DerivativeContext( $this->getContext() ); |
386 | // Remove subpage |
387 | $context->setTitle( $this->getPageTitle() ); |
388 | $htmlForm = HTMLForm::factory( 'ooui', $formDescriptor, $context ); |
389 | $htmlForm |
390 | ->setMethod( 'get' ) |
391 | ->setSubmitText( $lookup ) |
392 | ->setSubmitID( 'centralauth-submit-find' ) |
393 | ->setWrapperLegend( $legend ) |
394 | ->prepareForm() |
395 | ->displayForm( false ); |
396 | } |
397 | |
398 | private function showInfo() { |
399 | $attribs = $this->getInfoFields(); |
400 | |
401 | // Give grep a chance to find the usages: |
402 | // centralauth-admin-info-username, centralauth-admin-info-registered, |
403 | // centralauth-admin-info-editcount, centralauth-admin-info-locked, |
404 | // centralauth-admin-info-hidden, centralauth-admin-info-groups |
405 | $content = Xml::openElement( "ul" ); |
406 | foreach ( $attribs as $tag => $data ) { |
407 | $content .= Xml::openElement( "li" ) . Xml::openElement( "strong" ); |
408 | $msg = $this->msg( "centralauth-admin-info-$tag" ); |
409 | if ( $tag === 'groups' ) { |
410 | // @TODO: This special case is ugly |
411 | $msg->numParams( count( $this->mGlobalUser->getGlobalGroups() ) ); |
412 | } |
413 | $content .= $msg->escaped(); |
414 | $content .= Xml::closeElement( "strong" ) . ' ' . $data . Xml::closeElement( "li" ); |
415 | } |
416 | $content .= Xml::closeElement( "ul" ); |
417 | $out = Xml::fieldset( |
418 | $this->msg( 'centralauth-admin-info-header' )->text(), |
419 | $content, |
420 | [ "id" => "mw-centralauth-info" ] |
421 | ); |
422 | $this->getOutput()->addHTML( $out ); |
423 | } |
424 | |
425 | /** |
426 | * @return array Content is already escaped |
427 | */ |
428 | private function getInfoFields() { |
429 | $globalUser = $this->mGlobalUser; |
430 | |
431 | $reg = $globalUser->getRegistration(); |
432 | $age = $this->uiService->prettyTimespan( |
433 | $this->getContext(), |
434 | (int)wfTimestamp( TS_UNIX ) - (int)wfTimestamp( TS_UNIX, $reg ) |
435 | ); |
436 | $attribs = [ |
437 | 'username' => htmlspecialchars( $globalUser->getName() ), |
438 | 'registered' => htmlspecialchars( |
439 | $this->getLanguage()->timeanddate( $reg, true ) . " ($age)" ), |
440 | 'editcount' => htmlspecialchars( |
441 | $this->getLanguage()->formatNum( $this->evaluateTotalEditcount() ) ), |
442 | 'attached' => htmlspecialchars( |
443 | $this->getLanguage()->formatNum( count( $this->mAttachedLocalAccounts ) ) ), |
444 | ]; |
445 | |
446 | if ( |
447 | // Renaming self is not allowed. |
448 | $globalUser->getName() !== $this->getContext()->getUser()->getName() |
449 | && $this->getContext()->getAuthority()->isAllowed( 'centralauth-rename' ) |
450 | ) { |
451 | $renameLink = $this->getLinkRenderer()->makeKnownLink( |
452 | SpecialPage::getTitleFor( 'GlobalRenameUser', $globalUser->getName() ), |
453 | $this->msg( 'centralauth-admin-info-username-rename' )->text() |
454 | ); |
455 | |
456 | $attribs['username'] .= $this->msg( 'word-separator' )->escaped(); |
457 | $attribs['username'] .= $this->msg( 'parentheses' )->rawParams( $renameLink )->escaped(); |
458 | } |
459 | |
460 | if ( count( $this->mUnattachedLocalAccounts ) ) { |
461 | $attribs['unattached'] = htmlspecialchars( |
462 | $this->getLanguage()->formatNum( count( $this->mUnattachedLocalAccounts ) ) ); |
463 | } |
464 | |
465 | if ( $globalUser->isLocked() ) { |
466 | $attribs['locked'] = $this->msg( 'centralauth-admin-yes' )->escaped(); |
467 | } |
468 | |
469 | if ( $this->mCanSuppress ) { |
470 | $attribs['hidden'] = $this->uiService->formatHiddenLevel( |
471 | $this->getContext(), |
472 | $globalUser->getHiddenLevelInt() |
473 | ); |
474 | } |
475 | |
476 | if ( $this->tempUserConfig->isTempName( $globalUser->getName() ) ) { |
477 | $localUser = $this->userFactory->newFromName( $globalUser->getName() ); |
478 | // if the central user is valid, the local username is too, but Phan doesn't know that |
479 | '@phan-var User $localUser'; |
480 | $registrationDate = $this->userRegistrationLookup |
481 | ->getRegistration( $localUser, CentralAuthGlobalRegistrationProvider::TYPE ); |
482 | $expirationDays = $this->tempUserConfig->getExpireAfterDays(); |
483 | if ( $registrationDate && $expirationDays ) { |
484 | // Add one day to account for the expiration script running daily |
485 | $expirationDate = MWTimestamp::getInstance( $registrationDate ) |
486 | ->add( new DateInterval( 'P' . ( $expirationDays + 1 ) . 'D' ) ); |
487 | if ( $expirationDate->getTimestamp() < MWTimestamp::time() ) { |
488 | $attribs['expired'] = htmlspecialchars( $this->getLanguage() |
489 | ->userTimeAndDate( $expirationDate->timestamp, $localUser ) ); |
490 | } |
491 | } |
492 | } |
493 | |
494 | $groups = $globalUser->getGlobalGroupsWithExpiration(); |
495 | if ( $groups ) { |
496 | $groupLinks = []; |
497 | // Ensure temporary groups are displayed first, to avoid ambiguity like |
498 | // "first, second (expires at some point)" (unclear if only second expires or if both expire) |
499 | uasort( $groups, static function ( $first, $second ) { |
500 | if ( !$first && $second ) { |
501 | return 1; |
502 | } elseif ( $first && !$second ) { |
503 | return -1; |
504 | } else { |
505 | return 0; |
506 | } |
507 | } ); |
508 | |
509 | $uiLanguage = $this->getLanguage(); |
510 | $uiUser = $this->getUser(); |
511 | |
512 | foreach ( $groups as $group => $expiry ) { |
513 | $link = $this->getLinkRenderer()->makeLink( |
514 | SpecialPage::getTitleFor( 'GlobalGroupPermissions', $group ), |
515 | $uiLanguage->getGroupName( $group ) |
516 | ); |
517 | |
518 | if ( $expiry ) { |
519 | $link = $this->msg( 'group-membership-link-with-expiry' ) |
520 | ->rawParams( $link ) |
521 | ->params( $uiLanguage->userTimeAndDate( $expiry, $uiUser ) ) |
522 | ->escaped(); |
523 | } |
524 | |
525 | $groupLinks[] = $link; |
526 | } |
527 | |
528 | $attribs['groups'] = $uiLanguage->commaList( $groupLinks ); |
529 | } |
530 | |
531 | if ( $this->mCanChangeGroups ) { |
532 | if ( !isset( $attribs['groups'] ) ) { |
533 | $attribs['groups'] = $this->msg( 'rightsnone' )->escaped(); |
534 | } |
535 | |
536 | $manageGroupsLink = $this->getLinkRenderer()->makeKnownLink( |
537 | SpecialPage::getTitleFor( 'GlobalGroupMembership', $globalUser->getName() ), |
538 | $this->msg( 'centralauth-admin-info-groups-manage' )->text() |
539 | ); |
540 | |
541 | $attribs['groups'] .= $this->msg( 'word-separator' )->escaped(); |
542 | $attribs['groups'] .= $this->msg( 'parentheses' )->rawParams( $manageGroupsLink )->escaped(); |
543 | } |
544 | |
545 | return $attribs; |
546 | } |
547 | |
548 | private function showWikiLists() { |
549 | $merged = $this->mAttachedLocalAccounts; |
550 | $remainder = $this->mUnattachedLocalAccounts; |
551 | |
552 | $legend = $this->mCanUnmerge && $this->mGlobalUser->exists() ? |
553 | $this->msg( 'centralauth-admin-list-legend-rw' )->text() : |
554 | $this->msg( 'centralauth-admin-list-legend-ro' )->text(); |
555 | |
556 | $this->getOutput()->addHTML( Xml::fieldset( $legend ) ); |
557 | $this->getOutput()->addHTML( $this->listHeader() ); |
558 | $this->getOutput()->addHTML( $this->listAccounts( $merged ) ); |
559 | if ( $remainder ) { |
560 | $this->getOutput()->addHTML( $this->listAccounts( $remainder ) ); |
561 | } |
562 | $this->getOutput()->addHTML( $this->listFooter() ); |
563 | $this->getOutput()->addHTML( Xml::closeElement( 'fieldset' ) ); |
564 | $this->getOutput()->addModuleStyles( 'jquery.tablesorter.styles' ); |
565 | $this->getOutput()->addModules( 'jquery.tablesorter' ); |
566 | } |
567 | |
568 | /** |
569 | * @return string |
570 | */ |
571 | private function listHeader() { |
572 | $columns = [ |
573 | // centralauth-admin-list-localwiki |
574 | "localwiki", |
575 | // centralauth-admin-list-attached-on |
576 | "attached-on", |
577 | // centralauth-admin-list-method |
578 | "method", |
579 | // centralauth-admin-list-blocked |
580 | "blocked", |
581 | // centralauth-admin-list-editcount |
582 | "editcount", |
583 | // centralauth-admin-list-groups |
584 | "groups", |
585 | ]; |
586 | $header = Xml::openElement( 'form', [ |
587 | 'method' => 'post', |
588 | 'action' => |
589 | $this->getPageTitle( $this->mUserName )->getLocalUrl( 'action=submit' ), |
590 | 'id' => 'mw-centralauth-merged' |
591 | ] ); |
592 | $header .= Html::hidden( 'wpMethod', 'unmerge' ) . |
593 | Html::hidden( 'wpEditToken', $this->getUser()->getEditToken() ) . |
594 | Xml::openElement( |
595 | 'table', [ 'class' => 'wikitable sortable mw-centralauth-wikislist' ] ) . |
596 | "\n" . Xml::openElement( 'thead' ) . Xml::openElement( 'tr' ); |
597 | if ( $this->mCanUnmerge && $this->mGlobalUser->exists() ) { |
598 | $header .= Xml::openElement( 'th' ) . Xml::closeElement( 'th' ); |
599 | } |
600 | foreach ( $columns as $c ) { |
601 | $header .= Xml::openElement( 'th' ) . |
602 | $this->msg( "centralauth-admin-list-$c" )->escaped() . |
603 | Xml::closeElement( 'th' ); |
604 | } |
605 | $header .= Xml::closeElement( 'tr' ) . |
606 | Xml::closeElement( 'thead' ) . |
607 | Xml::openElement( 'tbody' ); |
608 | |
609 | return $header; |
610 | } |
611 | |
612 | /** |
613 | * @return string |
614 | */ |
615 | private function listFooter() { |
616 | $footer = Xml::closeElement( 'tbody' ) . |
617 | Xml::closeElement( 'table' ); |
618 | |
619 | if ( $this->mCanUnmerge && $this->mGlobalUser->exists() ) { |
620 | $footer .= Xml::submitButton( $this->msg( 'centralauth-admin-unmerge' )->text() ); |
621 | } |
622 | |
623 | return $footer . Xml::closeElement( 'form' ); |
624 | } |
625 | |
626 | /** |
627 | * @param array $list |
628 | * @return string |
629 | */ |
630 | private function listAccounts( array $list ) { |
631 | ksort( $list ); |
632 | return implode( "\n", array_map( [ $this, 'listWikiItem' ], $list ) ); |
633 | } |
634 | |
635 | /** |
636 | * @param array $row |
637 | * @return string |
638 | */ |
639 | private function listWikiItem( array $row ) { |
640 | $html = Xml::openElement( 'tr' ); |
641 | |
642 | if ( $this->mCanUnmerge && $this->mGlobalUser->exists() ) { |
643 | if ( !empty( $row['attachedMethod'] ) ) { |
644 | $html .= Xml::openElement( 'td' ) . |
645 | $this->adminCheck( $row['wiki'] ) . |
646 | Xml::closeElement( 'td' ); |
647 | } else { |
648 | // Account is unattached, don't show checkbox to detach |
649 | $html .= Xml::element( 'td' ); |
650 | } |
651 | } |
652 | |
653 | $html .= Xml::openElement( 'td' ) . |
654 | $this->foreignUserLink( $row['wiki'] ) . |
655 | Xml::closeElement( 'td' ); |
656 | |
657 | $attachedTimestamp = $row['attachedTimestamp'] ?? ''; |
658 | |
659 | $html .= $this->getAttachedTimestampField( $attachedTimestamp ) . |
660 | Xml::openElement( 'td', [ 'style' => "text-align: center;" ] ); |
661 | |
662 | if ( empty( $row['attachedMethod'] ) ) { |
663 | $html .= $this->msg( 'centralauth-admin-unattached' )->parse(); |
664 | } else { |
665 | $html .= $this->formatMergeMethod( $row['attachedMethod'] ); |
666 | } |
667 | |
668 | $html .= Xml::closeElement( 'td' ) . |
669 | Xml::openElement( 'td' ) . |
670 | $this->formatBlockStatus( $row ) . |
671 | Xml::closeElement( 'td' ) . |
672 | Xml::openElement( 'td', [ 'style' => "text-align: right;" ] ) . |
673 | $this->formatEditcount( $row ) . |
674 | Xml::closeElement( 'td' ) . |
675 | Xml::openElement( 'td' ) . |
676 | $this->formatGroups( $row ) . |
677 | Xml::closeElement( 'td' ) . |
678 | Xml::closeElement( 'tr' ); |
679 | |
680 | return $html; |
681 | } |
682 | |
683 | /** |
684 | * @param string|null $attachedTimestamp |
685 | * |
686 | * @return string |
687 | */ |
688 | private function getAttachedTimestampField( $attachedTimestamp ) { |
689 | if ( !$attachedTimestamp ) { |
690 | $html = Xml::openElement( 'td', [ 'data-sort-value' => '0' ] ) . |
691 | $this->msg( 'centralauth-admin-unattached' )->parse(); |
692 | } else { |
693 | $html = Xml::openElement( 'td', |
694 | [ 'data-sort-value' => $attachedTimestamp ] ) . |
695 | // visible date and time in users preference |
696 | htmlspecialchars( $this->getLanguage()->timeanddate( $attachedTimestamp, true ) ); |
697 | } |
698 | |
699 | $html .= Xml::closeElement( 'td' ); |
700 | return $html; |
701 | } |
702 | |
703 | /** |
704 | * @param string $method |
705 | * @return string |
706 | */ |
707 | private function formatMergeMethod( $method ) { |
708 | // Give grep a chance to find the usages: |
709 | // centralauth-merge-method-primary, centralauth-merge-method-empty, |
710 | // centralauth-merge-method-mail, centralauth-merge-method-password, |
711 | // centralauth-merge-method-admin, centralauth-merge-method-new, |
712 | // centralauth-merge-method-login |
713 | $brief = $this->msg( "centralauth-merge-method-{$method}" )->text(); |
714 | $html = |
715 | Html::element( |
716 | 'img', [ |
717 | 'src' => $this->getConfig()->get( 'ExtensionAssetsPath' ) |
718 | . "/CentralAuth/images/icons/merged-{$method}.png", |
719 | 'alt' => $brief, |
720 | 'title' => $brief, |
721 | ] |
722 | ) |
723 | . Html::element( |
724 | 'span', [ |
725 | 'class' => 'merge-method-help', |
726 | 'title' => $brief, |
727 | 'data-centralauth-mergemethod' => $method |
728 | ], |
729 | $this->msg( 'centralauth-merge-method-questionmark' )->text() |
730 | ); |
731 | |
732 | return $html; |
733 | } |
734 | |
735 | /** |
736 | * @param array $row |
737 | * @return string |
738 | */ |
739 | private function formatBlockStatus( $row ) { |
740 | $additionalHtml = ''; |
741 | if ( isset( $row['blocked'] ) && $row['blocked'] ) { |
742 | $optionMessage = $this->formatBlockParams( $row ); |
743 | if ( $row['block-expiry'] == 'infinity' ) { |
744 | $text = $this->msg( 'centralauth-admin-blocked2-indef' )->text(); |
745 | } else { |
746 | $expiry = $this->getLanguage()->timeanddate( $row['block-expiry'], true ); |
747 | $expiryd = $this->getLanguage()->date( $row['block-expiry'], true ); |
748 | $expiryt = $this->getLanguage()->time( $row['block-expiry'], true ); |
749 | |
750 | $text = $this->msg( 'centralauth-admin-blocked2', $expiry, $expiryd, $expiryt ) |
751 | ->text(); |
752 | } |
753 | |
754 | if ( $row['block-reason'] ) { |
755 | $reason = Sanitizer::escapeHtmlAllowEntities( $row['block-reason'] ); |
756 | $reason = $this->commentFormatter->formatLinks( |
757 | $reason, |
758 | null, |
759 | false, |
760 | $row['wiki'] |
761 | ); |
762 | |
763 | $msg = $this->msg( 'centralauth-admin-blocked-reason' ); |
764 | $msg->rawParams( '<span class="plainlinks">' . $reason . '</span>' ); |
765 | |
766 | $additionalHtml .= Html::rawElement( 'br' ) . $msg->parse(); |
767 | } |
768 | |
769 | $additionalHtml .= ' ' . $optionMessage; |
770 | |
771 | } else { |
772 | $text = $this->msg( 'centralauth-admin-notblocked' )->text(); |
773 | } |
774 | |
775 | return self::foreignLink( |
776 | $row['wiki'], |
777 | 'Special:Log/block', |
778 | $text, |
779 | $this->msg( 'centralauth-admin-blocklog' )->text(), |
780 | 'page=User:' . urlencode( $this->mUserName ) |
781 | ) . $additionalHtml; |
782 | } |
783 | |
784 | /** |
785 | * Format a block's parameters. |
786 | * |
787 | * @see BlockListPager::formatValue() |
788 | * |
789 | * @param array $row |
790 | * @return string |
791 | */ |
792 | private function formatBlockParams( $row ) { |
793 | global $wgConf; |
794 | |
795 | // Ensure all the data is loaded before trying to use. |
796 | $wgConf->loadFullData(); |
797 | |
798 | $properties = []; |
799 | |
800 | if ( $row['block-sitewide'] ) { |
801 | $properties[] = $this->msg( 'blocklist-editing-sitewide' )->escaped(); |
802 | } |
803 | |
804 | if ( !$row['block-sitewide'] && $row['block-restrictions'] ) { |
805 | $list = $this->getRestrictionListHTML( $row ); |
806 | if ( $list ) { |
807 | $properties[] = $this->msg( 'blocklist-editing' )->escaped() . $list; |
808 | } |
809 | } |
810 | |
811 | $options = [ |
812 | 'anononly' => 'anononlyblock', |
813 | 'nocreate' => 'createaccountblock', |
814 | 'noautoblock' => 'noautoblockblock', |
815 | 'noemail' => 'emailblock', |
816 | 'nousertalk' => 'blocklist-nousertalk', |
817 | ]; |
818 | foreach ( $options as $option => $msg ) { |
819 | if ( $row['block-' . $option] ) { |
820 | $properties[] = $this->msg( $msg )->escaped(); |
821 | } |
822 | } |
823 | |
824 | if ( !$properties ) { |
825 | return ''; |
826 | } |
827 | |
828 | return Html::rawElement( |
829 | 'ul', |
830 | [], |
831 | implode( '', array_map( static function ( $prop ) { |
832 | return Html::rawElement( |
833 | 'li', |
834 | [], |
835 | $prop |
836 | ); |
837 | }, $properties ) ) |
838 | ); |
839 | } |
840 | |
841 | /** |
842 | * @see BlockListPager::getRestrictionListHTML() |
843 | * |
844 | * @param array $row |
845 | * |
846 | * @return string |
847 | */ |
848 | private function getRestrictionListHTML( array $row ) { |
849 | $count = array_reduce( $row['block-restrictions'], static function ( $carry, $restriction ) { |
850 | $carry[$restriction->getType()] += 1; |
851 | return $carry; |
852 | }, [ |
853 | PageRestriction::TYPE => 0, |
854 | NamespaceRestriction::TYPE => 0, |
855 | ActionRestriction::TYPE => 0, |
856 | ] ); |
857 | |
858 | $restrictions = []; |
859 | foreach ( $count as $type => $value ) { |
860 | if ( $value === 0 ) { |
861 | continue; |
862 | } |
863 | |
864 | $restrictions[] = Html::rawElement( |
865 | 'li', |
866 | [], |
867 | self::foreignLink( |
868 | $row['wiki'], |
869 | 'Special:BlockList/' . $row['name'], |
870 | $this->msg( 'centralauth-block-editing-' . $type, $value )->text() |
871 | ) |
872 | ); |
873 | } |
874 | |
875 | if ( count( $restrictions ) === 0 ) { |
876 | return ''; |
877 | } |
878 | |
879 | return Html::rawElement( |
880 | 'ul', |
881 | [], |
882 | implode( '', $restrictions ) |
883 | ); |
884 | } |
885 | |
886 | /** |
887 | * @param array $row |
888 | * @return string |
889 | * @throws Exception |
890 | */ |
891 | private function formatEditcount( $row ) { |
892 | $wiki = WikiMap::getWiki( $row['wiki'] ); |
893 | if ( !$wiki ) { |
894 | throw new InvalidArgumentException( "Invalid wiki: {$row['wiki']}" ); |
895 | } |
896 | $wikiname = $wiki->getDisplayName(); |
897 | $editCount = $this->getLanguage()->formatNum( intval( $row['editCount'] ) ); |
898 | |
899 | return self::foreignLink( |
900 | $row['wiki'], |
901 | 'Special:Contributions/' . $this->mUserName, |
902 | $editCount, |
903 | $this->msg( 'centralauth-foreign-contributions' ) |
904 | ->params( $editCount, $wikiname )->text() |
905 | ); |
906 | } |
907 | |
908 | /** |
909 | * @param array $row |
910 | * @return string |
911 | */ |
912 | private function formatGroups( $row ) { |
913 | if ( !count( $row['groupMemberships'] ) ) { |
914 | return ''; |
915 | } |
916 | |
917 | // We place temporary groups before non-expiring groups in the list. |
918 | // This is to avoid the ambiguity of something like |
919 | // "sysop, bureaucrat (temporary)" -- users might wonder whether the |
920 | // "temporary" indication applies to both groups, or just the last one |
921 | $listTemporary = []; |
922 | $list = []; |
923 | /** @var UserGroupMembership $ugm */ |
924 | foreach ( $row['groupMemberships'] as $group => $ugm ) { |
925 | if ( $ugm->getExpiry() ) { |
926 | $listTemporary[] = $this->msg( 'centralauth-admin-group-temporary', |
927 | wfEscapeWikitext( $group ) )->parse(); |
928 | } else { |
929 | $list[] = htmlspecialchars( $group ); |
930 | } |
931 | } |
932 | return $this->getLanguage()->commaList( array_merge( $listTemporary, $list ) ); |
933 | } |
934 | |
935 | /** |
936 | * @param string|WikiReference $wikiID |
937 | * @param string $title |
938 | * @param string $text not HTML escaped |
939 | * @param string $hint |
940 | * @param string $params |
941 | * @return string |
942 | * @throws Exception |
943 | */ |
944 | public static function foreignLink( $wikiID, $title, $text, $hint = '', $params = '' ) { |
945 | if ( $wikiID instanceof WikiReference ) { |
946 | $wiki = $wikiID; |
947 | } else { |
948 | $wiki = WikiMap::getWiki( $wikiID ); |
949 | if ( !$wiki ) { |
950 | throw new InvalidArgumentException( "Invalid wiki: $wikiID" ); |
951 | } |
952 | } |
953 | |
954 | $url = $wiki->getFullUrl( $title ); |
955 | if ( $params ) { |
956 | $url .= '?' . $params; |
957 | } |
958 | return Xml::element( 'a', |
959 | [ |
960 | 'href' => $url, |
961 | 'title' => $hint, |
962 | ], |
963 | $text ); |
964 | } |
965 | |
966 | /** |
967 | * @param string $wikiID |
968 | * @return string |
969 | * @throws Exception |
970 | */ |
971 | private function foreignUserLink( $wikiID ) { |
972 | $wiki = WikiMap::getWiki( $wikiID ); |
973 | if ( !$wiki ) { |
974 | throw new InvalidArgumentException( "Invalid wiki: $wikiID" ); |
975 | } |
976 | |
977 | $wikiname = $wiki->getDisplayName(); |
978 | return self::foreignLink( |
979 | $wiki, |
980 | $this->namespaceInfo->getCanonicalName( NS_USER ) . ':' . $this->mUserName, |
981 | $wikiname, |
982 | $this->msg( 'centralauth-foreign-link', $this->mUserName, $wikiname )->text() |
983 | ); |
984 | } |
985 | |
986 | /** |
987 | * @param string $wikiID |
988 | * @return string |
989 | */ |
990 | private function adminCheck( $wikiID ) { |
991 | return Xml::check( 'wpWikis[]', false, [ 'value' => $wikiID ] ); |
992 | } |
993 | |
994 | /** |
995 | * @param string $action Only 'delete' supported |
996 | */ |
997 | private function showActionForm( $action ) { |
998 | $this->getOutput()->addHTML( |
999 | # to be able to find messages: centralauth-admin-delete-title, |
1000 | # centralauth-admin-delete-description, centralauth-admin-delete-button |
1001 | Xml::fieldset( $this->msg( "centralauth-admin-{$action}-title" )->text() ) . |
1002 | Xml::openElement( 'form', [ |
1003 | 'method' => 'POST', |
1004 | 'action' => $this->getPageTitle()->getFullUrl( |
1005 | 'target=' . urlencode( $this->mUserName ) |
1006 | ), |
1007 | 'id' => "mw-centralauth-$action" ] ) . |
1008 | Html::hidden( 'wpMethod', $action ) . |
1009 | Html::hidden( 'wpEditToken', $this->getUser()->getEditToken() ) . |
1010 | $this->msg( "centralauth-admin-{$action}-description" )->parseAsBlock() . |
1011 | Xml::buildForm( |
1012 | [ 'centralauth-admin-reason' => Xml::input( 'reason', |
1013 | false, false, [ 'id' => "{$action}-reason" ] ) ], |
1014 | "centralauth-admin-{$action}-button" |
1015 | ) . Xml::closeElement( 'form' ) . Xml::closeElement( 'fieldset' ) ); |
1016 | } |
1017 | |
1018 | private function showStatusForm() { |
1019 | // Allows locking, hiding, locking and hiding. |
1020 | $form = ''; |
1021 | $form .= Xml::fieldset( $this->msg( 'centralauth-admin-status' )->text() ); |
1022 | $form .= Html::hidden( 'wpMethod', 'set-status' ); |
1023 | $form .= Html::hidden( 'wpEditToken', $this->getUser()->getEditToken() ); |
1024 | $form .= Html::hidden( 'wpUserState', $this->mGlobalUser->getStateHash( false ) ); |
1025 | $form .= $this->msg( 'centralauth-admin-status-intro' )->parseAsBlock(); |
1026 | |
1027 | // Radio buttons |
1028 | $radioLocked = |
1029 | Xml::radioLabel( |
1030 | $this->msg( 'centralauth-admin-status-locked-no' )->text(), |
1031 | 'wpStatusLocked', |
1032 | '0', |
1033 | 'mw-centralauth-status-locked-no', |
1034 | !$this->mGlobalUser->isLocked() ) . |
1035 | '<br />' . |
1036 | Xml::radioLabel( |
1037 | $this->msg( 'centralauth-admin-status-locked-yes' )->text(), |
1038 | 'wpStatusLocked', |
1039 | '1', |
1040 | 'mw-centralauth-status-locked-yes', |
1041 | $this->mGlobalUser->isLocked() ); |
1042 | |
1043 | $radioHidden = |
1044 | Xml::radioLabel( |
1045 | $this->msg( 'centralauth-admin-status-hidden-no' )->text(), |
1046 | 'wpStatusHidden', |
1047 | (string)CentralAuthUser::HIDDEN_LEVEL_NONE, |
1048 | 'mw-centralauth-status-hidden-no', |
1049 | $this->mGlobalUser->getHiddenLevelInt() == CentralAuthUser::HIDDEN_LEVEL_NONE ); |
1050 | if ( $this->mCanSuppress ) { |
1051 | $radioHidden .= '<br />' . |
1052 | Xml::radioLabel( |
1053 | $this->msg( 'centralauth-admin-status-hidden-list' )->text(), |
1054 | 'wpStatusHidden', |
1055 | (string)CentralAuthUser::HIDDEN_LEVEL_LISTS, |
1056 | 'mw-centralauth-status-hidden-list', |
1057 | $this->mGlobalUser->getHiddenLevelInt() == CentralAuthUser::HIDDEN_LEVEL_LISTS |
1058 | ) . |
1059 | '<br />' . |
1060 | Xml::radioLabel( |
1061 | $this->msg( 'centralauth-admin-status-hidden-oversight' )->text(), |
1062 | 'wpStatusHidden', |
1063 | (string)CentralAuthUser::HIDDEN_LEVEL_SUPPRESSED, |
1064 | 'mw-centralauth-status-hidden-oversight', |
1065 | $this->mGlobalUser->getHiddenLevelInt() == CentralAuthUser::HIDDEN_LEVEL_SUPPRESSED |
1066 | ); |
1067 | } |
1068 | |
1069 | $reasonList = Xml::listDropdown( |
1070 | 'wpReasonList', |
1071 | $this->msg( 'centralauth-admin-status-reasons' )->inContentLanguage()->text(), |
1072 | $this->msg( 'centralauth-admin-reason-other-select' )->inContentLanguage()->text() |
1073 | ); |
1074 | $reasonField = Xml::input( 'wpReason', 45, false ); |
1075 | |
1076 | $form .= Xml::buildForm( |
1077 | [ |
1078 | 'centralauth-admin-status-locked' => $radioLocked, |
1079 | 'centralauth-admin-status-hidden' => $radioHidden, |
1080 | 'centralauth-admin-reason' => $reasonList, |
1081 | 'centralauth-admin-reason-other' => $reasonField |
1082 | ], |
1083 | 'centralauth-admin-status-submit' |
1084 | ); |
1085 | |
1086 | $form .= Xml::closeElement( 'fieldset' ); |
1087 | $form = Xml::tags( |
1088 | 'form', |
1089 | [ |
1090 | 'method' => 'POST', |
1091 | 'action' => $this->getPageTitle()->getFullURL( |
1092 | [ 'target' => $this->mUserName ] |
1093 | ), |
1094 | ], |
1095 | $form |
1096 | ); |
1097 | $this->getOutput()->addHTML( $form ); |
1098 | } |
1099 | |
1100 | private function showLogExtract() { |
1101 | $user = $this->mGlobalUser->getName(); |
1102 | $title = Title::newFromText( $this->namespaceInfo->getCanonicalName( NS_USER ) . ":{$user}@global" ); |
1103 | if ( !$title ) { |
1104 | // Don't fatal even if a Title couldn't be generated |
1105 | // because we've invalid usernames too :/ |
1106 | return; |
1107 | } |
1108 | $logTypes = [ 'globalauth' ]; |
1109 | if ( $this->mCanSuppress ) { |
1110 | $logTypes[] = 'suppress'; |
1111 | } |
1112 | $text = ''; |
1113 | $numRows = LogEventsList::showLogExtract( |
1114 | $text, |
1115 | $logTypes, |
1116 | $title->getPrefixedText(), |
1117 | '', |
1118 | [ 'showIfEmpty' => true ] ); |
1119 | |
1120 | if ( $numRows ) { |
1121 | $this->getOutput()->addHTML( Xml::fieldset( |
1122 | $this->msg( 'centralauth-admin-logsnippet' )->text(), |
1123 | $text |
1124 | ) ); |
1125 | |
1126 | return; |
1127 | } |
1128 | |
1129 | if ( $this->mGlobalUser->isLocked() ) { |
1130 | $logOtherWikiMsg = $this |
1131 | ->msg( 'centralauth-admin-log-otherwiki' ) |
1132 | ->params( $this->mGlobalUser->getName() ); |
1133 | |
1134 | if ( !$logOtherWikiMsg->isDisabled() ) { |
1135 | $this->getOutput()->addHTML( |
1136 | Html::warningBox( |
1137 | $logOtherWikiMsg->parse(), |
1138 | 'centralauth-admin-log-otherwiki' |
1139 | ) |
1140 | ); |
1141 | } |
1142 | } |
1143 | } |
1144 | |
1145 | /** |
1146 | * @return int |
1147 | */ |
1148 | private function evaluateTotalEditcount() { |
1149 | $total = 0; |
1150 | foreach ( $this->mAttachedLocalAccounts as $acc ) { |
1151 | $total += $acc['editCount']; |
1152 | } |
1153 | return $total; |
1154 | } |
1155 | |
1156 | /** |
1157 | * @return array[] |
1158 | */ |
1159 | private function getMergeMethodDescriptions() { |
1160 | // Give grep a chance to find the usages: |
1161 | // centralauth-merge-method-primary, centralauth-merge-method-new, |
1162 | // centralauth-merge-method-empty, centralauth-merge-method-password, |
1163 | // centralauth-merge-method-mail, centralauth-merge-method-admin, |
1164 | // centralauth-merge-method-login |
1165 | // Give grep a chance to find the usages: |
1166 | // centralauth-merge-method-primary-desc, centralauth-merge-method-new-desc, |
1167 | // centralauth-merge-method-empty-desc, centralauth-merge-method-password-desc, |
1168 | // centralauth-merge-method-mail-desc, centralauth-merge-method-admin-desc, |
1169 | // centralauth-merge-method-login-desc |
1170 | $mergeMethodDescriptions = []; |
1171 | foreach ( [ 'primary', 'new', 'empty', 'password', 'mail', 'admin', 'login' ] as $method ) { |
1172 | $mergeMethodDescriptions[$method] = [ |
1173 | 'short' => $this->getLanguage()->ucfirst( |
1174 | $this->msg( "centralauth-merge-method-{$method}" )->escaped() |
1175 | ), |
1176 | 'desc' => $this->msg( "centralauth-merge-method-{$method}-desc" )->escaped() |
1177 | ]; |
1178 | } |
1179 | return $mergeMethodDescriptions; |
1180 | } |
1181 | |
1182 | /** |
1183 | * Return an array of subpages beginning with $search that this special page will accept. |
1184 | * |
1185 | * @param string $search Prefix to search for |
1186 | * @param int $limit Maximum number of results to return (usually 10) |
1187 | * @param int $offset Number of results to skip (usually 0) |
1188 | * @return string[] Matching subpages |
1189 | */ |
1190 | public function prefixSearchSubpages( $search, $limit, $offset ) { |
1191 | $search = $this->userNameUtils->getCanonical( $search ); |
1192 | if ( !$search ) { |
1193 | // No prefix suggestion for invalid user |
1194 | return []; |
1195 | } |
1196 | |
1197 | $dbr = $this->databaseManager->getCentralReplicaDB(); |
1198 | |
1199 | // Autocomplete subpage as user list - non-hidden users to allow caching |
1200 | return $dbr->newSelectQueryBuilder() |
1201 | ->select( 'gu_name' ) |
1202 | ->from( 'globaluser' ) |
1203 | ->where( [ |
1204 | $dbr->expr( 'gu_name', IExpression::LIKE, new LikeValue( $search, $dbr->anyString() ) ), |
1205 | 'gu_hidden_level' => CentralAuthUser::HIDDEN_LEVEL_NONE, |
1206 | ] ) |
1207 | ->orderBy( 'gu_name' ) |
1208 | ->limit( $limit ) |
1209 | ->offset( $offset ) |
1210 | ->caller( __METHOD__ ) |
1211 | ->fetchFieldValues(); |
1212 | } |
1213 | |
1214 | /** @inheritDoc */ |
1215 | protected function getGroupName() { |
1216 | return 'users'; |
1217 | } |
1218 | } |