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
53 public function doesWrites() {
54 return true;
55 }
56
62 public function execute( $par ) {
63 $this->setHeaders();
64 $this->addHelpLink( 'Help:Renameuser' );
65
66 $this->checkPermissions();
67 $this->checkReadOnly();
68
69 $performer = $this->getUser();
70
71 $block = $performer->getBlock();
72 if ( $block ) {
73 throw new UserBlockedError( $block );
74 }
75
76 $out = $this->getOutput();
77 $out->addWikiMsg( 'renameuser-summary' );
78
80
81 $request = $this->getRequest();
82
83 // This works as "/" is not valid in usernames
84 $userNames = $par !== null ? explode( '/', $par, 2 ) : [];
85
86 // Get the old name, applying minimal validation or canonicalization
87 $oldName = $request->getText( 'oldusername', $userNames[0] ?? '' );
88 $oldName = trim( str_replace( '_', ' ', $oldName ) );
89 $oldTitle = $this->titleFactory->makeTitle( NS_USER, $oldName );
90
91 // Get the new name and canonicalize it
92 $origNewName = $request->getText( 'newusername', $userNames[1] ?? '' );
93 $origNewName = trim( str_replace( '_', ' ', $origNewName ) );
94 // Force uppercase of new username, otherwise wikis
95 // with wgCapitalLinks=false can create lc usernames
96 $newTitle = $this->titleFactory->makeTitleSafe( NS_USER, $this->getContentLanguage()->ucfirst( $origNewName ) );
97 $newName = $newTitle ? $newTitle->getText() : '';
98
99 $reason = $request->getText( 'reason' );
100 $moveChecked = $request->getBool( 'movepages', !$request->wasPosted() );
101 $suppressChecked = $request->getCheck( 'suppressredirect' );
102
103 if ( $oldName !== '' && $newName !== '' && !$request->getCheck( 'confirmaction' ) ) {
104 $warnings = $this->getWarnings( $oldName, $newName );
105 } else {
106 $warnings = [];
107 }
108
109 $this->showForm( $oldName, $newName, $warnings, $reason, $moveChecked, $suppressChecked );
110
111 if ( $request->getText( 'wpEditToken' ) === '' ) {
112 # They probably haven't even submitted the form, so don't go further.
113 return;
114 }
115 if ( $warnings ) {
116 # Let user read warnings
117 return;
118 }
119 if (
120 !$request->wasPosted() ||
121 !$performer->matchEditToken( $request->getVal( 'wpEditToken' ) )
122 ) {
123 $out->addHTML( Html::errorBox( $out->msg( 'renameuser-error-request' )->parse() ) );
124
125 return;
126 }
127 if ( !$newTitle ) {
128 $out->addHTML( Html::errorBox(
129 $out->msg( 'renameusererrorinvalid' )->params( $request->getText( 'newusername' ) )->parse()
130 ) );
131
132 return;
133 }
134 if ( $oldName === $newName ) {
135 $out->addHTML( Html::errorBox( $out->msg( 'renameuser-error-same-user' )->parse() ) );
136
137 return;
138 }
139
140 // Suppress username validation of old username
141 $oldUser = $this->userFactory->newFromName( $oldName, $this->userFactory::RIGOR_NONE );
142 $newUser = $this->userFactory->newFromName( $newName, $this->userFactory::RIGOR_CREATABLE );
143
144 // It won't be an object if for instance "|" is supplied as a value
145 if ( !$oldUser ) {
146 $out->addHTML( Html::errorBox(
147 $out->msg( 'renameusererrorinvalid' )->params( $oldTitle->getText() )->parse()
148 ) );
149
150 return;
151 }
152 if ( !$newUser ) {
153 $out->addHTML( Html::errorBox(
154 $out->msg( 'renameusererrorinvalid' )->params( $newTitle->getText() )->parse()
155 ) );
156
157 return;
158 }
159
160 // Check for the existence of lowercase old username in database.
161 // Until r19631 it was possible to rename a user to a name with first character as lowercase
162 if ( $oldName !== $this->getContentLanguage()->ucfirst( $oldName ) ) {
163 // old username was entered as lowercase -> check for existence in table 'user'
164 $dbr = $this->dbConns->getReplicaDatabase();
165 $uid = $dbr->newSelectQueryBuilder()
166 ->select( 'user_id' )
167 ->from( 'user' )
168 ->where( [ 'user_name' => $oldName ] )
169 ->caller( __METHOD__ )
170 ->fetchField();
171 if ( $uid === false ) {
172 if ( !$this->getConfig()->get( MainConfigNames::CapitalLinks ) ) {
173 $uid = 0; // We are on a lowercase wiki but lowercase username does not exist
174 } else {
175 // We are on a standard uppercase wiki, use normal
176 $uid = $oldUser->idForName();
177 $oldTitle = $this->titleFactory->makeTitleSafe( NS_USER, $oldUser->getName() );
178 if ( !$oldTitle ) {
179 $out->addHTML( Html::errorBox(
180 $out->msg( 'renameusererrorinvalid' )->params( $oldName )->parse()
181 ) );
182 return;
183 }
184 $oldName = $oldTitle->getText();
185 }
186 }
187 } else {
188 // old username was entered as uppercase -> standard procedure
189 $uid = $oldUser->idForName();
190 }
191
192 if ( $uid === 0 ) {
193 $out->addHTML( Html::errorBox(
194 $out->msg( 'renameusererrordoesnotexist' )->params( $oldName )->parse()
195 ) );
196
197 return;
198 }
199
200 if ( $newUser->idForName() !== 0 ) {
201 $out->addHTML( Html::errorBox(
202 $out->msg( 'renameusererrorexists' )->params( $newName )->parse()
203 ) );
204
205 return;
206 }
207
208 // Check user rights again
209 // This is needed because SpecialPage::__construct only supports
210 // checking for one right, but both renameuser and -global is required
211 // to rename a global user.
212 if ( !$this->permissionManager->userHasRight( $performer, 'renameuser' ) ) {
214 }
215 if ( $this->userFactory->isUserTableShared()
216 && !$this->permissionManager->userHasRight( $performer, 'renameuser-global' ) ) {
217 $out->addHTML( Html::errorBox( $out->msg( 'renameuser-error-global-rights' )->parse() ) );
218 return;
219 }
220
221 // Give other affected extensions a chance to validate or abort
222 if ( !$this->getHookRunner()->onRenameUserAbort( $uid, $oldName, $newName ) ) {
223 return;
224 }
225
226 $rename = $this->renameUserFactory->newRenameUser( $performer, $oldUser, $newName, $reason, [
227 'movePages' => $moveChecked,
228 'suppressRedirect' => $suppressChecked,
229 ] );
230 $status = $rename->rename();
231
232 if ( $status->isGood() ) {
233 // Output success message stuff :)
234 $out->addHTML(
235 Html::successBox(
236 $out->msg( 'renameusersuccess' )
237 ->params( $oldTitle->getText(), $newTitle->getText() )
238 ->parse()
239 )
240 );
241 } else {
242 // Output errors stuff
243 $outHtml = '';
244 foreach ( $status->getMessages() as $msg ) {
245 $outHtml = $outHtml . $out->msg( $msg )->parse() . '<br/>';
246 }
247 if ( $status->isOK() ) {
248 $out->addHTML( Html::warningBox( $outHtml ) );
249 } else {
250 $out->addHTML( Html::errorBox( $outHtml ) );
251 }
252 }
253 }
254
255 private function getWarnings( string $oldName, string $newName ): array {
256 $warnings = [];
257 $oldUser = $this->userFactory->newFromName( $oldName, $this->userFactory::RIGOR_NONE );
258 if ( $oldUser && !$oldUser->isTemp() && $oldUser->getBlock() ) {
259 $warnings[] = [
260 'renameuser-warning-currentblock',
261 SpecialPage::getTitleFor( 'Log', 'block' )->getFullURL( [ 'page' => $oldName ] )
262 ];
263 }
264 $this->getHookRunner()->onRenameUserWarning( $oldName, $newName, $warnings );
265 return $warnings;
266 }
267
268 private function showForm(
269 ?string $oldName, ?string $newName, array $warnings, string $reason, bool $moveChecked, bool $suppressChecked
270 ) {
271 $performer = $this->getUser();
272
273 $formDescriptor = [
274 'oldusername' => [
275 'type' => 'user',
276 'name' => 'oldusername',
277 'label-message' => 'renameuserold',
278 'default' => $oldName,
279 'required' => true,
280 'excludetemp' => true,
281 ],
282 'newusername' => [
283 'type' => 'text',
284 'name' => 'newusername',
285 'label-message' => 'renameusernew',
286 'default' => $newName,
287 'required' => true,
288 ],
289 'reason' => [
290 'type' => 'text',
291 'name' => 'reason',
292 'label-message' => 'renameuserreason',
293 'maxlength' => CommentStore::COMMENT_CHARACTER_LIMIT,
294 'maxlength-unit' => 'codepoints',
295 'infusable' => true,
296 'default' => $reason,
297 'required' => true,
298 ],
299 ];
300
301 if ( $this->permissionManager->userHasRight( $performer, 'move' ) ) {
302 $formDescriptor['confirm'] = [
303 'type' => 'check',
304 'id' => 'movepages',
305 'name' => 'movepages',
306 'label-message' => 'renameusermove',
307 'default' => $moveChecked,
308 ];
309 }
310 if ( $this->permissionManager->userHasRight( $performer, 'suppressredirect' ) ) {
311 $formDescriptor['suppressredirect'] = [
312 'type' => 'check',
313 'id' => 'suppressredirect',
314 'name' => 'suppressredirect',
315 'label-message' => 'renameusersuppress',
316 'default' => $suppressChecked,
317 ];
318 }
319
320 if ( $warnings ) {
321 $warningsHtml = [];
322 foreach ( $warnings as $warning ) {
323 $warningsHtml[] = is_array( $warning ) ?
324 $this->msg( $warning[0] )->params( array_slice( $warning, 1 ) )->parse() :
325 $this->msg( $warning )->parse();
326 }
327
328 $formDescriptor['renameuserwarnings'] = [
329 'type' => 'info',
330 'label-message' => 'renameuserwarnings',
331 'raw' => true,
332 'rawrow' => true,
333 'default' => new FieldLayout(
334 new MessageWidget( [
335 'label' => new HtmlSnippet(
336 '<ul><li>'
337 . implode( '</li><li>', $warningsHtml )
338 . '</li></ul>'
339 ),
340 'type' => 'warning',
341 ] )
342 ),
343 ];
344
345 $formDescriptor['confirmaction'] = [
346 'type' => 'check',
347 'name' => 'confirmaction',
348 'id' => 'confirmaction',
349 'label-message' => 'renameuserconfirm',
350 ];
351 }
352
353 $htmlForm = HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() )
354 ->setMethod( 'post' )
355 ->setId( 'renameuser' )
356 ->setSubmitTextMsg( 'renameusersubmit' );
357
358 $this->getOutput()->addHTML( $htmlForm->prepareForm()->getHTML( false ) );
359 }
360
369 public function prefixSearchSubpages( $search, $limit, $offset ) {
370 $user = $this->userFactory->newFromName( $search );
371 if ( !$user ) {
372 // No prefix suggestion for invalid user
373 return [];
374 }
375 // Autocomplete subpage as user list - public to allow caching
376 return $this->userNamePrefixSearch->search( 'public', $search, $limit, $offset );
377 }
378
380 protected function getGroupName() {
381 return 'users';
382 }
383}
384
389class_alias( SpecialRenameUser::class, 'SpecialRenameuser' );
const NS_USER
Definition Defines.php:53
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:195
This class is a collection of static functions that serve two purposes:
Definition Html.php:43
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.
msg( $key,... $params)