MediaWiki master
ActorMigrationBase.php
Go to the documentation of this file.
1<?php
7namespace MediaWiki\User;
8
9use InvalidArgumentException;
10use LogicException;
11use ReflectionClass;
12use Wikimedia\IPUtils;
15
24 private $joinCache = [];
25
27 private readonly int $readStage;
28
30 private readonly int $writeStage;
31
32 private readonly bool $allowUnknown;
33
34 private bool $forImport = false;
35
61 public function __construct(
62 private readonly array $fieldInfos,
63 $stage,
64 protected readonly ActorStoreFactory $actorStoreFactory,
65 $options = [],
66 ) {
67 $this->allowUnknown = $options['allowUnknown'] ?? true;
68
69 $writeStage = $stage & SCHEMA_COMPAT_WRITE_MASK;
70 $readStage = $stage & SCHEMA_COMPAT_READ_MASK;
71 if ( $writeStage === 0 ) {
72 throw new InvalidArgumentException( '$stage must include a write mode' );
73 }
74 if ( $readStage === 0 ) {
75 throw new InvalidArgumentException( '$stage must include a read mode' );
76 }
77 if ( !in_array( $readStage, [ SCHEMA_COMPAT_READ_OLD, SCHEMA_COMPAT_READ_NEW ] ) ) {
78 throw new InvalidArgumentException( 'Cannot read multiple schemas' );
79 }
80 if ( $readStage === SCHEMA_COMPAT_READ_OLD && !( $writeStage & SCHEMA_COMPAT_WRITE_OLD ) ) {
81 throw new InvalidArgumentException( 'Cannot read the old schema without also writing it' );
82 }
83 if ( $readStage === SCHEMA_COMPAT_READ_NEW && !( $writeStage & SCHEMA_COMPAT_WRITE_NEW ) ) {
84 throw new InvalidArgumentException( 'Cannot read the new schema without also writing it' );
85 }
86 $this->readStage = $readStage;
87 $this->writeStage = $writeStage;
88 }
89
94 public static function newMigrationForImport() {
95 throw new LogicException( __METHOD__ . " must be overridden" );
96 }
97
106 protected function getFieldInfo( $key ) {
107 if ( isset( $this->fieldInfos[$key] ) ) {
108 return $this->fieldInfos[$key];
109 } elseif ( $this->allowUnknown ) {
110 return [];
111 } else {
112 throw new InvalidArgumentException( $this->getInstanceName() . ": unknown key $key" );
113 }
114 }
115
124 protected function getInstanceName() {
125 if ( ( new ReflectionClass( $this ) )->isAnonymous() ) {
126 // Mostly for PHPUnit
127 return self::class;
128 } else {
129 return static::class;
130 }
131 }
132
140 protected function checkDeprecation( $key ) {
141 $fieldInfo = $this->getFieldInfo( $key );
142 if ( isset( $fieldInfo['removedVersion'] ) ) {
143 $removedVersion = $fieldInfo['removedVersion'];
144 $component = $fieldInfo['component'] ?? 'MediaWiki';
145 throw new InvalidArgumentException(
146 "Use of {$this->getInstanceName()} for '$key' was removed in $component $removedVersion"
147 );
148 }
149 if ( isset( $fieldInfo['deprecatedVersion'] ) ) {
150 $deprecatedVersion = $fieldInfo['deprecatedVersion'];
151 $component = $fieldInfo['component'] ?? 'MediaWiki';
152 wfDeprecated( "{$this->getInstanceName()} for '$key'", $deprecatedVersion, $component, 3 );
153 }
154 }
155
161 public function isAnon( $field ) {
162 return ( $this->readStage & SCHEMA_COMPAT_READ_NEW ) ? "$field IS NULL" : "$field = 0";
163 }
164
170 public function isNotAnon( $field ) {
171 return ( $this->readStage & SCHEMA_COMPAT_READ_NEW ) ? "$field IS NOT NULL" : "$field != 0";
172 }
173
179 private function getFieldNames( $key ) {
180 $fieldInfo = $this->getFieldInfo( $key );
181 $textField = $fieldInfo['textField'] ?? $key . '_text';
182 $actorField = $fieldInfo['actorField'] ?? substr( $key, 0, -5 ) . '_actor';
183 return [ $textField, $actorField ];
184 }
185
198 public function getJoin( $key ) {
199 $this->checkDeprecation( $key );
200
201 if ( !isset( $this->joinCache[$key] ) ) {
202 $tables = [];
203 $fields = [];
204 $joins = [];
205
206 [ $text, $actor ] = $this->getFieldNames( $key );
207
208 if ( $this->readStage === SCHEMA_COMPAT_READ_OLD ) {
209 $fields[$key] = $key;
210 $fields[$text] = $text;
211 $fields[$actor] = 'NULL';
212 } else /* SCHEMA_COMPAT_READ_NEW */ {
213 $alias = "actor_$key";
214 $tables[$alias] = 'actor';
215 $joins[$alias] = [ 'JOIN', "{$alias}.actor_id = {$actor}" ];
216
217 $fields[$key] = "{$alias}.actor_user";
218 $fields[$text] = "{$alias}.actor_name";
219 $fields[$actor] = $actor;
220 }
221
222 $this->joinCache[$key] = [
223 'tables' => $tables,
224 'fields' => $fields,
225 'joins' => $joins,
226 ];
227 }
228
229 return $this->joinCache[$key];
230 }
231
241 public function getInsertValues( IDatabase $dbw, $key, UserIdentity $user ) {
242 $this->checkDeprecation( $key );
243
244 [ $text, $actor ] = $this->getFieldNames( $key );
245 $ret = [];
246 if ( $this->writeStage & SCHEMA_COMPAT_WRITE_OLD ) {
247 $ret[$key] = $user->getId();
248 $ret[$text] = $user->getName();
249 }
250 if ( $this->writeStage & SCHEMA_COMPAT_WRITE_NEW ) {
251 $ret[$actor] = $this->getActorNormalization( $dbw->getDomainID() )
252 ->acquireActorId( $user, $dbw );
253 }
254 return $ret;
255 }
256
281 public function getWhere( IReadableDatabase $db, $key, $users, $useId = true ) {
282 $this->checkDeprecation( $key );
283
284 $tables = [];
285 $conds = [];
286 $joins = [];
287
288 if ( $users instanceof UserIdentity ) {
289 $users = [ $users ];
290 } elseif ( $users === null || $users === false ) {
291 // DWIM
292 $users = [];
293 } elseif ( !is_array( $users ) ) {
294 $what = get_debug_type( $users );
295 throw new InvalidArgumentException(
296 __METHOD__ . ": Value for \$users must be a UserIdentity or array, got $what"
297 );
298 }
299
300 // Get information about all the passed users
301 $ids = [];
302 $names = [];
303 $actors = [];
304 foreach ( $users as $user ) {
305 if ( $useId && $user->isRegistered() ) {
306 $ids[] = $user->getId();
307 } else {
308 // make sure to use normalized form of IP for anonymous users
309 $names[] = IPUtils::sanitizeIP( $user->getName() );
310 }
311 $actorId = $this->getActorNormalization( $db->getDomainID() )
312 ->findActorId( $user, $db );
313
314 if ( $actorId ) {
315 $actors[] = $actorId;
316 }
317 }
318
319 [ $text, $actor ] = $this->getFieldNames( $key );
320
321 // Combine data into conditions to be ORed together
322 if ( $this->readStage === SCHEMA_COMPAT_READ_NEW ) {
323 if ( $actors ) {
324 $conds['newactor'] = $db->makeList( [ $actor => $actors ], IDatabase::LIST_AND );
325 }
326 } else {
327 if ( $ids ) {
328 $conds['userid'] = $db->makeList( [ $key => $ids ], IDatabase::LIST_AND );
329 }
330 if ( $names ) {
331 $conds['username'] = $db->makeList( [ $text => $names ], IDatabase::LIST_AND );
332 }
333 }
334
335 return [
336 'tables' => $tables,
337 'conds' => $conds ? $db->makeList( array_values( $conds ), IDatabase::LIST_OR ) : '1=0',
338 'orconds' => $conds,
339 'joins' => $joins,
340 ];
341 }
342
347 public function setForImport( bool $forImport ): void {
348 $this->forImport = $forImport;
349 }
350
355 protected function getActorNormalization( $domainId ): ActorNormalization {
356 if ( $this->forImport ) {
357 return $this->actorStoreFactory->getActorNormalizationForImport( $domainId );
358 } else {
359 return $this->actorStoreFactory->getActorNormalization( $domainId );
360 }
361 }
362}
const SCHEMA_COMPAT_READ_NEW
Definition Defines.php:298
const SCHEMA_COMPAT_WRITE_OLD
Definition Defines.php:293
const SCHEMA_COMPAT_READ_OLD
Definition Defines.php:294
const SCHEMA_COMPAT_WRITE_NEW
Definition Defines.php:297
const SCHEMA_COMPAT_WRITE_MASK
Definition Defines.php:299
const SCHEMA_COMPAT_READ_MASK
Definition Defines.php:300
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:69
Help migrate core and extension code with the actor table migration.
getJoin( $key)
Get SELECT fields and joins for the actor key.
getInstanceName()
Get a name for this instance to use in error messages.
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.
__construct(private readonly array $fieldInfos, $stage, protected readonly ActorStoreFactory $actorStoreFactory, $options=[],)
checkDeprecation( $key)
Issue deprecation warning/error as appropriate.
ActorStore factory for any wiki domain.
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:31
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.