MediaWiki master
RenameUser.php
Go to the documentation of this file.
1<?php
2
4
5use LogicException;
22use Psr\Log\LoggerInterface;
24
31
33 private $performer;
35 private $target;
37 private $oldName;
39 private $newName;
41 private $reason;
42
44 private $forceGlobalDetach = false;
46 private $movePages = true;
48 private $suppressRedirect = false;
50 private $derived = false;
51
52 private readonly LoggerInterface $logger;
53
57 public const CONSTRUCTOR_OPTIONS = [
59 ];
60
69 public function __construct(
70 private readonly ServiceOptions $options,
71 private readonly CentralIdLookupFactory $centralIdLookupFactory,
72 private readonly IConnectionProvider $dbProvider,
73 private readonly JobQueueGroupFactory $jobQueueGroupFactory,
74 private readonly MovePageFactory $movePageFactory,
75 private readonly UserFactory $userFactory,
76 private readonly UserNameUtils $userNameUtils,
77 private readonly PermissionManager $permissionManager,
78 private readonly TitleFactory $titleFactory,
79 User $performer,
80 User $target,
81 string $oldName,
82 string $newName,
83 string $reason,
84 array $renameOptions
85 ) {
86 $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
87 $this->logger = LoggerFactory::getInstance( 'RenameUser' );
88
89 foreach ( [
90 'forceGlobalDetach',
91 'movePages',
92 'suppressRedirect',
93 'derived',
94 ] as $possibleOption ) {
95 if ( isset( $renameOptions[ $possibleOption ] ) ) {
96 $this->$possibleOption = $renameOptions[ $possibleOption ];
97 }
98 }
99
100 $this->performer = $performer;
101 $this->target = $target;
102 $this->oldName = $oldName;
103 $this->newName = $newName;
104 $this->reason = $reason;
105 }
106
114 private function check(): Status {
115 // Check if the user has a proper name
116 // The wiki triggering a global rename across a wiki family using virtual domains
117 // may not have the same user database as this wiki
118 $expectedName = $this->oldName;
119 if ( $this->derived && $this->userFactory->isUserTableShared() ) {
120 $expectedName = $this->newName;
121 }
122 if ( $this->target->getName() !== $expectedName ) {
123 return Status::newFatal( 'renameuser-error-unexpected-name' );
124 }
125
126 // Check user names valid
127 $newRigor = $this->derived ? UserFactory::RIGOR_NONE : UserFactory::RIGOR_CREATABLE;
128 $oldUser = $this->userFactory->newFromName( $this->oldName, UserFactory::RIGOR_NONE );
129 $newUser = $this->userFactory->newFromName( $this->newName, $newRigor );
130 if ( !$oldUser ) {
131 return Status::newFatal( 'renameusererrorinvalid', $this->oldName );
132 }
133 if ( !$newUser ) {
134 return Status::newFatal( 'renameusererrorinvalid', $this->newName );
135 }
136 $currentUser = $this->derived ? $newUser : $oldUser;
137 if ( !$currentUser->isRegistered() ) {
138 return Status::newFatal( 'renameusererrordoesnotexist', $currentUser->getName() );
139 }
140 if ( !$this->derived && $newUser->isRegistered() ) {
141 return Status::newFatal( 'renameusererrorexists', $this->newName );
142 }
143
144 // Do not act on temp users
145 if ( $this->userNameUtils->isTemp( $this->oldName ) ) {
146 return Status::newFatal( 'renameuser-error-temp-user', $this->oldName );
147 }
148 if (
149 $this->userNameUtils->isTemp( $this->newName ) ||
150 $this->userNameUtils->isTempReserved( $this->newName )
151 ) {
152 return Status::newFatal( 'renameuser-error-temp-user-reserved', $this->newName );
153 }
154
155 // Check global detaching
156 $centralIdLookup = $this->centralIdLookupFactory->getNonLocalLookup();
157 $userCentralAttached = $centralIdLookup && $centralIdLookup->isAttached( $this->target );
158 if ( !$this->forceGlobalDetach && $userCentralAttached ) {
159 return Status::newFatal( 'renameuser-error-global-detaching' );
160 }
161
162 return Status::newGood();
163 }
164
169 public function renameLocal(): Status {
170 $status = $this->check();
171 if ( !$status->isOK() ) {
172 return $status;
173 }
174
175 $user = $this->target;
176 $performer = $this->performer;
177 $oldName = $this->oldName;
178 $newName = $this->newName;
179
180 $options = [
181 'reason' => $this->reason,
182 'derived' => $this->derived,
183 ];
184
185 // Do the heavy lifting ...
186 $rename = new RenameuserSQL(
187 $oldName,
188 $newName,
189 $user->getId(),
190 $performer,
191 $options
192 );
193 $status->merge( $rename->renameUser() );
194 if ( !$status->isOK() ) {
195 return $status;
196 }
197
198 // If the user is renaming themself, make sure that code below uses a proper name
199 if ( $performer->getId() === $user->getId() ) {
200 $performer->setName( $newName );
201 $this->performer->setName( $newName );
202 }
203
204 // Move any user pages
205 $status->merge( $this->moveUserPages() );
206
207 return $status;
208 }
209
214 public function moveUserPages(): Status {
215 if ( $this->movePages && $this->permissionManager->userHasRight( $this->performer, 'move' ) ) {
216 $suppressRedirect = $this->suppressRedirect
217 && $this->permissionManager->userHasRight( $this->performer, 'suppressredirect' );
218 $oldTitle = $this->titleFactory->makeTitle( NS_USER, $this->oldName );
219 $newTitle = $this->titleFactory->makeTitle( NS_USER, $this->newName );
220
221 $status = $this->movePagesAndSubPages( $this->performer, $oldTitle, $newTitle, $suppressRedirect );
222 if ( !$status->isOK() ) {
223 return $status;
224 }
225
226 $oldTalkTitle = $oldTitle->getTalkPageIfDefined();
227 $newTalkTitle = $newTitle->getTalkPageIfDefined();
228 if ( $oldTalkTitle && $newTalkTitle ) {
229 $status = $this->movePagesAndSubPages(
230 $this->performer,
231 $oldTalkTitle,
232 $newTalkTitle,
233 $suppressRedirect
234 );
235 if ( !$status->isOK() ) {
236 return $status;
237 }
238 }
239 }
240 return Status::newGood();
241 }
242
243 private function movePagesAndSubPages(
244 User $performer, Title $oldTitle, Title $newTitle, bool $suppressRedirect
245 ): Status {
246 $status = Status::newGood();
247
248 $movePage = $this->movePageFactory->newMovePage(
249 $oldTitle,
250 $newTitle,
251 );
252 $movePage->setMaximumMovedPages( -1 );
253 $logMessage = RequestContext::getMain()->msg(
254 'renameuser-move-log',
255 $oldTitle->getText(),
256 $newTitle->getText()
257 )->inContentLanguage()->text();
258
259 if ( $oldTitle->exists() ) {
260 $status->merge( $movePage->moveIfAllowed( $performer, $logMessage, !$suppressRedirect ) );
261 if ( !$status->isGood() ) {
262 return $status;
263 }
264 }
265
266 $batchStatus = $movePage->moveSubpagesIfAllowed( $performer, $logMessage, !$suppressRedirect );
267 foreach ( $batchStatus->getValue() as $titleText => $moveStatus ) {
268 $status->merge( $moveStatus );
269 }
270 return $status;
271 }
272
283 public function renameGlobal(): Status {
284 if ( $this->derived ) {
285 throw new LogicException( "Can't rename globally with a command created with newDerivedRenameUser()" );
286 }
287 $status = $this->renameLocal();
288 if ( !$status->isGood() ) {
289 return $status;
290 }
291
292 // Create jobs for other wikis if needed
293 if ( $this->userFactory->isUserTableShared() ) {
294 foreach ( $this->options->get( MainConfigNames::LocalDatabases ) as $database ) {
295 if ( $database == WikiMap::getCurrentWikiDbDomain()->getId() ) {
296 continue;
297 }
298 $status->merge( $this->enqueueRemoteRename( $database ) );
299 }
300 }
301
302 return $status;
303 }
304
313 private function enqueueRemoteRename( string $database ): Status {
314 $jobParams = [
315 'oldname' => $this->oldName,
316 'newname' => $this->newName,
317 'uid' => $this->target->getId(),
318 'performer' => $this->performer->getId(),
319 'reason' => $this->reason,
320 'movePages' => $this->movePages,
321 'suppressRedirect' => $this->suppressRedirect,
322 ];
323 $oldTitle = $this->titleFactory->makeTitle( NS_USER, $this->oldName );
324 $this->logger->info( "Enqueuing a rename job for domain {$database}" );
325 $job = new JobSpecification( 'renameUserDerived', $jobParams, [], $oldTitle );
326 $this->jobQueueGroupFactory->makeJobQueueGroup( $database )->push( $job );
327 return Status::newGood();
328 }
329
339 public function renameUnsafe(): Status {
340 if ( !$this->derived && $this->userFactory->isUserTableShared() ) {
341 return $this->renameGlobal();
342 } else {
343 return $this->renameLocal();
344 }
345 }
346
355 public function rename(): Status {
356 // renameuser is always required
357 if ( !$this->permissionManager->userHasRight( $this->performer, 'renameuser' ) ) {
358 return Status::newFatal( 'renameuser-error-local-rights' );
359 }
360
361 // for global renames, renameuser-global is also required
362 $centralIdLookup = $this->centralIdLookupFactory->getNonLocalLookup();
363 $userCentralAttached = $centralIdLookup && $centralIdLookup->isAttached( $this->target );
364 if ( ( $this->userFactory->isUserTableShared() || $userCentralAttached )
365 && !$this->permissionManager->userHasRight( $this->performer, 'renameuser-global' ) ) {
366 return Status::newFatal( 'renameuser-error-global-rights' );
367 }
368
369 return $this->renameUnsafe();
370 }
371}
const NS_USER
Definition Defines.php:53
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:69
A class for passing options to services.
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(private readonly ServiceOptions $options, private readonly CentralIdLookupFactory $centralIdLookupFactory, private readonly IConnectionProvider $dbProvider, private readonly JobQueueGroupFactory $jobQueueGroupFactory, private readonly MovePageFactory $movePageFactory, private readonly UserFactory $userFactory, private readonly UserNameUtils $userNameUtils, private readonly PermissionManager $permissionManager, private readonly TitleFactory $titleFactory, User $performer, User $target, string $oldName, string $newName, string $reason, array $renameOptions)
Valid options for $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:44
Creates Title objects.
Represents a title within MediaWiki.
Definition Title.php:69
Create User objects.
UserNameUtils service.
User class for the MediaWiki software.
Definition User.php:130
getId( $wikiId=self::LOCAL)
Get the user's ID.
Definition User.php:1492
setName( $str)
Set the user name.
Definition User.php:1553
Tools for dealing with other locally-hosted wikis.
Definition WikiMap.php:19
Service for page rename actions.
Provide primary and replica IDatabase connections.
if(count( $args)< 1) $job