MediaWiki 1.40.4
SpecialRenameuser.php
Go to the documentation of this file.
1<?php
2
12
19 private $dbConns;
20
22 private $contentLanguage;
23
25 private $movePageFactory;
26
28 private $permissionManager;
29
31 private $titleFactory;
32
34 private $userFactory;
35
37 private $userNamePrefixSearch;
38
48 public function __construct(
49 IConnectionProvider $dbConns,
50 Language $contentLanguage,
51 MovePageFactory $movePageFactory,
52 PermissionManager $permissionManager,
53 TitleFactory $titleFactory,
54 UserFactory $userFactory,
55 UserNamePrefixSearch $userNamePrefixSearch
56 ) {
57 parent::__construct( 'Renameuser', 'renameuser' );
58
59 $this->dbConns = $dbConns;
60 $this->contentLanguage = $contentLanguage;
61 $this->movePageFactory = $movePageFactory;
62 $this->permissionManager = $permissionManager;
63 $this->titleFactory = $titleFactory;
64 $this->userFactory = $userFactory;
65 $this->userNamePrefixSearch = $userNamePrefixSearch;
66 }
67
68 public function doesWrites() {
69 return true;
70 }
71
77 public function execute( $par ) {
78 $this->setHeaders();
79 $this->addHelpLink( 'Help:Renameuser' );
80
81 $this->checkPermissions();
82 $this->checkReadOnly();
83
84 $performer = $this->getUser();
85
86 $block = $performer->getBlock();
87 if ( $block ) {
88 throw new UserBlockedError( $block );
89 }
90
91 $out = $this->getOutput();
92 $out->addWikiMsg( 'renameuser-summary' );
93
95
96 $request = $this->getRequest();
97
98 // This works as "/" is not valid in usernames
99 $userNames = $par !== null ? explode( '/', $par, 2 ) : [];
100
101 // Get the old name, applying minimal validation or canonicalization
102 $oldName = $request->getText( 'oldusername', $userNames[0] ?? '' );
103 $oldName = trim( str_replace( '_', ' ', $oldName ) );
104 $oldTitle = $this->titleFactory->makeTitle( NS_USER, $oldName );
105
106 // Get the new name and canonicalize it
107 $origNewName = $request->getText( 'newusername', $userNames[1] ?? '' );
108 $origNewName = trim( str_replace( '_', ' ', $origNewName ) );
109 // Force uppercase of new username, otherwise wikis
110 // with wgCapitalLinks=false can create lc usernames
111 $newTitle = $this->titleFactory->makeTitleSafe( NS_USER, $this->contentLanguage->ucfirst( $origNewName ) );
112 $newName = $newTitle ? $newTitle->getText() : '';
113
114 $reason = $request->getText( 'reason' );
115 $moveChecked = $request->getBool( 'movepages', !$request->wasPosted() );
116 $suppressChecked = $request->getCheck( 'suppressredirect' );
117
118 if ( $oldName !== '' && $newName !== '' && !$request->getCheck( 'confirmaction' ) ) {
119 $warnings = $this->getWarnings( $oldName, $newName );
120 } else {
121 $warnings = [];
122 }
123
124 $this->showForm( $oldName, $newName, $warnings, $reason, $moveChecked, $suppressChecked );
125
126 if ( $request->getText( 'token' ) === '' ) {
127 # They probably haven't even submitted the form, so don't go further.
128 return;
129 } elseif ( $warnings ) {
130 # Let user read warnings
131 return;
132 } elseif ( !$request->wasPosted() || !$performer->matchEditToken( $request->getVal( 'token' ) ) ) {
133 $out->addHTML( Html::errorBox( $out->msg( 'renameuser-error-request' )->parse() ) );
134
135 return;
136 } elseif ( !$newTitle ) {
137 $out->addHTML( Html::errorBox(
138 $out->msg( 'renameusererrorinvalid' )->params( $request->getText( 'newusername' ) )->parse()
139 ) );
140
141 return;
142 } elseif ( $oldName === $newName ) {
143 $out->addHTML( Html::errorBox( $out->msg( 'renameuser-error-same-user' )->parse() ) );
144
145 return;
146 }
147
148 // Suppress username validation of old username
149 $oldUser = $this->userFactory->newFromName( $oldName, $this->userFactory::RIGOR_NONE );
150 $newUser = $this->userFactory->newFromName( $newName, $this->userFactory::RIGOR_CREATABLE );
151
152 // It won't be an object if for instance "|" is supplied as a value
153 if ( !$oldUser ) {
154 $out->addHTML( Html::errorBox(
155 $out->msg( 'renameusererrorinvalid' )->params( $oldTitle->getText() )->parse()
156 ) );
157
158 return;
159 }
160 if ( !$newUser ) {
161 $out->addHTML( Html::errorBox(
162 $out->msg( 'renameusererrorinvalid' )->params( $newTitle->getText() )->parse()
163 ) );
164
165 return;
166 }
167
168 // Check for the existence of lowercase old username in database.
169 // Until r19631 it was possible to rename a user to a name with first character as lowercase
170 if ( $oldName !== $this->contentLanguage->ucfirst( $oldName ) ) {
171 // old username was entered as lowercase -> check for existence in table 'user'
172 $dbr = $this->dbConns->getReplicaDatabase();
173 $uid = $dbr->newSelectQueryBuilder()
174 ->select( 'user_id' )
175 ->from( 'user' )
176 ->where( [ 'user_name' => $oldName ] )
177 ->caller( __METHOD__ )
178 ->fetchField();
179 if ( $uid === false ) {
180 if ( !$this->getConfig()->get( 'CapitalLinks' ) ) {
181 $uid = 0; // We are on a lowercase wiki but lowercase username does not exist
182 } else {
183 // We are on a standard uppercase wiki, use normal
184 $uid = $oldUser->idForName();
185 $oldTitle = $this->titleFactory->makeTitleSafe( NS_USER, $oldUser->getName() );
186 if ( !$oldTitle ) {
187 $out->addHTML( Html::errorBox(
188 $out->msg( 'renameusererrorinvalid' )->params( $oldName )->parse()
189 ) );
190 return;
191 }
192 $oldName = $oldTitle->getText();
193 }
194 }
195 } else {
196 // old username was entered as uppercase -> standard procedure
197 $uid = $oldUser->idForName();
198 }
199
200 if ( $uid === 0 ) {
201 $out->addHTML( Html::errorBox(
202 $out->msg( 'renameusererrordoesnotexist' )->params( $oldName )->parse()
203 ) );
204
205 return;
206 }
207
208 if ( $newUser->idForName() !== 0 ) {
209 $out->addHTML( Html::errorBox(
210 $out->msg( 'renameusererrorexists' )->params( $newName )->parse()
211 ) );
212
213 return;
214 }
215
216 // Give other affected extensions a chance to validate or abort
217 if ( !$this->getHookRunner()->onRenameUserAbort( $uid, $oldName, $newName ) ) {
218 return;
219 }
220
221 // Do the heavy lifting...
222 $rename = new RenameuserSQL(
223 $oldTitle->getText(),
224 $newTitle->getText(),
225 $uid,
226 $this->getUser(),
227 [ 'reason' => $reason ]
228 );
229 if ( !$rename->rename() ) {
230 return;
231 }
232
233 // If this user is renaming his/herself, make sure that MovePage::move()
234 // doesn't make a bunch of null move edits under the old name!
235 if ( $performer->getId() === $uid ) {
236 $performer->setName( $newTitle->getText() );
237 }
238
239 // Move any user pages
240 if ( $moveChecked && $this->permissionManager->userHasRight( $performer, 'move' ) ) {
241 $suppressRedirect = $suppressChecked
242 && $this->permissionManager->userHasRight( $performer, 'suppressredirect' );
243 $this->movePages( $oldTitle, $newTitle, $suppressRedirect );
244 }
245
246 // Output success message stuff :)
247 $out->addHTML(
248 Html::successBox(
249 $out->msg( 'renameusersuccess' )
250 ->params( $oldTitle->getText(), $newTitle->getText() )
251 ->parse()
252 )
253 );
254 }
255
256 private function getWarnings( $oldName, $newName ) {
257 $warnings = [];
258 $oldUser = $this->userFactory->newFromName( $oldName, $this->userFactory::RIGOR_NONE );
259 if ( $oldUser && $oldUser->getBlock() ) {
260 $warnings[] = [
261 'renameuser-warning-currentblock',
262 SpecialPage::getTitleFor( 'Log', 'block' )->getFullURL( [ 'page' => $oldName ] )
263 ];
264 }
265 $this->getHookRunner()->onRenameUserWarning( $oldName, $newName, $warnings );
266 return $warnings;
267 }
268
269 private function showForm( $oldName, $newName, $warnings, $reason, $moveChecked, $suppressChecked ) {
270 $performer = $this->getUser();
271 $token = $performer->getEditToken();
272 $out = $this->getOutput();
273
274 $out->addHTML(
275 Xml::openElement( 'form', [
276 'method' => 'post',
277 'action' => $this->getPageTitle()->getLocalURL(),
278 'id' => 'renameuser'
279 ] ) .
280 Xml::openElement( 'fieldset' ) .
281 Xml::element( 'legend', null, $this->msg( 'renameuser' )->text() ) .
282 Xml::openElement( 'table', [ 'id' => 'mw-renameuser-table' ] ) .
283 "<tr>
284 <td class='mw-label'>" .
285 Xml::label( $this->msg( 'renameuserold' )->text(), 'oldusername' ) .
286 "</td>
287 <td class='mw-input'>" .
288 Xml::input( 'oldusername', 20, $oldName, [ 'type' => 'text', 'tabindex' => '1' ] ) . ' ' .
289 "</td>
290 </tr>
291 <tr>
292 <td class='mw-label'>" .
293 Xml::label( $this->msg( 'renameusernew' )->text(), 'newusername' ) .
294 "</td>
295 <td class='mw-input'>" .
296 Xml::input( 'newusername', 20, $newName, [ 'type' => 'text', 'tabindex' => '2' ] ) .
297 "</td>
298 </tr>
299 <tr>
300 <td class='mw-label'>" .
301 Xml::label( $this->msg( 'renameuserreason' )->text(), 'reason' ) .
302 "</td>
303 <td class='mw-input'>" .
304 Xml::input(
305 'reason',
306 40,
307 $reason,
308 [ 'type' => 'text', 'tabindex' => '3', 'maxlength' => 255 ]
309 ) .
310 '</td>
311 </tr>'
312 );
313 if ( $this->permissionManager->userHasRight( $performer, 'move' ) ) {
314 $out->addHTML( "
315 <tr>
316 <td>&#160;
317 </td>
318 <td class='mw-input'>" .
319 Xml::checkLabel( $this->msg( 'renameusermove' )->text(), 'movepages', 'movepages',
320 $moveChecked, [ 'tabindex' => '4' ] ) .
321 '</td>
322 </tr>'
323 );
324
325 if ( $this->permissionManager->userHasRight( $performer, 'suppressredirect' ) ) {
326 $out->addHTML( "
327 <tr>
328 <td>&#160;
329 </td>
330 <td class='mw-input'>" .
331 Xml::checkLabel(
332 $this->msg( 'renameusersuppress' )->text(),
333 'suppressredirect',
334 'suppressredirect',
335 $suppressChecked,
336 [ 'tabindex' => '5' ]
337 ) .
338 '</td>
339 </tr>'
340 );
341 }
342 }
343 if ( $warnings ) {
344 $warningsHtml = [];
345 foreach ( $warnings as $warning ) {
346 $warningsHtml[] = is_array( $warning ) ?
347 $this->msg( $warning[0] )->params( array_slice( $warning, 1 ) )->parse() :
348 $this->msg( $warning )->parse();
349 }
350
351 $out->addHTML( "
352 <tr>
353 <td class='mw-label'>" . $this->msg( 'renameuserwarnings' )->escaped() . "
354 </td>
355 <td class='mw-input'>" .
356 '<ul class="error"><li>' .
357 implode( '</li><li>', $warningsHtml ) . '</li></ul>' .
358 '</td>
359 </tr>'
360 );
361 $out->addHTML( "
362 <tr>
363 <td>&#160;
364 </td>
365 <td class='mw-input'>" .
366 Xml::checkLabel(
367 $this->msg( 'renameuserconfirm' )->text(),
368 'confirmaction',
369 'confirmaction',
370 false,
371 [ 'tabindex' => '6' ]
372 ) .
373 '</td>
374 </tr>'
375 );
376 }
377 $out->addHTML( "
378 <tr>
379 <td>&#160;
380 </td>
381 <td class='mw-submit'>" .
382 Xml::submitButton(
383 $this->msg( 'renameusersubmit' )->text(),
384 [
385 'name' => 'submit',
386 'tabindex' => '7',
387 'id' => 'submit'
388 ]
389 ) .
390 ' ' .
391 '</td>
392 </tr>' .
393 Xml::closeElement( 'table' ) .
394 Xml::closeElement( 'fieldset' ) .
395 Html::hidden( 'token', $token ) .
396 Xml::closeElement( 'form' ) . "\n"
397 );
398 }
399
408 private function movePages( Title $oldTitle, Title $newTitle, $suppressRedirect ) {
409 $output = $this->movePageAndSubpages( $oldTitle, $newTitle, $suppressRedirect );
410 $oldTalkTitle = $oldTitle->getTalkPageIfDefined();
411 $newTalkTitle = $newTitle->getTalkPageIfDefined();
412 if ( $oldTalkTitle && $newTalkTitle ) { // always true
413 $output .= $this->movePageAndSubpages( $oldTalkTitle, $newTalkTitle, $suppressRedirect );
414 }
415
416 if ( $output !== '' ) {
417 $this->getOutput()->addHTML( Html::rawElement( 'ul', [], $output ) );
418 }
419 }
420
429 private function movePageAndSubpages( Title $oldTitle, Title $newTitle, $suppressRedirect ) {
430 $performer = $this->getUser();
431 $logReason = $this->msg(
432 'renameuser-move-log', $oldTitle->getText(), $newTitle->getText()
433 )->inContentLanguage()->text();
434 $movePage = $this->movePageFactory->newMovePage( $oldTitle, $newTitle );
435
436 $output = '';
437 if ( $oldTitle->exists() ) {
438 $status = $movePage->moveIfAllowed( $performer, $logReason, !$suppressRedirect );
439 $output .= $this->getMoveStatusHtml( $status, $oldTitle, $newTitle );
440 }
441
442 $oldLength = strlen( $oldTitle->getText() );
443 $batchStatus = $movePage->moveSubpagesIfAllowed( $performer, $logReason, !$suppressRedirect );
444 foreach ( $batchStatus->getValue() as $titleText => $status ) {
445 $oldSubpageTitle = Title::newFromText( $titleText );
446 $newSubpageTitle = $newTitle->getSubpage(
447 substr( $oldSubpageTitle->getText(), $oldLength + 1 ) );
448 $output .= $this->getMoveStatusHtml( $status, $oldSubpageTitle, $newSubpageTitle );
449 }
450 return $output;
451 }
452
453 private function getMoveStatusHtml( Status $status, Title $oldTitle, Title $newTitle ) {
454 $linkRenderer = $this->getLinkRenderer();
455 if ( $status->hasMessage( 'articleexists' ) || $status->hasMessage( 'redirectexists' ) ) {
456 $link = $linkRenderer->makeKnownLink( $newTitle );
457 return Html::rawElement(
458 'li',
459 [ 'class' => 'mw-renameuser-pe' ],
460 $this->msg( 'renameuser-page-exists' )->rawParams( $link )->escaped()
461 );
462 } else {
463 if ( $status->isOK() ) {
464 // oldPage is not known in case of redirect suppression
465 $oldLink = $linkRenderer->makeLink( $oldTitle, null, [], [ 'redirect' => 'no' ] );
466
467 // newPage is always known because the move was successful
468 $newLink = $linkRenderer->makeKnownLink( $newTitle );
469
470 return Html::rawElement(
471 'li',
472 [ 'class' => 'mw-renameuser-pm' ],
473 $this->msg( 'renameuser-page-moved' )->rawParams( $oldLink, $newLink )->escaped()
474 );
475 } else {
476 $oldLink = $linkRenderer->makeKnownLink( $oldTitle );
477 $newLink = $linkRenderer->makeLink( $newTitle );
478 return Html::rawElement(
479 'li', [ 'class' => 'mw-renameuser-pu' ],
480 $this->msg( 'renameuser-page-unmoved' )->rawParams( $oldLink, $newLink )->escaped()
481 );
482 }
483 }
484 }
485
494 public function prefixSearchSubpages( $search, $limit, $offset ) {
495 $user = $this->userFactory->newFromName( $search );
496 if ( !$user ) {
497 // No prefix suggestion for invalid user
498 return [];
499 }
500 // Autocomplete subpage as user list - public to allow caching
501 return $this->userNamePrefixSearch->search( 'public', $search, $limit, $offset );
502 }
503
504 protected function getGroupName() {
505 return 'users';
506 }
507}
const NS_USER
Definition Defines.php:66
Base class for language-specific code.
Definition Language.php:56
This class is a collection of static functions that serve two purposes:
Definition Html.php:55
A service class for checking permissions To obtain an instance, use MediaWikiServices::getInstance()-...
Class which performs the actual renaming of users.
Creates Title objects.
Represents a title within MediaWiki.
Definition Title.php:82
getTalkPageIfDefined()
Get a Title object associated with the talk page of this article, if such a talk page can exist.
Definition Title.php:1701
exists( $flags=0)
Check if page exists.
Definition Title.php:3523
getSubpage( $text)
Get the title for a subpage of the current page.
Definition Title.php:2131
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition Title.php:697
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition Title.php:671
getText()
Get the text form (spaces not underscores) of the main part.
Definition Title.php:1072
Creates User objects.
Handles searching prefixes of user names.
Parent class for all special pages.
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes!
getOutput()
Get the OutputPage being used for this instance.
getUser()
Shortcut to get the User executing this instance.
checkPermissions()
Checks if userCanExecute, and if not throws a PermissionsError.
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
getConfig()
Shortcut to get main config object.
getRequest()
Get the WebRequest being used for this instance.
checkReadOnly()
If the wiki is currently in readonly mode, throws a ReadOnlyError.
getPageTitle( $subpage=false)
Get a self-referential title object.
useTransactionalTimeLimit()
Call wfTransactionalTimeLimit() if this request was POSTed.
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
Special page that allows authorised users to rename user accounts.
doesWrites()
Indicates whether this special page may perform database writes.
__construct(IConnectionProvider $dbConns, Language $contentLanguage, MovePageFactory $movePageFactory, PermissionManager $permissionManager, TitleFactory $titleFactory, UserFactory $userFactory, UserNamePrefixSearch $userNamePrefixSearch)
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
prefixSearchSubpages( $search, $limit, $offset)
Return an array of subpages beginning with $search that this special page will accept.
execute( $par)
Show the special page.
hasMessage( $message)
Returns true if the specified message is present as a warning or error.
isOK()
Returns whether the operation completed.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:46
Show an error when the user tries to do something whilst blocked.
Service for page rename actions.
Narrow interface providing primary/replica connections.