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 $readStage;
28
30 private $writeStage;
31
33
35 private $fieldInfos;
36
37 private bool $allowUnknown;
38
39 private bool $forImport = false;
40
66 public function __construct(
67 $fieldInfos,
68 $stage,
70 $options = []
71 ) {
72 $this->fieldInfos = $fieldInfos;
73 $this->allowUnknown = $options['allowUnknown'] ?? true;
74
75 $writeStage = $stage & SCHEMA_COMPAT_WRITE_MASK;
76 $readStage = $stage & SCHEMA_COMPAT_READ_MASK;
77 if ( $writeStage === 0 ) {
78 throw new InvalidArgumentException( '$stage must include a write mode' );
79 }
80 if ( $readStage === 0 ) {
81 throw new InvalidArgumentException( '$stage must include a read mode' );
82 }
83 if ( !in_array( $readStage, [ SCHEMA_COMPAT_READ_OLD, SCHEMA_COMPAT_READ_NEW ] ) ) {
84 throw new InvalidArgumentException( 'Cannot read multiple schemas' );
85 }
86 if ( $readStage === SCHEMA_COMPAT_READ_OLD && !( $writeStage & SCHEMA_COMPAT_WRITE_OLD ) ) {
87 throw new InvalidArgumentException( 'Cannot read the old schema without also writing it' );
88 }
89 if ( $readStage === SCHEMA_COMPAT_READ_NEW && !( $writeStage & SCHEMA_COMPAT_WRITE_NEW ) ) {
90 throw new InvalidArgumentException( 'Cannot read the new schema without also writing it' );
91 }
92 $this->readStage = $readStage;
93 $this->writeStage = $writeStage;
94
95 $this->actorStoreFactory = $actorStoreFactory;
96 }
97
102 public static function newMigrationForImport() {
103 throw new LogicException( __METHOD__ . " must be overridden" );
104 }
105
114 protected function getFieldInfo( $key ) {
115 if ( isset( $this->fieldInfos[$key] ) ) {
116 return $this->fieldInfos[$key];
117 } elseif ( $this->allowUnknown ) {
118 return [];
119 } else {
120 throw new InvalidArgumentException( $this->getInstanceName() . ": unknown key $key" );
121 }
122 }
123
132 protected function getInstanceName() {
133 if ( ( new ReflectionClass( $this ) )->isAnonymous() ) {
134 // Mostly for PHPUnit
135 return self::class;
136 } else {
137 return static::class;
138 }
139 }
140
148 protected function checkDeprecation( $key ) {
149 $fieldInfo = $this->getFieldInfo( $key );
150 if ( isset( $fieldInfo['removedVersion'] ) ) {
151 $removedVersion = $fieldInfo['removedVersion'];
152 $component = $fieldInfo['component'] ?? 'MediaWiki';
153 throw new InvalidArgumentException(
154 "Use of {$this->getInstanceName()} for '$key' was removed in $component $removedVersion"
155 );
156 }
157 if ( isset( $fieldInfo['deprecatedVersion'] ) ) {
158 $deprecatedVersion = $fieldInfo['deprecatedVersion'];
159 $component = $fieldInfo['component'] ?? 'MediaWiki';
160 wfDeprecated( "{$this->getInstanceName()} for '$key'", $deprecatedVersion, $component, 3 );
161 }
162 }
163
169 public function isAnon( $field ) {
170 return ( $this->readStage & SCHEMA_COMPAT_READ_NEW ) ? "$field IS NULL" : "$field = 0";
171 }
172
178 public function isNotAnon( $field ) {
179 return ( $this->readStage & SCHEMA_COMPAT_READ_NEW ) ? "$field IS NOT NULL" : "$field != 0";
180 }
181
187 private function getFieldNames( $key ) {
188 $fieldInfo = $this->getFieldInfo( $key );
189 $textField = $fieldInfo['textField'] ?? $key . '_text';
190 $actorField = $fieldInfo['actorField'] ?? substr( $key, 0, -5 ) . '_actor';
191 return [ $textField, $actorField ];
192 }
193
206 public function getJoin( $key ) {
207 $this->checkDeprecation( $key );
208
209 if ( !isset( $this->joinCache[$key] ) ) {
210 $tables = [];
211 $fields = [];
212 $joins = [];
213
214 [ $text, $actor ] = $this->getFieldNames( $key );
215
216 if ( $this->readStage === SCHEMA_COMPAT_READ_OLD ) {
217 $fields[$key] = $key;
218 $fields[$text] = $text;
219 $fields[$actor] = 'NULL';
220 } else /* SCHEMA_COMPAT_READ_NEW */ {
221 $alias = "actor_$key";
222 $tables[$alias] = 'actor';
223 $joins[$alias] = [ 'JOIN', "{$alias}.actor_id = {$actor}" ];
224
225 $fields[$key] = "{$alias}.actor_user";
226 $fields[$text] = "{$alias}.actor_name";
227 $fields[$actor] = $actor;
228 }
229
230 $this->joinCache[$key] = [
231 'tables' => $tables,
232 'fields' => $fields,
233 'joins' => $joins,
234 ];
235 }
236
237 return $this->joinCache[$key];
238 }
239
249 public function getInsertValues( IDatabase $dbw, $key, UserIdentity $user ) {
250 $this->checkDeprecation( $key );
251
252 [ $text, $actor ] = $this->getFieldNames( $key );
253 $ret = [];
254 if ( $this->writeStage & SCHEMA_COMPAT_WRITE_OLD ) {
255 $ret[$key] = $user->getId();
256 $ret[$text] = $user->getName();
257 }
258 if ( $this->writeStage & SCHEMA_COMPAT_WRITE_NEW ) {
259 $ret[$actor] = $this->getActorNormalization( $dbw->getDomainID() )
260 ->acquireActorId( $user, $dbw );
261 }
262 return $ret;
263 }
264
289 public function getWhere( IReadableDatabase $db, $key, $users, $useId = true ) {
290 $this->checkDeprecation( $key );
291
292 $tables = [];
293 $conds = [];
294 $joins = [];
295
296 if ( $users instanceof UserIdentity ) {
297 $users = [ $users ];
298 } elseif ( $users === null || $users === false ) {
299 // DWIM
300 $users = [];
301 } elseif ( !is_array( $users ) ) {
302 $what = get_debug_type( $users );
303 throw new InvalidArgumentException(
304 __METHOD__ . ": Value for \$users must be a UserIdentity or array, got $what"
305 );
306 }
307
308 // Get information about all the passed users
309 $ids = [];
310 $names = [];
311 $actors = [];
312 foreach ( $users as $user ) {
313 if ( $useId && $user->isRegistered() ) {
314 $ids[] = $user->getId();
315 } else {
316 // make sure to use normalized form of IP for anonymous users
317 $names[] = IPUtils::sanitizeIP( $user->getName() );
318 }
319 $actorId = $this->getActorNormalization( $db->getDomainID() )
320 ->findActorId( $user, $db );
321
322 if ( $actorId ) {
323 $actors[] = $actorId;
324 }
325 }
326
327 [ $text, $actor ] = $this->getFieldNames( $key );
328
329 // Combine data into conditions to be ORed together
330 if ( $this->readStage === SCHEMA_COMPAT_READ_NEW ) {
331 if ( $actors ) {
332 $conds['newactor'] = $db->makeList( [ $actor => $actors ], IDatabase::LIST_AND );
333 }
334 } else {
335 if ( $ids ) {
336 $conds['userid'] = $db->makeList( [ $key => $ids ], IDatabase::LIST_AND );
337 }
338 if ( $names ) {
339 $conds['username'] = $db->makeList( [ $text => $names ], IDatabase::LIST_AND );
340 }
341 }
342
343 return [
344 'tables' => $tables,
345 'conds' => $conds ? $db->makeList( array_values( $conds ), IDatabase::LIST_OR ) : '1=0',
346 'orconds' => $conds,
347 'joins' => $joins,
348 ];
349 }
350
355 public function setForImport( bool $forImport ): void {
356 $this->forImport = $forImport;
357 }
358
363 protected function getActorNormalization( $domainId ): ActorNormalization {
364 if ( $this->forImport ) {
365 return $this->actorStoreFactory->getActorNormalizationForImport( $domainId );
366 } else {
367 return $this->actorStoreFactory->getActorNormalization( $domainId );
368 }
369 }
370}
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:68
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.
__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.
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.