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
47 public const CONSTRUCTOR_OPTIONS = [
50 ];
51
53 private $options;
54
56 private $loadBalancerFactory;
57
59 private $loadBalancer;
60
62 private $userNameUtils;
63
65 private $lastUserFromIdentity = null;
66
72 public function __construct(
73 ServiceOptions $options,
74 ILBFactory $loadBalancerFactory,
75 UserNameUtils $userNameUtils
76 ) {
77 $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
78 $this->options = $options;
79 $this->loadBalancerFactory = $loadBalancerFactory;
80 $this->loadBalancer = $loadBalancerFactory->getMainLB();
81 $this->userNameUtils = $userNameUtils;
82 }
83
102 public function newFromName(
103 string $name,
104 string $validate = self::RIGOR_VALID
105 ): ?User {
106 // RIGOR_* constants are the same here and in the UserNameUtils class
107 $canonicalName = $this->userNameUtils->getCanonical( $name, $validate );
108 if ( $canonicalName === false ) {
109 return null;
110 }
111
112 $user = new User();
113 $user->mName = $canonicalName;
114 $user->mFrom = 'name';
115 $user->setItemLoaded( 'name' );
116 return $user;
117 }
118
127 public function newAnonymous( ?string $ip = null ): User {
128 if ( $ip ) {
129 if ( !$this->userNameUtils->isIP( $ip ) ) {
130 throw new InvalidArgumentException( 'Invalid IP address' );
131 }
132 $user = new User();
133 $user->setName( $ip );
134 } else {
135 $user = new User();
136 }
137 return $user;
138 }
139
148 public function newFromId( int $id ): User {
149 $user = new User();
150 $user->mId = $id;
151 $user->mFrom = 'id';
152 $user->setItemLoaded( 'id' );
153 return $user;
154 }
155
164 public function newFromActorId( int $actorId ): User {
165 $user = new User();
166 $user->mActorId = $actorId;
167 $user->mFrom = 'actor';
168 $user->setItemLoaded( 'actor' );
169 return $user;
170 }
171
180 public function newFromUserIdentity( UserIdentity $userIdentity ): User {
181 if ( $userIdentity instanceof User ) {
182 return $userIdentity;
183 }
184
185 $id = $userIdentity->getId();
186 $name = $userIdentity->getName();
187 // Cache the $userIdentity we converted last. This avoids redundant conversion
188 // in cases where we would be converting the same UserIdentity over and over,
189 // for instance because we need to access data preferences when formatting
190 // timestamps in a listing.
191 if (
192 $this->lastUserFromIdentity
193 && $this->lastUserFromIdentity->getId() === $id
194 && $this->lastUserFromIdentity->getName() === $name
195 ) {
196 return $this->lastUserFromIdentity;
197 }
198
199 $this->lastUserFromIdentity = $this->newFromAnyId(
200 $id === 0 ? null : $id,
201 $name === '' ? null : $name,
202 null
203 );
204
205 return $this->lastUserFromIdentity;
206 }
207
223 public function newFromAnyId(
224 ?int $userId,
225 ?string $userName,
226 ?int $actorId = null,
227 $dbDomain = false
228 ): User {
229 // Stop-gap solution for the problem described in T222212.
230 // Force the User ID and Actor ID to zero for users loaded from the database
231 // of another wiki, to prevent subtle data corruption and confusing failure modes.
232 if ( $dbDomain !== false ) {
233 $userId = 0;
234 $actorId = 0;
235 }
236
237 $user = new User;
238 $user->mFrom = 'defaults';
239
240 if ( $actorId !== null ) {
241 $user->mActorId = $actorId;
242 if ( $actorId !== 0 ) {
243 $user->mFrom = 'actor';
244 }
245 $user->setItemLoaded( 'actor' );
246 }
247
248 if ( $userName !== null && $userName !== '' ) {
249 $user->mName = $userName;
250 $user->mFrom = 'name';
251 $user->setItemLoaded( 'name' );
252 }
253
254 if ( $userId !== null ) {
255 $user->mId = $userId;
256 if ( $userId !== 0 ) {
257 $user->mFrom = 'id';
258 }
259 $user->setItemLoaded( 'id' );
260 }
261
262 if ( $user->mFrom === 'defaults' ) {
263 throw new InvalidArgumentException(
264 'Cannot create a user with no name, no ID, and no actor ID'
265 );
266 }
267
268 return $user;
269 }
270
283 public function newFromConfirmationCode(
284 string $confirmationCode,
285 int $flags = IDBAccessObject::READ_NORMAL
286 ) {
287 if ( ( $flags & IDBAccessObject::READ_LATEST ) == IDBAccessObject::READ_LATEST ) {
288 $db = $this->loadBalancer->getConnectionRef( DB_PRIMARY );
289 } else {
290 $db = $this->loadBalancer->getConnectionRef( DB_REPLICA );
291 }
292
293 $id = $db->newSelectQueryBuilder()
294 ->select( 'user_id' )
295 ->from( 'user' )
296 ->where( [ 'user_email_token' => md5( $confirmationCode ) ] )
297 ->andWhere( $db->expr( 'user_email_token_expires', '>', $db->timestamp() ) )
298 ->recency( $flags )
299 ->caller( __METHOD__ )->fetchField();
300
301 if ( !$id ) {
302 return null;
303 }
304
305 return $this->newFromId( (int)$id );
306 }
307
317 public function newFromRow( $row, $data = null ) {
318 return User::newFromRow( $row, $data );
319 }
320
326 public function newFromAuthority( Authority $authority ): User {
327 if ( $authority instanceof User ) {
328 return $authority;
329 }
330 return $this->newFromUserIdentity( $authority->getUser() );
331 }
332
341 public function newTempPlaceholder() {
342 $user = new User();
343 $user->setName( $this->userNameUtils->getTempPlaceholder() );
344 return $user;
345 }
346
354 public function newUnsavedTempUser( ?string $name ) {
355 $user = new User();
356 $user->setName( $name ?? $this->userNameUtils->getTempPlaceholder() );
357 return $user;
358 }
359
365 public function invalidateCache( UserIdentity $userIdentity ) {
366 if ( !$userIdentity->isRegistered() ) {
367 return;
368 }
369
370 $wikiId = $userIdentity->getWikiId();
371 if ( $wikiId === UserIdentity::LOCAL ) {
372 $legacyUser = $this->newFromUserIdentity( $userIdentity );
373 // Update user_touched within User class to manage state of User::mTouched for CAS check
374 $legacyUser->invalidateCache();
375 } else {
376 // cross-wiki invalidation
377 $userId = $userIdentity->getId( $wikiId );
378
379 $dbw = $this->getUserTableConnection( ILoadBalancer::DB_PRIMARY, $wikiId );
380 $dbw->newUpdateQueryBuilder()
381 ->update( 'user' )
382 ->set( [ 'user_touched' => $dbw->timestamp() ] )
383 ->where( [ 'user_id' => $userId ] )
384 ->caller( __METHOD__ )->execute();
385
386 $dbw->onTransactionPreCommitOrIdle(
387 static function () use ( $wikiId, $userId ) {
388 User::purge( $wikiId, $userId );
389 },
390 __METHOD__
391 );
392 }
393 }
394
400 private function getUserTableConnection( $mode, $wikiId ) {
401 if ( is_string( $wikiId ) && $this->loadBalancerFactory->getLocalDomainID() === $wikiId ) {
402 $wikiId = UserIdentity::LOCAL;
403 }
404
405 if ( $this->options->get( MainConfigNames::SharedDB ) &&
406 in_array( 'user', $this->options->get( MainConfigNames::SharedTables ) )
407 ) {
408 // The main LB is aliased for the shared database in Setup.php
409 $lb = $this->loadBalancer;
410 } else {
411 $lb = $this->loadBalancerFactory->getMainLB( $wikiId );
412 }
413
414 return $lb->getConnection( $mode, [], $wikiId );
415 }
416}
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.
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