MediaWiki master
UserFactory.php
Go to the documentation of this file.
1<?php
23namespace MediaWiki\User;
24
26use InvalidArgumentException;
30use stdClass;
34
40class UserFactory implements UserRigorOptions {
41
48 public const CONSTRUCTOR_OPTIONS = [
51 ];
52
54 private $options;
55
57 private $loadBalancerFactory;
58
60 private $loadBalancer;
61
63 private $userNameUtils;
64
66 private $lastUserFromIdentity = null;
67
73 public function __construct(
74 ServiceOptions $options,
75 ILBFactory $loadBalancerFactory,
76 UserNameUtils $userNameUtils
77 ) {
78 $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
79 $this->options = $options;
80 $this->loadBalancerFactory = $loadBalancerFactory;
81 $this->loadBalancer = $loadBalancerFactory->getMainLB();
82 $this->userNameUtils = $userNameUtils;
83 }
84
103 public function newFromName(
104 string $name,
105 string $validate = self::RIGOR_VALID
106 ): ?User {
107 // RIGOR_* constants are the same here and in the UserNameUtils class
108 $canonicalName = $this->userNameUtils->getCanonical( $name, $validate );
109 if ( $canonicalName === false ) {
110 return null;
111 }
112
113 $user = new User();
114 $user->mName = $canonicalName;
115 $user->mFrom = 'name';
116 $user->setItemLoaded( 'name' );
117 return $user;
118 }
119
128 public function newAnonymous( ?string $ip = null ): User {
129 if ( $ip ) {
130 if ( !$this->userNameUtils->isIP( $ip ) ) {
131 throw new InvalidArgumentException( 'Invalid IP address' );
132 }
133 $user = new User();
134 $user->setName( $ip );
135 } else {
136 $user = new User();
137 }
138 return $user;
139 }
140
149 public function newFromId( int $id ): User {
150 $user = new User();
151 $user->mId = $id;
152 $user->mFrom = 'id';
153 $user->setItemLoaded( 'id' );
154 return $user;
155 }
156
165 public function newFromActorId( int $actorId ): User {
166 $user = new User();
167 $user->mActorId = $actorId;
168 $user->mFrom = 'actor';
169 $user->setItemLoaded( 'actor' );
170 return $user;
171 }
172
181 public function newFromUserIdentity( UserIdentity $userIdentity ): User {
182 if ( $userIdentity instanceof User ) {
183 return $userIdentity;
184 }
185
186 $id = $userIdentity->getId();
187 $name = $userIdentity->getName();
188 // Cache the $userIdentity we converted last. This avoids redundant conversion
189 // in cases where we would be converting the same UserIdentity over and over,
190 // for instance because we need to access data preferences when formatting
191 // timestamps in a listing.
192 if (
193 $this->lastUserFromIdentity
194 && $this->lastUserFromIdentity->getId() === $id
195 && $this->lastUserFromIdentity->getName() === $name
196 ) {
197 return $this->lastUserFromIdentity;
198 }
199
200 $this->lastUserFromIdentity = $this->newFromAnyId(
201 $id === 0 ? null : $id,
202 $name === '' ? null : $name,
203 null
204 );
205
206 return $this->lastUserFromIdentity;
207 }
208
224 public function newFromAnyId(
225 ?int $userId,
226 ?string $userName,
227 ?int $actorId = null,
228 $dbDomain = false
229 ): User {
230 // Stop-gap solution for the problem described in T222212.
231 // Force the User ID and Actor ID to zero for users loaded from the database
232 // of another wiki, to prevent subtle data corruption and confusing failure modes.
233 if ( $dbDomain !== false ) {
234 $userId = 0;
235 $actorId = 0;
236 }
237
238 $user = new User;
239 $user->mFrom = 'defaults';
240
241 if ( $actorId !== null ) {
242 $user->mActorId = $actorId;
243 if ( $actorId !== 0 ) {
244 $user->mFrom = 'actor';
245 }
246 $user->setItemLoaded( 'actor' );
247 }
248
249 if ( $userName !== null && $userName !== '' ) {
250 $user->mName = $userName;
251 $user->mFrom = 'name';
252 $user->setItemLoaded( 'name' );
253 }
254
255 if ( $userId !== null ) {
256 $user->mId = $userId;
257 if ( $userId !== 0 ) {
258 $user->mFrom = 'id';
259 }
260 $user->setItemLoaded( 'id' );
261 }
262
263 if ( $user->mFrom === 'defaults' ) {
264 throw new InvalidArgumentException(
265 'Cannot create a user with no name, no ID, and no actor ID'
266 );
267 }
268
269 return $user;
270 }
271
284 public function newFromConfirmationCode(
285 string $confirmationCode,
286 int $flags = IDBAccessObject::READ_NORMAL
287 ) {
288 if ( ( $flags & IDBAccessObject::READ_LATEST ) == IDBAccessObject::READ_LATEST ) {
289 $db = $this->loadBalancer->getConnectionRef( DB_PRIMARY );
290 } else {
291 $db = $this->loadBalancer->getConnectionRef( DB_REPLICA );
292 }
293
294 $id = $db->newSelectQueryBuilder()
295 ->select( 'user_id' )
296 ->from( 'user' )
297 ->where( [ 'user_email_token' => md5( $confirmationCode ) ] )
298 ->andWhere( $db->expr( 'user_email_token_expires', '>', $db->timestamp() ) )
299 ->recency( $flags )
300 ->caller( __METHOD__ )->fetchField();
301
302 if ( !$id ) {
303 return null;
304 }
305
306 return $this->newFromId( (int)$id );
307 }
308
318 public function newFromRow( $row, $data = null ) {
319 return User::newFromRow( $row, $data );
320 }
321
327 public function newFromAuthority( Authority $authority ): User {
328 if ( $authority instanceof User ) {
329 return $authority;
330 }
331 return $this->newFromUserIdentity( $authority->getUser() );
332 }
333
342 public function newTempPlaceholder() {
343 $user = new User();
344 $user->setName( $this->userNameUtils->getTempPlaceholder() );
345 return $user;
346 }
347
355 public function newUnsavedTempUser( ?string $name ) {
356 $user = new User();
357 $user->setName( $name ?? $this->userNameUtils->getTempPlaceholder() );
358 return $user;
359 }
360
366 public function invalidateCache( UserIdentity $userIdentity ) {
367 if ( !$userIdentity->isRegistered() ) {
368 return;
369 }
370
371 $wikiId = $userIdentity->getWikiId();
372 if ( $wikiId === UserIdentity::LOCAL ) {
373 $legacyUser = $this->newFromUserIdentity( $userIdentity );
374 // Update user_touched within User class to manage state of User::mTouched for CAS check
375 $legacyUser->invalidateCache();
376 } else {
377 // cross-wiki invalidation
378 $userId = $userIdentity->getId( $wikiId );
379
380 $dbw = $this->getUserTableConnection( ILoadBalancer::DB_PRIMARY, $wikiId );
381 $dbw->newUpdateQueryBuilder()
382 ->update( 'user' )
383 ->set( [ 'user_touched' => $dbw->timestamp() ] )
384 ->where( [ 'user_id' => $userId ] )
385 ->caller( __METHOD__ )->execute();
386
387 $dbw->onTransactionPreCommitOrIdle(
388 static function () use ( $wikiId, $userId ) {
389 User::purge( $wikiId, $userId );
390 },
391 __METHOD__
392 );
393 }
394 }
395
401 private function getUserTableConnection( $mode, $wikiId ) {
402 if ( is_string( $wikiId ) && $this->loadBalancerFactory->getLocalDomainID() === $wikiId ) {
403 $wikiId = UserIdentity::LOCAL;
404 }
405
406 if ( $this->options->get( MainConfigNames::SharedDB ) &&
407 in_array( 'user', $this->options->get( MainConfigNames::SharedTables ) )
408 ) {
409 // The main LB is aliased for the shared database in Setup.php
410 $lb = $this->loadBalancer;
411 } else {
412 $lb = $this->loadBalancerFactory->getMainLB( $wikiId );
413 }
414
415 return $lb->getConnection( $mode, [], $wikiId );
416 }
417}
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,...
A class containing constants representing the names of configuration variables.
const SharedDB
Name constant for the SharedDB setting, for use with Config::get()
const SharedTables
Name constant for the SharedTables setting, for use with Config::get()
Creates User objects.
newFromId(int $id)
Factory method for creation from a given user ID, replacing User::newFromId.
newFromAuthority(Authority $authority)
newFromName(string $name, string $validate=self::RIGOR_VALID)
Factory method for creating users by name, replacing static User::newFromName.
newAnonymous(?string $ip=null)
Returns a new anonymous User based on ip.
invalidateCache(UserIdentity $userIdentity)
Purge user related caches, "touch" the user table to invalidate further caches.
newFromRow( $row, $data=null)
newFromAnyId(?int $userId, ?string $userName, ?int $actorId=null, $dbDomain=false)
Factory method for creation from an ID, name, and/or actor ID, replacing User::newFromAnyId.
newFromUserIdentity(UserIdentity $userIdentity)
Factory method for creation fom a given UserIdentity, replacing User::newFromIdentity.
__construct(ServiceOptions $options, ILBFactory $loadBalancerFactory, UserNameUtils $userNameUtils)
newFromConfirmationCode(string $confirmationCode, int $flags=IDBAccessObject::READ_NORMAL)
Factory method to fetch the user for a given email confirmation code, replacing User::newFromConfirma...
newUnsavedTempUser(?string $name)
Create an unsaved temporary user with a previously acquired name or a placeholder name.
newTempPlaceholder()
Create a placeholder user for an anonymous user who will be upgraded to a temporary user.
newFromActorId(int $actorId)
Factory method for creation from a given actor ID, replacing User::newFromActorId.
const CONSTRUCTOR_OPTIONS
RIGOR_* constants are inherited from UserRigorOptions READ_* constants are inherited from IDBAccessOb...
UserNameUtils service.
internal since 1.36
Definition User.php:93
setItemLoaded( $item)
Set that an item has been loaded.
Definition User.php:1052
Interface for database access objects.
getWikiId()
Get the ID of the wiki this page belongs to.
This interface represents the authority associated with the current execution context,...
Definition Authority.php:37
Interface for objects representing user identity.
isRegistered()
This must be equivalent to getId() != 0 and is provided for code readability.
getId( $wikiId=self::LOCAL)
Shared interface for rigor levels when dealing with User methods.
Basic database interface for live and lazy-loaded relation database handles.
Definition IDatabase.php:36
Manager of ILoadBalancer objects and, indirectly, IDatabase connections.
getMainLB( $domain=false)
Get the tracked load balancer instance for the main cluster that handles the given domain.
This class is a delegate to ILBFactory for a given database cluster.
Utility class for bot passwords.
const DB_REPLICA
Definition defines.php:26
const DB_PRIMARY
Definition defines.php:28