MediaWiki master
ActorMigrationBase.php
Go to the documentation of this file.
1<?php
23namespace MediaWiki\User;
24
25use InvalidArgumentException;
26use LogicException;
27use ReflectionClass;
28use Wikimedia\IPUtils;
31
41 private $joinCache = [];
42
44 private $readStage;
45
47 private $writeStage;
48
50
52 private $fieldInfos;
53
54 private bool $allowUnknown;
55
56 private bool $forImport = false;
57
83 public function __construct(
84 $fieldInfos,
85 $stage,
87 $options = []
88 ) {
89 $this->fieldInfos = $fieldInfos;
90 $this->allowUnknown = $options['allowUnknown'] ?? true;
91
92 $writeStage = $stage & SCHEMA_COMPAT_WRITE_MASK;
93 $readStage = $stage & SCHEMA_COMPAT_READ_MASK;
94 if ( $writeStage === 0 ) {
95 throw new InvalidArgumentException( '$stage must include a write mode' );
96 }
97 if ( $readStage === 0 ) {
98 throw new InvalidArgumentException( '$stage must include a read mode' );
99 }
100 if ( !in_array( $readStage, [ SCHEMA_COMPAT_READ_OLD, SCHEMA_COMPAT_READ_NEW ] ) ) {
101 throw new InvalidArgumentException( 'Cannot read multiple schemas' );
102 }
103 if ( $readStage === SCHEMA_COMPAT_READ_OLD && !( $writeStage & SCHEMA_COMPAT_WRITE_OLD ) ) {
104 throw new InvalidArgumentException( 'Cannot read the old schema without also writing it' );
105 }
106 if ( $readStage === SCHEMA_COMPAT_READ_NEW && !( $writeStage & SCHEMA_COMPAT_WRITE_NEW ) ) {
107 throw new InvalidArgumentException( 'Cannot read the new schema without also writing it' );
108 }
109 $this->readStage = $readStage;
110 $this->writeStage = $writeStage;
111
112 $this->actorStoreFactory = $actorStoreFactory;
113 }
114
119 public static function newMigrationForImport() {
120 throw new LogicException( __METHOD__ . " must be overridden" );
121 }
122
131 protected function getFieldInfo( $key ) {
132 if ( isset( $this->fieldInfos[$key] ) ) {
133 return $this->fieldInfos[$key];
134 } elseif ( $this->allowUnknown ) {
135 return [];
136 } else {
137 throw new InvalidArgumentException( $this->getInstanceName() . ": unknown key $key" );
138 }
139 }
140
149 protected function getInstanceName() {
150 if ( ( new ReflectionClass( $this ) )->isAnonymous() ) {
151 // Mostly for PHPUnit
152 return self::class;
153 } else {
154 return static::class;
155 }
156 }
157
165 protected function checkDeprecation( $key ) {
166 $fieldInfo = $this->getFieldInfo( $key );
167 if ( isset( $fieldInfo['removedVersion'] ) ) {
168 $removedVersion = $fieldInfo['removedVersion'];
169 $component = $fieldInfo['component'] ?? 'MediaWiki';
170 throw new InvalidArgumentException(
171 "Use of {$this->getInstanceName()} for '$key' was removed in $component $removedVersion"
172 );
173 }
174 if ( isset( $fieldInfo['deprecatedVersion'] ) ) {
175 $deprecatedVersion = $fieldInfo['deprecatedVersion'];
176 $component = $fieldInfo['component'] ?? 'MediaWiki';
177 wfDeprecated( "{$this->getInstanceName()} for '$key'", $deprecatedVersion, $component, 3 );
178 }
179 }
180
186 public function isAnon( $field ) {
187 return ( $this->readStage & SCHEMA_COMPAT_READ_NEW ) ? "$field IS NULL" : "$field = 0";
188 }
189
195 public function isNotAnon( $field ) {
196 return ( $this->readStage & SCHEMA_COMPAT_READ_NEW ) ? "$field IS NOT NULL" : "$field != 0";
197 }
198
204 private function getFieldNames( $key ) {
205 $fieldInfo = $this->getFieldInfo( $key );
206 $textField = $fieldInfo['textField'] ?? $key . '_text';
207 $actorField = $fieldInfo['actorField'] ?? substr( $key, 0, -5 ) . '_actor';
208 return [ $textField, $actorField ];
209 }
210
223 public function getJoin( $key ) {
224 $this->checkDeprecation( $key );
225
226 if ( !isset( $this->joinCache[$key] ) ) {
227 $tables = [];
228 $fields = [];
229 $joins = [];
230
231 [ $text, $actor ] = $this->getFieldNames( $key );
232
233 if ( $this->readStage === SCHEMA_COMPAT_READ_OLD ) {
234 $fields[$key] = $key;
235 $fields[$text] = $text;
236 $fields[$actor] = 'NULL';
237 } else /* SCHEMA_COMPAT_READ_NEW */ {
238 $alias = "actor_$key";
239 $tables[$alias] = 'actor';
240 $joins[$alias] = [ 'JOIN', "{$alias}.actor_id = {$actor}" ];
241
242 $fields[$key] = "{$alias}.actor_user";
243 $fields[$text] = "{$alias}.actor_name";
244 $fields[$actor] = $actor;
245 }
246
247 $this->joinCache[$key] = [
248 'tables' => $tables,
249 'fields' => $fields,
250 'joins' => $joins,
251 ];
252 }
253
254 return $this->joinCache[$key];
255 }
256
266 public function getInsertValues( IDatabase $dbw, $key, UserIdentity $user ) {
267 $this->checkDeprecation( $key );
268
269 [ $text, $actor ] = $this->getFieldNames( $key );
270 $ret = [];
271 if ( $this->writeStage & SCHEMA_COMPAT_WRITE_OLD ) {
272 $ret[$key] = $user->getId();
273 $ret[$text] = $user->getName();
274 }
275 if ( $this->writeStage & SCHEMA_COMPAT_WRITE_NEW ) {
276 $ret[$actor] = $this->getActorNormalization( $dbw->getDomainID() )
277 ->acquireActorId( $user, $dbw );
278 }
279 return $ret;
280 }
281
306 public function getWhere( IReadableDatabase $db, $key, $users, $useId = true ) {
307 $this->checkDeprecation( $key );
308
309 $tables = [];
310 $conds = [];
311 $joins = [];
312
313 if ( $users instanceof UserIdentity ) {
314 $users = [ $users ];
315 } elseif ( $users === null || $users === false ) {
316 // DWIM
317 $users = [];
318 } elseif ( !is_array( $users ) ) {
319 $what = get_debug_type( $users );
320 throw new InvalidArgumentException(
321 __METHOD__ . ": Value for \$users must be a UserIdentity or array, got $what"
322 );
323 }
324
325 // Get information about all the passed users
326 $ids = [];
327 $names = [];
328 $actors = [];
329 foreach ( $users as $user ) {
330 if ( $useId && $user->isRegistered() ) {
331 $ids[] = $user->getId();
332 } else {
333 // make sure to use normalized form of IP for anonymous users
334 $names[] = IPUtils::sanitizeIP( $user->getName() );
335 }
336 $actorId = $this->getActorNormalization( $db->getDomainID() )
337 ->findActorId( $user, $db );
338
339 if ( $actorId ) {
340 $actors[] = $actorId;
341 }
342 }
343
344 [ $text, $actor ] = $this->getFieldNames( $key );
345
346 // Combine data into conditions to be ORed together
347 if ( $this->readStage === SCHEMA_COMPAT_READ_NEW ) {
348 if ( $actors ) {
349 $conds['newactor'] = $db->makeList( [ $actor => $actors ], IDatabase::LIST_AND );
350 }
351 } else {
352 if ( $ids ) {
353 $conds['userid'] = $db->makeList( [ $key => $ids ], IDatabase::LIST_AND );
354 }
355 if ( $names ) {
356 $conds['username'] = $db->makeList( [ $text => $names ], IDatabase::LIST_AND );
357 }
358 }
359
360 return [
361 'tables' => $tables,
362 'conds' => $conds ? $db->makeList( array_values( $conds ), IDatabase::LIST_OR ) : '1=0',
363 'orconds' => $conds,
364 'joins' => $joins,
365 ];
366 }
367
372 public function setForImport( bool $forImport ): void {
373 $this->forImport = $forImport;
374 }
375
380 protected function getActorNormalization( $domainId ): ActorNormalization {
381 if ( $this->forImport ) {
382 return $this->actorStoreFactory->getActorNormalizationForImport( $domainId );
383 } else {
384 return $this->actorStoreFactory->getActorNormalization( $domainId );
385 }
386 }
387}
388
390class_alias( ActorMigrationBase::class, 'ActorMigrationBase' );
const SCHEMA_COMPAT_READ_NEW
Definition Defines.php:287
const SCHEMA_COMPAT_WRITE_OLD
Definition Defines.php:282
const SCHEMA_COMPAT_READ_OLD
Definition Defines.php:283
const SCHEMA_COMPAT_WRITE_NEW
Definition Defines.php:286
const SCHEMA_COMPAT_WRITE_MASK
Definition Defines.php:288
const SCHEMA_COMPAT_READ_MASK
Definition Defines.php:289
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:81
This abstract base class helps migrate core and extension code to use the actor table.
getJoin( $key)
Get SELECT fields and joins for the actor key.
getInstanceName()
Get a name for this instance to use in error messages.
__construct( $fieldInfos, $stage, ActorStoreFactory $actorStoreFactory, $options=[])
getWhere(IReadableDatabase $db, $key, $users, $useId=true)
Get WHERE condition for the actor.
getFieldInfo( $key)
Get config information about a field.
isNotAnon( $field)
Return an SQL condition to test if a user field is non-anonymous.
getInsertValues(IDatabase $dbw, $key, UserIdentity $user)
Get UPDATE fields for the actor.
static newMigrationForImport()
Get an instance that allows IP actor creation.
isAnon( $field)
Return an SQL condition to test if a user field is anonymous.
checkDeprecation( $key)
Issue deprecation warning/error as appropriate.
Service for dealing with the actor table.
Interface for objects representing user identity.
getId( $wikiId=self::LOCAL)
Interface to a relational database.
Definition IDatabase.php:48
A database connection without write operations.
getDomainID()
Return the currently selected domain ID.
makeList(array $a, $mode=self::LIST_COMMA)
Makes an encoded list of strings from an array.
Utility class for bot passwords.