MediaWiki REL1_35
ActorMigration.php
Go to the documentation of this file.
1<?php
26
40
46
55 private static $tempTables = [
56 'rev_user' => [
57 'table' => 'revision_actor_temp',
58 'pk' => 'revactor_rev',
59 'field' => 'revactor_actor',
60 'joinPK' => 'rev_id',
61 'extra' => [
62 'revactor_timestamp' => 'rev_timestamp',
63 'revactor_page' => 'rev_page',
64 ],
65 ],
66 ];
67
73 private static $formerTempTables = [];
74
80 private static $deprecated = [
81 'ar_user' => null, // 1.34
82 'img_user' => null, // 1.34
83 'oi_user' => null, // 1.34
84 'fa_user' => null, // 1.34
85 'rc_user' => null, // 1.34
86 'log_user' => null, // 1.34
87 'ipb_by' => null, // 1.34
88 ];
89
95 private static $removed = [];
96
102 private static $specialFields = [
103 'ipb_by' => [ 'ipb_by_text', 'ipb_by_actor' ],
104 ];
105
107 private $joinCache = [];
108
110 private $stage;
111
116 public function __construct( $stage ) {
117 if ( ( $stage & SCHEMA_COMPAT_WRITE_BOTH ) === 0 ) {
118 throw new InvalidArgumentException( '$stage must include a write mode' );
119 }
120 if ( ( $stage & SCHEMA_COMPAT_READ_BOTH ) === 0 ) {
121 throw new InvalidArgumentException( '$stage must include a read mode' );
122 }
124 throw new InvalidArgumentException( 'Cannot read both schemas' );
125 }
127 throw new InvalidArgumentException( 'Cannot read the old schema without also writing it' );
128 }
130 throw new InvalidArgumentException( 'Cannot read the new schema without also writing it' );
131 }
132
133 $this->stage = $stage;
134 }
135
140 public static function newMigration() {
141 return MediaWikiServices::getInstance()->getActorMigration();
142 }
143
148 private static function checkDeprecation( $key ) {
149 if ( isset( self::$removed[$key] ) ) {
150 throw new InvalidArgumentException(
151 "Use of " . static::class . " for '$key' was removed in MediaWiki " . self::$removed[$key]
152 );
153 }
154 if ( !empty( self::$deprecated[$key] ) ) {
155 wfDeprecated( static::class . " for '$key'", self::$deprecated[$key], false, 3 );
156 }
157 }
158
164 public function isAnon( $field ) {
165 return ( $this->stage & SCHEMA_COMPAT_READ_NEW ) ? "$field IS NULL" : "$field = 0";
166 }
167
173 public function isNotAnon( $field ) {
174 return ( $this->stage & SCHEMA_COMPAT_READ_NEW ) ? "$field IS NOT NULL" : "$field != 0";
175 }
176
182 private static function getFieldNames( $key ) {
183 return self::$specialFields[$key] ?? [ $key . '_text', substr( $key, 0, -5 ) . '_actor' ];
184 }
185
198 public function getJoin( $key ) {
199 self::checkDeprecation( $key );
200
201 if ( !isset( $this->joinCache[$key] ) ) {
202 $tables = [];
203 $fields = [];
204 $joins = [];
205
206 list( $text, $actor ) = self::getFieldNames( $key );
207
208 if ( $this->stage & SCHEMA_COMPAT_READ_OLD ) {
209 $fields[$key] = $key;
210 $fields[$text] = $text;
211 $fields[$actor] = 'NULL';
212 } else {
213 if ( isset( self::$tempTables[$key] ) ) {
214 $t = self::$tempTables[$key];
215 $alias = "temp_$key";
216 $tables[$alias] = $t['table'];
217 $joins[$alias] = [ 'JOIN', "{$alias}.{$t['pk']} = {$t['joinPK']}" ];
218 $joinField = "{$alias}.{$t['field']}";
219 } else {
220 $joinField = $actor;
221 }
222
223 $alias = "actor_$key";
224 $tables[$alias] = 'actor';
225 $joins[$alias] = [ 'JOIN', "{$alias}.actor_id = {$joinField}" ];
226
227 $fields[$key] = "{$alias}.actor_user";
228 $fields[$text] = "{$alias}.actor_name";
229 $fields[$actor] = $joinField;
230 }
231
232 $this->joinCache[$key] = [
233 'tables' => $tables,
234 'fields' => $fields,
235 'joins' => $joins,
236 ];
237 }
238
239 return $this->joinCache[$key];
240 }
241
251 public function getExistingActorId( IDatabase $db, UserIdentity $user ) {
252 $row = $db->selectRow(
253 'actor',
254 [ 'actor_id' ],
255 [ 'actor_name' => $user->getName() ],
256 __METHOD__
257 );
258 if ( $row === false ) {
259 return false;
260 }
261
262 return (int)$row->actor_id;
263 }
264
276 public function getNewActorId( IDatabase $dbw, UserIdentity $user ) {
277 // TODO: inject
278 $userNameUtils = MediaWikiServices::getInstance()->getUserNameUtils();
279
280 $q = [
281 'actor_user' => $user->getId() ?: null,
282 'actor_name' => (string)$user->getName(),
283 ];
284 if ( $q['actor_user'] === null && $userNameUtils->isUsable( $q['actor_name'] ) ) {
286 'Cannot create an actor for a usable name that is not an existing user: ' .
287 "user_id={$user->getId()} user_name=\"{$user->getName()}\""
288 );
289 }
290 if ( $q['actor_name'] === '' ) {
292 'Cannot create an actor for a user with no name: ' .
293 "user_id={$user->getId()} user_name=\"{$user->getName()}\""
294 );
295 }
296
297 $dbw->insert( 'actor', $q, __METHOD__, [ 'IGNORE' ] );
298
299 if ( $dbw->affectedRows() ) {
300 $actorId = (int)$dbw->insertId();
301 } else {
302 // Outdated cache?
303 // Use LOCK IN SHARE MODE to bypass any MySQL REPEATABLE-READ snapshot.
304 $actorId = (int)$dbw->selectField(
305 'actor',
306 'actor_id',
307 $q,
308 __METHOD__,
309 [ 'LOCK IN SHARE MODE' ]
310 );
311 if ( !$actorId ) {
313 "Failed to create actor ID for " .
314 "user_id={$user->getId()} user_name=\"{$user->getName()}\""
315 );
316 }
317 }
318
319 return $actorId;
320 }
321
331 public function getInsertValues( IDatabase $dbw, $key, UserIdentity $user ) {
332 self::checkDeprecation( $key );
333
334 if ( isset( self::$tempTables[$key] ) ) {
335 throw new InvalidArgumentException( "Must use getInsertValuesWithTempTable() for $key" );
336 }
337
338 list( $text, $actor ) = self::getFieldNames( $key );
339 $ret = [];
340 if ( $this->stage & SCHEMA_COMPAT_WRITE_OLD ) {
341 $ret[$key] = $user->getId();
342 $ret[$text] = $user->getName();
343 }
344 if ( $this->stage & SCHEMA_COMPAT_WRITE_NEW ) {
345 // NOTE: Don't use $user->getActorId(), since that may be for the wrong wiki (T260485)
346 // TODO: Make User object wiki-aware and let it handle all cases (T260933)
347 $existingActorId = $this->getExistingActorId( $dbw, $user );
348 if ( $existingActorId !== false ) {
349 $ret[$actor] = $existingActorId;
350 } else {
351 $ret[$actor] = $this->getNewActorId( $dbw, $user );
352 }
353 }
354 return $ret;
355 }
356
369 public function getInsertValuesWithTempTable( IDatabase $dbw, $key, UserIdentity $user ) {
370 self::checkDeprecation( $key );
371
372 if ( isset( self::$formerTempTables[$key] ) ) {
373 wfDeprecated( __METHOD__ . " for $key", self::$formerTempTables[$key] );
374 } elseif ( !isset( self::$tempTables[$key] ) ) {
375 throw new InvalidArgumentException( "Must use getInsertValues() for $key" );
376 }
377
378 list( $text, $actor ) = self::getFieldNames( $key );
379 $ret = [];
380 $callback = null;
381 if ( $this->stage & SCHEMA_COMPAT_WRITE_OLD ) {
382 $ret[$key] = $user->getId();
383 $ret[$text] = $user->getName();
384 }
385 if ( $this->stage & SCHEMA_COMPAT_WRITE_NEW ) {
386 // We need to be able to assign an actor ID if none exists
387 if ( !$user instanceof User && !$user->getActorId() ) {
388 $user = User::newFromAnyId( $user->getId(), $user->getName(), null );
389 }
390 $id = $user->getActorId( $dbw );
391
392 if ( isset( self::$tempTables[$key] ) ) {
393 $func = __METHOD__;
394 $callback = function ( $pk, array $extra ) use ( $dbw, $key, $id, $func ) {
395 $t = self::$tempTables[$key];
396 $set = [ $t['field'] => $id ];
397 foreach ( $t['extra'] as $to => $from ) {
398 if ( !array_key_exists( $from, $extra ) ) {
399 throw new InvalidArgumentException( "$func callback: \$extra[$from] is not provided" );
400 }
401 $set[$to] = $extra[$from];
402 }
403 $dbw->upsert(
404 $t['table'],
405 [ $t['pk'] => $pk ] + $set,
406 [ [ $t['pk'] ] ],
407 $set,
408 $func
409 );
410 };
411 } else {
412 $ret[$actor] = $id;
413 $callback = function ( $pk, array $extra ) {
414 };
415 }
416 } elseif ( isset( self::$tempTables[$key] ) ) {
417 $func = __METHOD__;
418 $callback = function ( $pk, array $extra ) use ( $key, $func ) {
419 $t = self::$tempTables[$key];
420 foreach ( $t['extra'] as $to => $from ) {
421 if ( !array_key_exists( $from, $extra ) ) {
422 throw new InvalidArgumentException( "$func callback: \$extra[$from] is not provided" );
423 }
424 }
425 };
426 } else {
427 $callback = function ( $pk, array $extra ) {
428 };
429 }
430 return [ $ret, $callback ];
431 }
432
456 public function getWhere( IDatabase $db, $key, $users, $useId = true ) {
457 self::checkDeprecation( $key );
458
459 $tables = [];
460 $conds = [];
461 $joins = [];
462
463 if ( $users instanceof UserIdentity ) {
464 $users = [ $users ];
465 } elseif ( $users === null || $users === false ) {
466 // DWIM
467 $users = [];
468 } elseif ( !is_array( $users ) ) {
469 $what = is_object( $users ) ? get_class( $users ) : gettype( $users );
470 throw new InvalidArgumentException(
471 __METHOD__ . ": Value for \$users must be a UserIdentity or array, got $what"
472 );
473 }
474
475 // Get information about all the passed users
476 $ids = [];
477 $names = [];
478 $actors = [];
479 foreach ( $users as $user ) {
480 if ( $useId && $user->getId() ) {
481 $ids[] = $user->getId();
482 } else {
483 $names[] = $user->getName();
484 }
485 $actorId = $user->getActorId();
486 if ( $actorId ) {
487 $actors[] = $actorId;
488 }
489 }
490
491 list( $text, $actor ) = self::getFieldNames( $key );
492
493 // Combine data into conditions to be ORed together
494 if ( $this->stage & SCHEMA_COMPAT_READ_NEW ) {
495 if ( $actors ) {
496 if ( isset( self::$tempTables[$key] ) ) {
497 $t = self::$tempTables[$key];
498 $alias = "temp_$key";
499 $tables[$alias] = $t['table'];
500 $joins[$alias] = [ 'JOIN', "{$alias}.{$t['pk']} = {$t['joinPK']}" ];
501 $joinField = "{$alias}.{$t['field']}";
502 } else {
503 $joinField = $actor;
504 }
505 $conds['actor'] = $db->makeList( [ $joinField => $actors ], IDatabase::LIST_AND );
506 }
507 } else {
508 if ( $ids ) {
509 $conds['userid'] = $db->makeList( [ $key => $ids ], IDatabase::LIST_AND );
510 }
511 if ( $names ) {
512 $conds['username'] = $db->makeList( [ $text => $names ], IDatabase::LIST_AND );
513 }
514 }
515
516 return [
517 'tables' => $tables,
518 'conds' => $conds ? $db->makeList( array_values( $conds ), IDatabase::LIST_OR ) : '1=0',
519 'orconds' => $conds,
520 'joins' => $joins,
521 ];
522 }
523
524}
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that $function is deprecated.
This class handles the logic for the actor table migration and should always be used in lieu of direc...
const MIGRATION_STAGE_SCHEMA_COMPAT
Constant for extensions to feature-test whether $wgActorTableSchemaMigrationStage (in MW <1....
static array $formerTempTables
Fields that formerly used $tempTables Key is '$key', value is the MediaWiki version in which it was r...
isAnon( $field)
Return an SQL condition to test if a user field is anonymous.
getInsertValues(IDatabase $dbw, $key, UserIdentity $user)
Get UPDATE fields for the actor.
static array $tempTables
Define fields that use temporary tables for transitional purposes Keys are '$key',...
static array $specialFields
Define fields that use non-standard mapping Keys are the user id column name, values are arrays with ...
array $joinCache
Cache for self::getJoin()
static newMigration()
Static constructor.
static checkDeprecation( $key)
Issue deprecation warning/error as appropriate.
getInsertValuesWithTempTable(IDatabase $dbw, $key, UserIdentity $user)
Get UPDATE fields for the actor.
getNewActorId(IDatabase $dbw, UserIdentity $user)
Attempt to assign an actor ID to the given user.
int $stage
Combination of SCHEMA_COMPAT_* constants.
isNotAnon( $field)
Return an SQL condition to test if a user field is non-anonymous.
getExistingActorId(IDatabase $db, UserIdentity $user)
Get actor ID from UserIdentity, if it exists.
static string[] $removed
Define fields that are removed for use with this class.
getWhere(IDatabase $db, $key, $users, $useId=true)
Get WHERE condition for the actor.
static string null[] $deprecated
Define fields that are deprecated for use with this class.
getJoin( $key)
Get SELECT fields and joins for the actor key.
static getFieldNames( $key)
Exception thrown when an actor can't be created.
MediaWikiServices is the service locator for the application scope of MediaWiki.
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:60
static newFromAnyId( $userId, $userName, $actorId, $dbDomain=false)
Static factory method for creation from an ID, name, and/or actor ID.
Definition User.php:616
const SCHEMA_COMPAT_WRITE_BOTH
Definition Defines.php:278
const SCHEMA_COMPAT_READ_NEW
Definition Defines.php:277
const SCHEMA_COMPAT_READ_BOTH
Definition Defines.php:279
const SCHEMA_COMPAT_WRITE_OLD
Definition Defines.php:274
const SCHEMA_COMPAT_READ_OLD
Definition Defines.php:275
const SCHEMA_COMPAT_WRITE_NEW
Definition Defines.php:276
Interface for objects representing user identity.
Basic database interface for live and lazy-loaded relation database handles.
Definition IDatabase.php:38
makeList(array $a, $mode=self::LIST_COMMA)
Makes an encoded list of strings from an array.
selectRow( $table, $vars, $conds, $fname=__METHOD__, $options=[], $join_conds=[])
Wrapper to IDatabase::select() that only fetches one row (via LIMIT)
affectedRows()
Get the number of rows affected by the last write query.
upsert( $table, array $rows, $uniqueKeys, array $set, $fname=__METHOD__)
Upsert the given row(s) into a table.
selectField( $table, $var, $cond='', $fname=__METHOD__, $options=[], $join_conds=[])
A SELECT wrapper which returns a single field from a single result row.
insert( $table, $rows, $fname=__METHOD__, $options=[])
Insert the given row(s) into a table.
insertId()
Get the inserted value of an auto-increment row.