MediaWiki master
ActorMigrationBase.php
Go to the documentation of this file.
1<?php
21namespace MediaWiki\User;
22
23use InvalidArgumentException;
24use LogicException;
25use ReflectionClass;
26use Wikimedia\IPUtils;
29
38 private $joinCache = [];
39
41 private $readStage;
42
44 private $writeStage;
45
47
49 private $fieldInfos;
50
51 private bool $allowUnknown;
52
53 private bool $forImport = false;
54
80 public function __construct(
81 $fieldInfos,
82 $stage,
84 $options = []
85 ) {
86 $this->fieldInfos = $fieldInfos;
87 $this->allowUnknown = $options['allowUnknown'] ?? true;
88
89 $writeStage = $stage & SCHEMA_COMPAT_WRITE_MASK;
90 $readStage = $stage & SCHEMA_COMPAT_READ_MASK;
91 if ( $writeStage === 0 ) {
92 throw new InvalidArgumentException( '$stage must include a write mode' );
93 }
94 if ( $readStage === 0 ) {
95 throw new InvalidArgumentException( '$stage must include a read mode' );
96 }
97 if ( !in_array( $readStage, [ SCHEMA_COMPAT_READ_OLD, SCHEMA_COMPAT_READ_NEW ] ) ) {
98 throw new InvalidArgumentException( 'Cannot read multiple schemas' );
99 }
100 if ( $readStage === SCHEMA_COMPAT_READ_OLD && !( $writeStage & SCHEMA_COMPAT_WRITE_OLD ) ) {
101 throw new InvalidArgumentException( 'Cannot read the old schema without also writing it' );
102 }
103 if ( $readStage === SCHEMA_COMPAT_READ_NEW && !( $writeStage & SCHEMA_COMPAT_WRITE_NEW ) ) {
104 throw new InvalidArgumentException( 'Cannot read the new schema without also writing it' );
105 }
106 $this->readStage = $readStage;
107 $this->writeStage = $writeStage;
108
109 $this->actorStoreFactory = $actorStoreFactory;
110 }
111
116 public static function newMigrationForImport() {
117 throw new LogicException( __METHOD__ . " must be overridden" );
118 }
119
128 protected function getFieldInfo( $key ) {
129 if ( isset( $this->fieldInfos[$key] ) ) {
130 return $this->fieldInfos[$key];
131 } elseif ( $this->allowUnknown ) {
132 return [];
133 } else {
134 throw new InvalidArgumentException( $this->getInstanceName() . ": unknown key $key" );
135 }
136 }
137
146 protected function getInstanceName() {
147 if ( ( new ReflectionClass( $this ) )->isAnonymous() ) {
148 // Mostly for PHPUnit
149 return self::class;
150 } else {
151 return static::class;
152 }
153 }
154
162 protected function checkDeprecation( $key ) {
163 $fieldInfo = $this->getFieldInfo( $key );
164 if ( isset( $fieldInfo['removedVersion'] ) ) {
165 $removedVersion = $fieldInfo['removedVersion'];
166 $component = $fieldInfo['component'] ?? 'MediaWiki';
167 throw new InvalidArgumentException(
168 "Use of {$this->getInstanceName()} for '$key' was removed in $component $removedVersion"
169 );
170 }
171 if ( isset( $fieldInfo['deprecatedVersion'] ) ) {
172 $deprecatedVersion = $fieldInfo['deprecatedVersion'];
173 $component = $fieldInfo['component'] ?? 'MediaWiki';
174 wfDeprecated( "{$this->getInstanceName()} for '$key'", $deprecatedVersion, $component, 3 );
175 }
176 }
177
183 public function isAnon( $field ) {
184 return ( $this->readStage & SCHEMA_COMPAT_READ_NEW ) ? "$field IS NULL" : "$field = 0";
185 }
186
192 public function isNotAnon( $field ) {
193 return ( $this->readStage & SCHEMA_COMPAT_READ_NEW ) ? "$field IS NOT NULL" : "$field != 0";
194 }
195
201 private function getFieldNames( $key ) {
202 $fieldInfo = $this->getFieldInfo( $key );
203 $textField = $fieldInfo['textField'] ?? $key . '_text';
204 $actorField = $fieldInfo['actorField'] ?? substr( $key, 0, -5 ) . '_actor';
205 return [ $textField, $actorField ];
206 }
207
220 public function getJoin( $key ) {
221 $this->checkDeprecation( $key );
222
223 if ( !isset( $this->joinCache[$key] ) ) {
224 $tables = [];
225 $fields = [];
226 $joins = [];
227
228 [ $text, $actor ] = $this->getFieldNames( $key );
229
230 if ( $this->readStage === SCHEMA_COMPAT_READ_OLD ) {
231 $fields[$key] = $key;
232 $fields[$text] = $text;
233 $fields[$actor] = 'NULL';
234 } else /* SCHEMA_COMPAT_READ_NEW */ {
235 $alias = "actor_$key";
236 $tables[$alias] = 'actor';
237 $joins[$alias] = [ 'JOIN', "{$alias}.actor_id = {$actor}" ];
238
239 $fields[$key] = "{$alias}.actor_user";
240 $fields[$text] = "{$alias}.actor_name";
241 $fields[$actor] = $actor;
242 }
243
244 $this->joinCache[$key] = [
245 'tables' => $tables,
246 'fields' => $fields,
247 'joins' => $joins,
248 ];
249 }
250
251 return $this->joinCache[$key];
252 }
253
263 public function getInsertValues( IDatabase $dbw, $key, UserIdentity $user ) {
264 $this->checkDeprecation( $key );
265
266 [ $text, $actor ] = $this->getFieldNames( $key );
267 $ret = [];
268 if ( $this->writeStage & SCHEMA_COMPAT_WRITE_OLD ) {
269 $ret[$key] = $user->getId();
270 $ret[$text] = $user->getName();
271 }
272 if ( $this->writeStage & SCHEMA_COMPAT_WRITE_NEW ) {
273 $ret[$actor] = $this->getActorNormalization( $dbw->getDomainID() )
274 ->acquireActorId( $user, $dbw );
275 }
276 return $ret;
277 }
278
303 public function getWhere( IReadableDatabase $db, $key, $users, $useId = true ) {
304 $this->checkDeprecation( $key );
305
306 $tables = [];
307 $conds = [];
308 $joins = [];
309
310 if ( $users instanceof UserIdentity ) {
311 $users = [ $users ];
312 } elseif ( $users === null || $users === false ) {
313 // DWIM
314 $users = [];
315 } elseif ( !is_array( $users ) ) {
316 $what = get_debug_type( $users );
317 throw new InvalidArgumentException(
318 __METHOD__ . ": Value for \$users must be a UserIdentity or array, got $what"
319 );
320 }
321
322 // Get information about all the passed users
323 $ids = [];
324 $names = [];
325 $actors = [];
326 foreach ( $users as $user ) {
327 if ( $useId && $user->isRegistered() ) {
328 $ids[] = $user->getId();
329 } else {
330 // make sure to use normalized form of IP for anonymous users
331 $names[] = IPUtils::sanitizeIP( $user->getName() );
332 }
333 $actorId = $this->getActorNormalization( $db->getDomainID() )
334 ->findActorId( $user, $db );
335
336 if ( $actorId ) {
337 $actors[] = $actorId;
338 }
339 }
340
341 [ $text, $actor ] = $this->getFieldNames( $key );
342
343 // Combine data into conditions to be ORed together
344 if ( $this->readStage === SCHEMA_COMPAT_READ_NEW ) {
345 if ( $actors ) {
346 $conds['newactor'] = $db->makeList( [ $actor => $actors ], IDatabase::LIST_AND );
347 }
348 } else {
349 if ( $ids ) {
350 $conds['userid'] = $db->makeList( [ $key => $ids ], IDatabase::LIST_AND );
351 }
352 if ( $names ) {
353 $conds['username'] = $db->makeList( [ $text => $names ], IDatabase::LIST_AND );
354 }
355 }
356
357 return [
358 'tables' => $tables,
359 'conds' => $conds ? $db->makeList( array_values( $conds ), IDatabase::LIST_OR ) : '1=0',
360 'orconds' => $conds,
361 'joins' => $joins,
362 ];
363 }
364
369 public function setForImport( bool $forImport ): void {
370 $this->forImport = $forImport;
371 }
372
377 protected function getActorNormalization( $domainId ): ActorNormalization {
378 if ( $this->forImport ) {
379 return $this->actorStoreFactory->getActorNormalizationForImport( $domainId );
380 } else {
381 return $this->actorStoreFactory->getActorNormalization( $domainId );
382 }
383 }
384}
const SCHEMA_COMPAT_READ_NEW
Definition Defines.php:308
const SCHEMA_COMPAT_WRITE_OLD
Definition Defines.php:303
const SCHEMA_COMPAT_READ_OLD
Definition Defines.php:304
const SCHEMA_COMPAT_WRITE_NEW
Definition Defines.php:307
const SCHEMA_COMPAT_WRITE_MASK
Definition Defines.php:309
const SCHEMA_COMPAT_READ_MASK
Definition Defines.php:310
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
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:45
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.