MediaWiki master
SpecialRenameUser.php
Go to the documentation of this file.
1<?php
2
3namespace MediaWiki\Specials;
4
16use OOUI\FieldLayout;
17use OOUI\HtmlSnippet;
18use OOUI\MessageWidget;
20
27 private IConnectionProvider $dbConns;
28 private PermissionManager $permissionManager;
29 private TitleFactory $titleFactory;
30 private UserFactory $userFactory;
31 private UserNamePrefixSearch $userNamePrefixSearch;
32 private RenameUserFactory $renameUserFactory;
33
34 public function __construct(
35 IConnectionProvider $dbConns,
36 PermissionManager $permissionManager,
37 TitleFactory $titleFactory,
38 UserFactory $userFactory,
39 UserNamePrefixSearch $userNamePrefixSearch,
40 RenameUserFactory $renameUserFactory
41 ) {
42 parent::__construct( 'Renameuser', $userFactory->isUserTableShared() ? 'renameuser-global' : 'renameuser' );
43
44 $this->dbConns = $dbConns;
45 $this->permissionManager = $permissionManager;
46 $this->titleFactory = $titleFactory;
47 $this->userFactory = $userFactory;
48 $this->userNamePrefixSearch = $userNamePrefixSearch;
49 $this->renameUserFactory = $renameUserFactory;
50 }
51
52 public function doesWrites() {
53 return true;
54 }
55
61 public function execute( $par ) {
62 $this->setHeaders();
63 $this->addHelpLink( 'Help:Renameuser' );
64
65 $this->checkPermissions();
66 $this->checkReadOnly();
67
68 $performer = $this->getUser();
69
70 $block = $performer->getBlock();
71 if ( $block ) {
72 throw new UserBlockedError( $block );
73 }
74
75 $out = $this->getOutput();
76 $out->addWikiMsg( 'renameuser-summary' );
77
79
80 $request = $this->getRequest();
81
82 // This works as "/" is not valid in usernames
83 $userNames = $par !== null ? explode( '/', $par, 2 ) : [];
84
85 // Get the old name, applying minimal validation or canonicalization
86 $oldName = $request->getText( 'oldusername', $userNames[0] ?? '' );
87 $oldName = trim( str_replace( '_', ' ', $oldName ) );
88 $oldTitle = $this->titleFactory->makeTitle( NS_USER, $oldName );
89
90 // Get the new name and canonicalize it
91 $origNewName = $request->getText( 'newusername', $userNames[1] ?? '' );
92 $origNewName = trim( str_replace( '_', ' ', $origNewName ) );
93 // Force uppercase of new username, otherwise wikis
94 // with wgCapitalLinks=false can create lc usernames
95 $newTitle = $this->titleFactory->makeTitleSafe( NS_USER, $this->getContentLanguage()->ucfirst( $origNewName ) );
96 $newName = $newTitle ? $newTitle->getText() : '';
97
98 $reason = $request->getText( 'reason' );
99 $moveChecked = $request->getBool( 'movepages', !$request->wasPosted() );
100 $suppressChecked = $request->getCheck( 'suppressredirect' );
101
102 if ( $oldName !== '' && $newName !== '' && !$request->getCheck( 'confirmaction' ) ) {
103 $warnings = $this->getWarnings( $oldName, $newName );
104 } else {
105 $warnings = [];
106 }
107
108 $this->showForm( $oldName, $newName, $warnings, $reason, $moveChecked, $suppressChecked );
109
110 if ( $request->getText( 'wpEditToken' ) === '' ) {
111 # They probably haven't even submitted the form, so don't go further.
112 return;
113 }
114 if ( $warnings ) {
115 # Let user read warnings
116 return;
117 }
118 if (
119 !$request->wasPosted() ||
120 !$performer->matchEditToken( $request->getVal( 'wpEditToken' ) )
121 ) {
122 $out->addHTML( Html::errorBox( $out->msg( 'renameuser-error-request' )->parse() ) );
123
124 return;
125 }
126 if ( !$newTitle ) {
127 $out->addHTML( Html::errorBox(
128 $out->msg( 'renameusererrorinvalid' )->params( $request->getText( 'newusername' ) )->parse()
129 ) );
130
131 return;
132 }
133 if ( $oldName === $newName ) {
134 $out->addHTML( Html::errorBox( $out->msg( 'renameuser-error-same-user' )->parse() ) );
135
136 return;
137 }
138
139 // Suppress username validation of old username
140 $oldUser = $this->userFactory->newFromName( $oldName, $this->userFactory::RIGOR_NONE );
141 $newUser = $this->userFactory->newFromName( $newName, $this->userFactory::RIGOR_CREATABLE );
142
143 // It won't be an object if for instance "|" is supplied as a value
144 if ( !$oldUser ) {
145 $out->addHTML( Html::errorBox(
146 $out->msg( 'renameusererrorinvalid' )->params( $oldTitle->getText() )->parse()
147 ) );
148
149 return;
150 }
151 if ( !$newUser ) {
152 $out->addHTML( Html::errorBox(
153 $out->msg( 'renameusererrorinvalid' )->params( $newTitle->getText() )->parse()
154 ) );
155
156 return;
157 }
158
159 // Check for the existence of lowercase old username in database.
160 // Until r19631 it was possible to rename a user to a name with first character as lowercase
161 if ( $oldName !== $this->getContentLanguage()->ucfirst( $oldName ) ) {
162 // old username was entered as lowercase -> check for existence in table 'user'
163 $dbr = $this->dbConns->getReplicaDatabase();
164 $uid = $dbr->newSelectQueryBuilder()
165 ->select( 'user_id' )
166 ->from( 'user' )
167 ->where( [ 'user_name' => $oldName ] )
168 ->caller( __METHOD__ )
169 ->fetchField();
170 if ( $uid === false ) {
171 if ( !$this->getConfig()->get( MainConfigNames::CapitalLinks ) ) {
172 $uid = 0; // We are on a lowercase wiki but lowercase username does not exist
173 } else {
174 // We are on a standard uppercase wiki, use normal
175 $uid = $oldUser->idForName();
176 $oldTitle = $this->titleFactory->makeTitleSafe( NS_USER, $oldUser->getName() );
177 if ( !$oldTitle ) {
178 $out->addHTML( Html::errorBox(
179 $out->msg( 'renameusererrorinvalid' )->params( $oldName )->parse()
180 ) );
181 return;
182 }
183 $oldName = $oldTitle->getText();
184 }
185 }
186 } else {
187 // old username was entered as uppercase -> standard procedure
188 $uid = $oldUser->idForName();
189 }
190
191 if ( $uid === 0 ) {
192 $out->addHTML( Html::errorBox(
193 $out->msg( 'renameusererrordoesnotexist' )->params( $oldName )->parse()
194 ) );
195
196 return;
197 }
198
199 if ( $newUser->idForName() !== 0 ) {
200 $out->addHTML( Html::errorBox(
201 $out->msg( 'renameusererrorexists' )->params( $newName )->parse()
202 ) );
203
204 return;
205 }
206
207 // Check user rights again
208 // This is needed because SpecialPage::__construct only supports
209 // checking for one right, but both renameuser and -global is required
210 // to rename a global user.
211 if ( !$this->permissionManager->userHasRight( $performer, 'renameuser' ) ) {
213 }
214 if ( $this->userFactory->isUserTableShared()
215 && !$this->permissionManager->userHasRight( $performer, 'renameuser-global' ) ) {
216 $out->addHTML( Html::errorBox( $out->msg( 'renameuser-error-global-rights' )->parse() ) );
217 return;
218 }
219
220 // Give other affected extensions a chance to validate or abort
221 if ( !$this->getHookRunner()->onRenameUserAbort( $uid, $oldName, $newName ) ) {
222 return;
223 }
224
225 $rename = $this->renameUserFactory->newRenameUser( $performer, $oldUser, $newName, $reason, [
226 'movePages' => $moveChecked,
227 'suppressRedirect' => $suppressChecked,
228 ] );
229 $status = $rename->rename();
230
231 if ( $status->isGood() ) {
232 // Output success message stuff :)
233 $out->addHTML(
234 Html::successBox(
235 $out->msg( 'renameusersuccess' )
236 ->params( $oldTitle->getText(), $newTitle->getText() )
237 ->parse()
238 )
239 );
240 } else {
241 // Output errors stuff
242 $outHtml = '';
243 foreach ( $status->getMessages() as $msg ) {
244 $outHtml = $outHtml . $out->msg( $msg )->parse() . '<br/>';
245 }
246 if ( $status->isOK() ) {
247 $out->addHTML( Html::warningBox( $outHtml ) );
248 } else {
249 $out->addHTML( Html::errorBox( $outHtml ) );
250 }
251 }
252 }
253
254 private function getWarnings( string $oldName, string $newName ): array {
255 $warnings = [];
256 $oldUser = $this->userFactory->newFromName( $oldName, $this->userFactory::RIGOR_NONE );
257 if ( $oldUser && !$oldUser->isTemp() && $oldUser->getBlock() ) {
258 $warnings[] = [
259 'renameuser-warning-currentblock',
260 SpecialPage::getTitleFor( 'Log', 'block' )->getFullURL( [ 'page' => $oldName ] )
261 ];
262 }
263 $this->getHookRunner()->onRenameUserWarning( $oldName, $newName, $warnings );
264 return $warnings;
265 }
266
267 private function showForm(
268 ?string $oldName, ?string $newName, array $warnings, string $reason, bool $moveChecked, bool $suppressChecked
269 ) {
270 $performer = $this->getUser();
271
272 $formDescriptor = [
273 'oldusername' => [
274 'type' => 'user',
275 'name' => 'oldusername',
276 'label-message' => 'renameuserold',
277 'default' => $oldName,
278 'required' => true,
279 'excludetemp' => true,
280 ],
281 'newusername' => [
282 'type' => 'text',
283 'name' => 'newusername',
284 'label-message' => 'renameusernew',
285 'default' => $newName,
286 'required' => true,
287 ],
288 'reason' => [
289 'type' => 'text',
290 'name' => 'reason',
291 'label-message' => 'renameuserreason',
292 'maxlength' => CommentStore::COMMENT_CHARACTER_LIMIT,
293 'maxlength-unit' => 'codepoints',
294 'infusable' => true,
295 'default' => $reason,
296 'required' => true,
297 ],
298 ];
299
300 if ( $this->permissionManager->userHasRight( $performer, 'move' ) ) {
301 $formDescriptor['confirm'] = [
302 'type' => 'check',
303 'id' => 'movepages',
304 'name' => 'movepages',
305 'label-message' => 'renameusermove',
306 'default' => $moveChecked,
307 ];
308 }
309 if ( $this->permissionManager->userHasRight( $performer, 'suppressredirect' ) ) {
310 $formDescriptor['suppressredirect'] = [
311 'type' => 'check',
312 'id' => 'suppressredirect',
313 'name' => 'suppressredirect',
314 'label-message' => 'renameusersuppress',
315 'default' => $suppressChecked,
316 ];
317 }
318
319 if ( $warnings ) {
320 $warningsHtml = [];
321 foreach ( $warnings as $warning ) {
322 $warningsHtml[] = is_array( $warning ) ?
323 $this->msg( $warning[0] )->params( array_slice( $warning, 1 ) )->parse() :
324 $this->msg( $warning )->parse();
325 }
326
327 $formDescriptor['renameuserwarnings'] = [
328 'type' => 'info',
329 'label-message' => 'renameuserwarnings',
330 'raw' => true,
331 'rawrow' => true,
332 'default' => new FieldLayout(
333 new MessageWidget( [
334 'label' => new HtmlSnippet(
335 '<ul><li>'
336 . implode( '</li><li>', $warningsHtml )
337 . '</li></ul>'
338 ),
339 'type' => 'warning',
340 ] )
341 ),
342 ];
343
344 $formDescriptor['confirmaction'] = [
345 'type' => 'check',
346 'name' => 'confirmaction',
347 'id' => 'confirmaction',
348 'label-message' => 'renameuserconfirm',
349 ];
350 }
351
352 $htmlForm = HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() )
353 ->setMethod( 'post' )
354 ->setId( 'renameuser' )
355 ->setSubmitTextMsg( 'renameusersubmit' );
356
357 $this->getOutput()->addHTML( $htmlForm->prepareForm()->getHTML( false ) );
358 }
359
368 public function prefixSearchSubpages( $search, $limit, $offset ) {
369 $user = $this->userFactory->newFromName( $search );
370 if ( !$user ) {
371 // No prefix suggestion for invalid user
372 return [];
373 }
374 // Autocomplete subpage as user list - public to allow caching
375 return $this->userNamePrefixSearch->search( 'public', $search, $limit, $offset );
376 }
377
378 protected function getGroupName() {
379 return 'users';
380 }
381}
382
387class_alias( SpecialRenameUser::class, 'SpecialRenameuser' );
const NS_USER
Definition Defines.php:67
Handle database storage of comments such as edit summaries and log reasons.
Show an error when the user tries to do something whilst blocked.
Object handling generic submission, CSRF protection, layout and other logic for UI forms in a reusabl...
Definition HTMLForm.php:209
This class is a collection of static functions that serve two purposes:
Definition Html.php:57
A class containing constants representing the names of configuration variables.
const CapitalLinks
Name constant for the CapitalLinks setting, for use with Config::get()
A service class for checking permissions To obtain an instance, use MediaWikiServices::getInstance()-...
Parent class for all special pages.
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes!
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,...
displayRestrictionError()
Output an error message telling the user what access level they have to have.
getUser()
Shortcut to get the User executing this instance.
useTransactionalTimeLimit()
Call wfTransactionalTimeLimit() if this request was POSTed.
checkPermissions()
Checks if userCanExecute, and if not throws a PermissionsError.
checkReadOnly()
If the wiki is currently in readonly mode, throws a ReadOnlyError.
getConfig()
Shortcut to get main config object.
getRequest()
Get the WebRequest being used for this instance.
getOutput()
Get the OutputPage being used for this instance.
getContentLanguage()
Shortcut to get content language.
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
execute( $par)
Show the special page.
doesWrites()
Indicates whether POST requests to this special page require write access to the wiki.
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.
__construct(IConnectionProvider $dbConns, PermissionManager $permissionManager, TitleFactory $titleFactory, UserFactory $userFactory, UserNamePrefixSearch $userNamePrefixSearch, RenameUserFactory $renameUserFactory)
Creates Title objects.
Create User objects.
isUserTableShared()
Returns if the user table is shared with other wikis.
Handles searching prefixes of user names.
Provide primary and replica IDatabase connections.