MediaWiki master
RenameUser.php
Go to the documentation of this file.
1<?php
2
4
5use LogicException;
22use Psr\Log\LoggerInterface;
23
30
32 private $performer;
34 private $target;
36 private $oldName;
38 private $newName;
40 private $reason;
41
43 private $forceGlobalDetach = false;
45 private $movePages = true;
47 private $suppressRedirect = false;
49 private $derived = false;
50
51 private CentralIdLookupFactory $centralIdLookupFactory;
52 private JobQueueGroupFactory $jobQueueGroupFactory;
53 private MovePageFactory $movePageFactory;
54 private UserFactory $userFactory;
55 private UserNameUtils $userNameUtils;
56 private PermissionManager $permissionManager;
57 private TitleFactory $titleFactory;
58 private LoggerInterface $logger;
59
63 public const CONSTRUCTOR_OPTIONS = [
65 ];
66
67 private array $localDatabases;
68
91 public function __construct(
92 ServiceOptions $options,
93 CentralIdLookupFactory $centralIdLookupFactory,
94 JobQueueGroupFactory $jobQueueGroupFactory,
95 MovePageFactory $movePageFactory,
96 UserFactory $userFactory,
97 UserNameUtils $userNameUtils,
98 PermissionManager $permissionManager,
99 TitleFactory $titleFactory,
100 User $performer,
101 User $target,
102 string $oldName,
103 string $newName,
104 string $reason,
105 array $renameOptions
106 ) {
107 $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
108 $this->localDatabases = $options->get( MainConfigNames::LocalDatabases );
109
110 $this->centralIdLookupFactory = $centralIdLookupFactory;
111 $this->movePageFactory = $movePageFactory;
112 $this->jobQueueGroupFactory = $jobQueueGroupFactory;
113 $this->userFactory = $userFactory;
114 $this->userNameUtils = $userNameUtils;
115 $this->permissionManager = $permissionManager;
116 $this->titleFactory = $titleFactory;
117 $this->logger = LoggerFactory::getInstance( 'RenameUser' );
118
119 foreach ( [
120 'forceGlobalDetach',
121 'movePages',
122 'suppressRedirect',
123 'derived',
124 ] as $possibleOption ) {
125 if ( isset( $renameOptions[ $possibleOption ] ) ) {
126 $this->$possibleOption = $renameOptions[ $possibleOption ];
127 }
128 }
129
130 $this->performer = $performer;
131 $this->target = $target;
132 $this->oldName = $oldName;
133 $this->newName = $newName;
134 $this->reason = $reason;
135 }
136
144 private function check(): Status {
145 // Check if the user has a proper name
146 // The wiki triggering a global rename across a wiki family using virtual domains
147 // may not have the same user database as this wiki
148 $expectedName = $this->oldName;
149 if ( $this->derived && $this->userFactory->isUserTableShared() ) {
150 $expectedName = $this->newName;
151 }
152 if ( $this->target->getName() !== $expectedName ) {
153 return Status::newFatal( 'renameuser-error-unexpected-name' );
154 }
155
156 // Check user names valid
157 $newRigor = $this->derived ? UserFactory::RIGOR_NONE : UserFactory::RIGOR_CREATABLE;
158 $oldUser = $this->userFactory->newFromName( $this->oldName, UserFactory::RIGOR_NONE );
159 $newUser = $this->userFactory->newFromName( $this->newName, $newRigor );
160 if ( !$oldUser ) {
161 return Status::newFatal( 'renameusererrorinvalid', $this->oldName );
162 }
163 if ( !$newUser ) {
164 return Status::newFatal( 'renameusererrorinvalid', $this->newName );
165 }
166 $currentUser = $this->derived ? $newUser : $oldUser;
167 if ( !$currentUser->isRegistered() ) {
168 return Status::newFatal( 'renameusererrordoesnotexist', $currentUser->getName() );
169 }
170 if ( !$this->derived && $newUser->isRegistered() ) {
171 return Status::newFatal( 'renameusererrorexists', $this->newName );
172 }
173
174 // Do not act on temp users
175 if ( $this->userNameUtils->isTemp( $this->oldName ) ) {
176 return Status::newFatal( 'renameuser-error-temp-user', $this->oldName );
177 }
178 if (
179 $this->userNameUtils->isTemp( $this->newName ) ||
180 $this->userNameUtils->isTempReserved( $this->newName )
181 ) {
182 return Status::newFatal( 'renameuser-error-temp-user-reserved', $this->newName );
183 }
184
185 // Check global detaching
186 $centralIdLookup = $this->centralIdLookupFactory->getNonLocalLookup();
187 $userCentralAttached = $centralIdLookup && $centralIdLookup->isAttached( $this->target );
188 if ( !$this->forceGlobalDetach && $userCentralAttached ) {
189 return Status::newFatal( 'renameuser-error-global-detaching' );
190 }
191
192 return Status::newGood();
193 }
194
199 public function renameLocal(): Status {
200 $status = $this->check();
201 if ( !$status->isOK() ) {
202 return $status;
203 }
204
205 $user = $this->target;
206 $performer = $this->performer;
207 $oldName = $this->oldName;
208 $newName = $this->newName;
209
210 $options = [
211 'reason' => $this->reason,
212 'derived' => $this->derived,
213 ];
214
215 // Do the heavy lifting ...
216 $rename = new RenameuserSQL(
217 $oldName,
218 $newName,
219 $user->getId(),
220 $performer,
221 $options
222 );
223 $status->merge( $rename->renameUser() );
224 if ( !$status->isOK() ) {
225 return $status;
226 }
227
228 // If the user is renaming themself, make sure that code below uses a proper name
229 if ( $performer->getId() === $user->getId() ) {
230 $performer->setName( $newName );
231 $this->performer->setName( $newName );
232 }
233
234 // Move any user pages
235 $status->merge( $this->moveUserPages() );
236
237 return $status;
238 }
239
244 public function moveUserPages(): Status {
245 if ( $this->movePages && $this->permissionManager->userHasRight( $this->performer, 'move' ) ) {
246 $suppressRedirect = $this->suppressRedirect
247 && $this->permissionManager->userHasRight( $this->performer, 'suppressredirect' );
248 $oldTitle = $this->titleFactory->makeTitle( NS_USER, $this->oldName );
249 $newTitle = $this->titleFactory->makeTitle( NS_USER, $this->newName );
250
251 $status = $this->movePagesAndSubPages( $this->performer, $oldTitle, $newTitle, $suppressRedirect );
252 if ( !$status->isOK() ) {
253 return $status;
254 }
255
256 $oldTalkTitle = $oldTitle->getTalkPageIfDefined();
257 $newTalkTitle = $newTitle->getTalkPageIfDefined();
258 if ( $oldTalkTitle && $newTalkTitle ) {
259 $status = $this->movePagesAndSubPages(
260 $this->performer,
261 $oldTalkTitle,
262 $newTalkTitle,
263 $suppressRedirect
264 );
265 if ( !$status->isOK() ) {
266 return $status;
267 }
268 }
269 }
270 return Status::newGood();
271 }
272
273 private function movePagesAndSubPages(
274 User $performer, Title $oldTitle, Title $newTitle, bool $suppressRedirect
275 ): Status {
276 $status = Status::newGood();
277
278 $movePage = $this->movePageFactory->newMovePage(
279 $oldTitle,
280 $newTitle,
281 );
282 $movePage->setMaximumMovedPages( -1 );
283 $logMessage = RequestContext::getMain()->msg(
284 'renameuser-move-log',
285 $oldTitle->getText(),
286 $newTitle->getText()
287 )->inContentLanguage()->text();
288
289 if ( $oldTitle->exists() ) {
290 $status->merge( $movePage->moveIfAllowed( $performer, $logMessage, !$suppressRedirect ) );
291 if ( !$status->isGood() ) {
292 return $status;
293 }
294 }
295
296 $batchStatus = $movePage->moveSubpagesIfAllowed( $performer, $logMessage, !$suppressRedirect );
297 foreach ( $batchStatus->getValue() as $titleText => $moveStatus ) {
298 $status->merge( $moveStatus );
299 }
300 return $status;
301 }
302
313 public function renameGlobal(): Status {
314 if ( $this->derived ) {
315 throw new LogicException( "Can't rename globally with a command created with newDerivedRenameUser()" );
316 }
317 $status = $this->renameLocal();
318 if ( !$status->isGood() ) {
319 return $status;
320 }
321
322 // Create jobs for other wikis if needed
323 if ( $this->userFactory->isUserTableShared() ) {
324 foreach ( $this->localDatabases as $database ) {
325 if ( $database == WikiMap::getCurrentWikiDbDomain()->getId() ) {
326 continue;
327 }
328 $status->merge( $this->enqueueRemoteRename( $database ) );
329 }
330 }
331
332 return $status;
333 }
334
343 private function enqueueRemoteRename( string $database ): Status {
344 $jobParams = [
345 'oldname' => $this->oldName,
346 'newname' => $this->newName,
347 'uid' => $this->target->getId(),
348 'performer' => $this->performer->getId(),
349 'reason' => $this->reason,
350 'movePages' => $this->movePages,
351 'suppressRedirect' => $this->suppressRedirect,
352 ];
353 $oldTitle = $this->titleFactory->makeTitle( NS_USER, $this->oldName );
354 $this->logger->info( "Enqueuing a rename job for domain {$database}" );
355 $job = new JobSpecification( 'renameUserDerived', $jobParams, [], $oldTitle );
356 $this->jobQueueGroupFactory->makeJobQueueGroup( $database )->push( $job );
357 return Status::newGood();
358 }
359
369 public function renameUnsafe(): Status {
370 if ( !$this->derived && $this->userFactory->isUserTableShared() ) {
371 return $this->renameGlobal();
372 } else {
373 return $this->renameLocal();
374 }
375 }
376
385 public function rename(): Status {
386 // renameuser is always required
387 if ( !$this->permissionManager->userHasRight( $this->performer, 'renameuser' ) ) {
388 return Status::newFatal( 'badaccess-groups', 'renameuser' );
389 }
390
391 // for global renames, renameuser-global is also required
392 $centralIdLookup = $this->centralIdLookupFactory->getNonLocalLookup();
393 $userCentralAttached = $centralIdLookup && $centralIdLookup->isAttached( $this->target );
394 if ( ( $this->userFactory->isUserTableShared() || $userCentralAttached )
395 && !$this->permissionManager->userHasRight( $this->performer, 'renameuser-global' ) ) {
396 return Status::newFatal( 'badaccess-groups', 'renameuser-global' );
397 }
398
399 return $this->renameUnsafe();
400 }
401}
const NS_USER
Definition Defines.php:67
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:81
A class for passing options to services.
assertRequiredOptions(array $expectedKeys)
Assert that the list of options provided in this instance exactly match $expectedKeys,...
Group all the pieces relevant to the context of a request into one instance.
Factory for JobQueueGroup objects.
Job queue task description base code.
Create PSR-3 logger objects.
A class containing constants representing the names of configuration variables.
const LocalDatabases
Name constant for the LocalDatabases setting, for use with Config::get()
A service class for checking permissions To obtain an instance, use MediaWikiServices::getInstance()-...
Handles the backend logic of renaming users.
rename()
Attempts to perform the rename smartly after checking the performer's rights.
renameGlobal()
Attempts to perform the rename globally.
moveUserPages()
Attempts to move local user pages.
renameUnsafe()
Attempts to perform the rename smartly.
__construct(ServiceOptions $options, CentralIdLookupFactory $centralIdLookupFactory, JobQueueGroupFactory $jobQueueGroupFactory, MovePageFactory $movePageFactory, UserFactory $userFactory, UserNameUtils $userNameUtils, PermissionManager $permissionManager, TitleFactory $titleFactory, User $performer, User $target, string $oldName, string $newName, string $reason, array $renameOptions)
renameLocal()
Performs the rename in local domain.
Class which performs the actual renaming of users.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:54
Creates Title objects.
Represents a title within MediaWiki.
Definition Title.php:78
Create User objects.
UserNameUtils service.
User class for the MediaWiki software.
Definition User.php:121
getId( $wikiId=self::LOCAL)
Get the user's ID.
Definition User.php:1579
setName( $str)
Set the user name.
Definition User.php:1640
Tools for dealing with other locally-hosted wikis.
Definition WikiMap.php:31
Service for page rename actions.
if(count( $args)< 1) $job