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