MediaWiki  1.34.4
ActorMigration.php
Go to the documentation of this file.
1 <?php
26 
39 
45 
54  private static $tempTables = [
55  'rev_user' => [
56  'table' => 'revision_actor_temp',
57  'pk' => 'revactor_rev',
58  'field' => 'revactor_actor',
59  'joinPK' => 'rev_id',
60  'extra' => [
61  'revactor_timestamp' => 'rev_timestamp',
62  'revactor_page' => 'rev_page',
63  ],
64  ],
65  ];
66 
72  private static $formerTempTables = [];
73 
79  private static $deprecated = [
80  'ar_user' => null, // 1.34
81  'img_user' => null, // 1.34
82  'oi_user' => null, // 1.34
83  'fa_user' => null, // 1.34
84  'rc_user' => null, // 1.34
85  'log_user' => null, // 1.34
86  'ipb_by' => null, // 1.34
87  ];
88 
94  private static $removed = [];
95 
101  private static $specialFields = [
102  'ipb_by' => [ 'ipb_by_text', 'ipb_by_actor' ],
103  ];
104 
106  private $joinCache = null;
107 
109  private $stage;
110 
112  public function __construct( $stage ) {
113  if ( ( $stage & SCHEMA_COMPAT_WRITE_BOTH ) === 0 ) {
114  throw new InvalidArgumentException( '$stage must include a write mode' );
115  }
116  if ( ( $stage & SCHEMA_COMPAT_READ_BOTH ) === 0 ) {
117  throw new InvalidArgumentException( '$stage must include a read mode' );
118  }
120  throw new InvalidArgumentException( 'Cannot read both schemas' );
121  }
123  throw new InvalidArgumentException( 'Cannot read the old schema without also writing it' );
124  }
126  throw new InvalidArgumentException( 'Cannot read the new schema without also writing it' );
127  }
128 
129  $this->stage = $stage;
130  }
131 
136  public static function newMigration() {
137  return MediaWikiServices::getInstance()->getActorMigration();
138  }
139 
144  private static function checkDeprecation( $key ) {
145  if ( isset( self::$removed[$key] ) ) {
146  throw new InvalidArgumentException(
147  "Use of " . static::class . " for '$key' was removed in MediaWiki " . self::$removed[$key]
148  );
149  }
150  if ( !empty( self::$deprecated[$key] ) ) {
151  wfDeprecated( static::class . " for '$key'", self::$deprecated[$key], false, 3 );
152  }
153  }
154 
160  public function isAnon( $field ) {
161  return ( $this->stage & SCHEMA_COMPAT_READ_NEW ) ? "$field IS NULL" : "$field = 0";
162  }
163 
169  public function isNotAnon( $field ) {
170  return ( $this->stage & SCHEMA_COMPAT_READ_NEW ) ? "$field IS NOT NULL" : "$field != 0";
171  }
172 
178  private static function getFieldNames( $key ) {
179  return self::$specialFields[$key] ?? [ $key . '_text', substr( $key, 0, -5 ) . '_actor' ];
180  }
181 
194  public function getJoin( $key ) {
195  self::checkDeprecation( $key );
196 
197  if ( !isset( $this->joinCache[$key] ) ) {
198  $tables = [];
199  $fields = [];
200  $joins = [];
201 
202  list( $text, $actor ) = self::getFieldNames( $key );
203 
204  if ( $this->stage & SCHEMA_COMPAT_READ_OLD ) {
205  $fields[$key] = $key;
206  $fields[$text] = $text;
207  $fields[$actor] = 'NULL';
208  } else {
209  if ( isset( self::$tempTables[$key] ) ) {
210  $t = self::$tempTables[$key];
211  $alias = "temp_$key";
212  $tables[$alias] = $t['table'];
213  $joins[$alias] = [ 'JOIN', "{$alias}.{$t['pk']} = {$t['joinPK']}" ];
214  $joinField = "{$alias}.{$t['field']}";
215  } else {
216  $joinField = $actor;
217  }
218 
219  $alias = "actor_$key";
220  $tables[$alias] = 'actor';
221  $joins[$alias] = [ 'JOIN', "{$alias}.actor_id = {$joinField}" ];
222 
223  $fields[$key] = "{$alias}.actor_user";
224  $fields[$text] = "{$alias}.actor_name";
225  $fields[$actor] = $joinField;
226  }
227 
228  $this->joinCache[$key] = [
229  'tables' => $tables,
230  'fields' => $fields,
231  'joins' => $joins,
232  ];
233  }
234 
235  return $this->joinCache[$key];
236  }
237 
247  public function getExistingActorId( IDatabase $db, UserIdentity $user ) {
248  $row = $db->selectRow(
249  'actor',
250  [ 'actor_id' ],
251  [ 'actor_name' => $user->getName() ],
252  __METHOD__
253  );
254  if ( $row === false ) {
255  return false;
256  }
257 
258  return (int)$row->actor_id;
259  }
260 
272  public function getNewActorId( IDatabase $dbw, UserIdentity $user ) {
273  $q = [
274  'actor_user' => $user->getId() ?: null,
275  'actor_name' => (string)$user->getName(),
276  ];
277  if ( $q['actor_user'] === null && User::isUsableName( $q['actor_name'] ) ) {
278  throw new CannotCreateActorException(
279  'Cannot create an actor for a usable name that is not an existing user: ' .
280  "user_id={$user->getId()} user_name=\"{$user->getName()}\""
281  );
282  }
283  if ( $q['actor_name'] === '' ) {
284  throw new CannotCreateActorException(
285  'Cannot create an actor for a user with no name: ' .
286  "user_id={$user->getId()} user_name=\"{$user->getName()}\""
287  );
288  }
289 
290  $dbw->insert( 'actor', $q, __METHOD__, [ 'IGNORE' ] );
291 
292  if ( $dbw->affectedRows() ) {
293  $actorId = (int)$dbw->insertId();
294  } else {
295  // Outdated cache?
296  // Use LOCK IN SHARE MODE to bypass any MySQL REPEATABLE-READ snapshot.
297  $actorId = (int)$dbw->selectField(
298  'actor',
299  'actor_id',
300  $q,
301  __METHOD__,
302  [ 'LOCK IN SHARE MODE' ]
303  );
304  if ( !$actorId ) {
305  throw new CannotCreateActorException(
306  "Failed to create actor ID for " .
307  "user_id={$user->getId()} user_name=\"{$user->getName()}\""
308  );
309  }
310  }
311 
312  return $actorId;
313  }
314 
324  public function getInsertValues( IDatabase $dbw, $key, UserIdentity $user ) {
325  self::checkDeprecation( $key );
326 
327  if ( isset( self::$tempTables[$key] ) ) {
328  throw new InvalidArgumentException( "Must use getInsertValuesWithTempTable() for $key" );
329  }
330 
331  list( $text, $actor ) = self::getFieldNames( $key );
332  $ret = [];
333  if ( $this->stage & SCHEMA_COMPAT_WRITE_OLD ) {
334  $ret[$key] = $user->getId();
335  $ret[$text] = $user->getName();
336  }
337  if ( $this->stage & SCHEMA_COMPAT_WRITE_NEW ) {
338  // NOTE: Don't use $user->getActorId(), since that may be for the wrong wiki (T260485)
339  // TODO: Make User object wiki-aware and let it handle all cases (T260933)
340  $existingActorId = $this->getExistingActorId( $dbw, $user );
341  if ( $existingActorId !== false ) {
342  $ret[$actor] = $existingActorId;
343  } else {
344  $ret[$actor] = $this->getNewActorId( $dbw, $user );
345  }
346  }
347  return $ret;
348  }
349 
362  public function getInsertValuesWithTempTable( IDatabase $dbw, $key, UserIdentity $user ) {
363  self::checkDeprecation( $key );
364 
365  if ( isset( self::$formerTempTables[$key] ) ) {
366  wfDeprecated( __METHOD__ . " for $key", self::$formerTempTables[$key] );
367  } elseif ( !isset( self::$tempTables[$key] ) ) {
368  throw new InvalidArgumentException( "Must use getInsertValues() for $key" );
369  }
370 
371  list( $text, $actor ) = self::getFieldNames( $key );
372  $ret = [];
373  $callback = null;
374  if ( $this->stage & SCHEMA_COMPAT_WRITE_OLD ) {
375  $ret[$key] = $user->getId();
376  $ret[$text] = $user->getName();
377  }
378  if ( $this->stage & SCHEMA_COMPAT_WRITE_NEW ) {
379  // We need to be able to assign an actor ID if none exists
380  if ( !$user instanceof User && !$user->getActorId() ) {
381  $user = User::newFromAnyId( $user->getId(), $user->getName(), null );
382  }
383  $id = $user->getActorId( $dbw );
384 
385  if ( isset( self::$tempTables[$key] ) ) {
386  $func = __METHOD__;
387  $callback = function ( $pk, array $extra ) use ( $dbw, $key, $id, $func ) {
388  $t = self::$tempTables[$key];
389  $set = [ $t['field'] => $id ];
390  foreach ( $t['extra'] as $to => $from ) {
391  if ( !array_key_exists( $from, $extra ) ) {
392  throw new InvalidArgumentException( "$func callback: \$extra[$from] is not provided" );
393  }
394  $set[$to] = $extra[$from];
395  }
396  $dbw->upsert(
397  $t['table'],
398  [ $t['pk'] => $pk ] + $set,
399  [ $t['pk'] ],
400  $set,
401  $func
402  );
403  };
404  } else {
405  $ret[$actor] = $id;
406  $callback = function ( $pk, array $extra ) {
407  };
408  }
409  } elseif ( isset( self::$tempTables[$key] ) ) {
410  $func = __METHOD__;
411  $callback = function ( $pk, array $extra ) use ( $key, $func ) {
412  $t = self::$tempTables[$key];
413  foreach ( $t['extra'] as $to => $from ) {
414  if ( !array_key_exists( $from, $extra ) ) {
415  throw new InvalidArgumentException( "$func callback: \$extra[$from] is not provided" );
416  }
417  }
418  };
419  } else {
420  $callback = function ( $pk, array $extra ) {
421  };
422  }
423  return [ $ret, $callback ];
424  }
425 
447  public function getWhere( IDatabase $db, $key, $users, $useId = true ) {
448  self::checkDeprecation( $key );
449 
450  $tables = [];
451  $conds = [];
452  $joins = [];
453 
454  if ( $users instanceof UserIdentity ) {
455  $users = [ $users ];
456  }
457 
458  // Get information about all the passed users
459  $ids = [];
460  $names = [];
461  $actors = [];
462  foreach ( $users as $user ) {
463  if ( $useId && $user->getId() ) {
464  $ids[] = $user->getId();
465  } else {
466  $names[] = $user->getName();
467  }
468  $actorId = $user->getActorId();
469  if ( $actorId ) {
470  $actors[] = $actorId;
471  }
472  }
473 
474  list( $text, $actor ) = self::getFieldNames( $key );
475 
476  // Combine data into conditions to be ORed together
477  if ( $this->stage & SCHEMA_COMPAT_READ_NEW ) {
478  if ( $actors ) {
479  if ( isset( self::$tempTables[$key] ) ) {
480  $t = self::$tempTables[$key];
481  $alias = "temp_$key";
482  $tables[$alias] = $t['table'];
483  $joins[$alias] = [ 'JOIN', "{$alias}.{$t['pk']} = {$t['joinPK']}" ];
484  $joinField = "{$alias}.{$t['field']}";
485  } else {
486  $joinField = $actor;
487  }
488  $conds['actor'] = $db->makeList( [ $joinField => $actors ], IDatabase::LIST_AND );
489  }
490  } else {
491  if ( $ids ) {
492  $conds['userid'] = $db->makeList( [ $key => $ids ], IDatabase::LIST_AND );
493  }
494  if ( $names ) {
495  $conds['username'] = $db->makeList( [ $text => $names ], IDatabase::LIST_AND );
496  }
497  }
498 
499  return [
500  'tables' => $tables,
501  'conds' => $conds ? $db->makeList( array_values( $conds ), IDatabase::LIST_OR ) : '1=0',
502  'orconds' => $conds,
503  'joins' => $joins,
504  ];
505  }
506 
507 }
ActorMigration\MIGRATION_STAGE_SCHEMA_COMPAT
const MIGRATION_STAGE_SCHEMA_COMPAT
Constant for extensions to feature-test whether $wgActorTableSchemaMigrationStage (in MW <1....
Definition: ActorMigration.php:44
Wikimedia\Rdbms\IDatabase\upsert
upsert( $table, array $rows, $uniqueIndexes, array $set, $fname=__METHOD__)
INSERT ON DUPLICATE KEY UPDATE wrapper, upserts an array into a table.
Wikimedia\Rdbms\IDatabase\affectedRows
affectedRows()
Get the number of rows affected by the last write query.
SCHEMA_COMPAT_READ_NEW
const SCHEMA_COMPAT_READ_NEW
Definition: Defines.php:276
Wikimedia\Rdbms\IDatabase\makeList
makeList( $a, $mode=self::LIST_COMMA)
Makes an encoded list of strings from an array.
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:117
ActorMigration\$removed
static string[] $removed
Define fields that are removed for use with this class.
Definition: ActorMigration.php:94
ActorMigration\__construct
__construct( $stage)
Definition: ActorMigration.php:112
ActorMigration
This class handles the logic for the actor table migration.
Definition: ActorMigration.php:38
Wikimedia\Rdbms\IDatabase\selectField
selectField( $table, $var, $cond='', $fname=__METHOD__, $options=[], $join_conds=[])
A SELECT wrapper which returns a single field from a single result row.
MediaWiki\User\UserIdentity
Interface for objects representing user identity.
Definition: UserIdentity.php:32
Wikimedia\Rdbms\IDatabase\insert
insert( $table, $a, $fname=__METHOD__, $options=[])
INSERT wrapper, inserts an array into a table.
ActorMigration\newMigration
static newMigration()
Static constructor.
Definition: ActorMigration.php:136
MediaWiki\User\UserIdentity\getActorId
getActorId()
LIST_AND
const LIST_AND
Definition: Defines.php:48
Wikimedia\Rdbms\IDatabase
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:38
ActorMigration\isAnon
isAnon( $field)
Return an SQL condition to test if a user field is anonymous.
Definition: ActorMigration.php:160
ActorMigration\$joinCache
array null $joinCache
Cache for self::getJoin()
Definition: ActorMigration.php:106
ActorMigration\getFieldNames
static getFieldNames( $key)
Definition: ActorMigration.php:178
LIST_OR
const LIST_OR
Definition: Defines.php:51
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
Definition: GlobalFunctions.php:1044
ActorMigration\checkDeprecation
static checkDeprecation( $key)
Issue deprecation warning/error as appropriate.
Definition: ActorMigration.php:144
ActorMigration\$stage
int $stage
Combination of SCHEMA_COMPAT_* constants.
Definition: ActorMigration.php:109
$t
$t
Definition: make-normalization-table.php:143
MediaWiki\User\UserIdentity\getName
getName()
User\newFromAnyId
static newFromAnyId( $userId, $userName, $actorId, $dbDomain=false)
Static factory method for creation from an ID, name, and/or actor ID.
Definition: User.php:599
ActorMigration\getExistingActorId
getExistingActorId(IDatabase $db, UserIdentity $user)
Get actor ID from UserIdentity, if it exists.
Definition: ActorMigration.php:247
ActorMigration\isNotAnon
isNotAnon( $field)
Return an SQL condition to test if a user field is non-anonymous.
Definition: ActorMigration.php:169
Wikimedia\Rdbms\IDatabase\selectRow
selectRow( $table, $vars, $conds, $fname=__METHOD__, $options=[], $join_conds=[])
Wrapper to IDatabase::select() that only fetches one row (via LIMIT)
SCHEMA_COMPAT_WRITE_OLD
const SCHEMA_COMPAT_WRITE_OLD
Definition: Defines.php:273
CannotCreateActorException
Exception thrown when an actor can't be created.
Definition: CannotCreateActorException.php:28
ActorMigration\$specialFields
static array $specialFields
Define fields that use non-standard mapping Keys are the user id column name, values are arrays with ...
Definition: ActorMigration.php:101
SCHEMA_COMPAT_WRITE_NEW
const SCHEMA_COMPAT_WRITE_NEW
Definition: Defines.php:275
ActorMigration\getWhere
getWhere(IDatabase $db, $key, $users, $useId=true)
Get WHERE condition for the actor.
Definition: ActorMigration.php:447
ActorMigration\getJoin
getJoin( $key)
Get SELECT fields and joins for the actor key.
Definition: ActorMigration.php:194
ActorMigration\getInsertValuesWithTempTable
getInsertValuesWithTempTable(IDatabase $dbw, $key, UserIdentity $user)
Get UPDATE fields for the actor.
Definition: ActorMigration.php:362
MediaWiki\User\UserIdentity\getId
getId()
ActorMigration\$tempTables
static array $tempTables
Define fields that use temporary tables for transitional purposes Keys are '$key',...
Definition: ActorMigration.php:54
SCHEMA_COMPAT_WRITE_BOTH
const SCHEMA_COMPAT_WRITE_BOTH
Definition: Defines.php:277
Wikimedia\Rdbms\IDatabase\insertId
insertId()
Get the inserted value of an auto-increment row.
User\isUsableName
static isUsableName( $name)
Usernames which fail to pass this function will be blocked from user login and new account registrati...
Definition: User.php:1008
ActorMigration\$deprecated
static string null[] $deprecated
Define fields that are deprecated for use with this class.
Definition: ActorMigration.php:79
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:51
ActorMigration\getInsertValues
getInsertValues(IDatabase $dbw, $key, UserIdentity $user)
Get UPDATE fields for the actor.
Definition: ActorMigration.php:324
SCHEMA_COMPAT_READ_BOTH
const SCHEMA_COMPAT_READ_BOTH
Definition: Defines.php:278
SCHEMA_COMPAT_READ_OLD
const SCHEMA_COMPAT_READ_OLD
Definition: Defines.php:274
ActorMigration\getNewActorId
getNewActorId(IDatabase $dbw, UserIdentity $user)
Attempt to assign an actor ID to the given user.
Definition: ActorMigration.php:272
ActorMigration\$formerTempTables
static array $formerTempTables
Fields that formerly used $tempTables Key is '$key', value is the MediaWiki version in which it was r...
Definition: ActorMigration.php:72