MediaWiki REL1_31
ActorMigration.php
Go to the documentation of this file.
1<?php
26
36
45 private static $tempTables = [
46 'rev_user' => [
47 'table' => 'revision_actor_temp',
48 'pk' => 'revactor_rev',
49 'field' => 'revactor_actor',
50 'joinPK' => 'rev_id',
51 'extra' => [
52 'revactor_timestamp' => 'rev_timestamp',
53 'revactor_page' => 'rev_page',
54 ],
55 ],
56 ];
57
63 private static $formerTempTables = [];
64
70 private static $specialFields = [
71 'ipb_by' => [ 'ipb_by_text', 'ipb_by_actor' ],
72 ];
73
75 private $joinCache = null;
76
78 private $stage;
79
81 public function __construct( $stage ) {
82 $this->stage = $stage;
83 }
84
89 public static function newMigration() {
90 return MediaWikiServices::getInstance()->getActorMigration();
91 }
92
98 public function isAnon( $field ) {
99 return $this->stage === MIGRATION_NEW ? "$field IS NULL" : "$field = 0";
100 }
101
107 public function isNotAnon( $field ) {
108 return $this->stage === MIGRATION_NEW ? "$field IS NOT NULL" : "$field != 0";
109 }
110
116 private static function getFieldNames( $key ) {
117 if ( isset( self::$specialFields[$key] ) ) {
118 return self::$specialFields[$key];
119 }
120
121 return [ $key . '_text', substr( $key, 0, -5 ) . '_actor' ];
122 }
123
135 public function getJoin( $key ) {
136 if ( !isset( $this->joinCache[$key] ) ) {
137 $tables = [];
138 $fields = [];
139 $joins = [];
140
141 list( $text, $actor ) = self::getFieldNames( $key );
142
143 if ( $this->stage === MIGRATION_OLD ) {
144 $fields[$key] = $key;
145 $fields[$text] = $text;
146 $fields[$actor] = 'NULL';
147 } else {
148 $join = $this->stage === MIGRATION_NEW ? 'JOIN' : 'LEFT JOIN';
149
150 if ( isset( self::$tempTables[$key] ) ) {
151 $t = self::$tempTables[$key];
152 $alias = "temp_$key";
153 $tables[$alias] = $t['table'];
154 $joins[$alias] = [ $join, "{$alias}.{$t['pk']} = {$t['joinPK']}" ];
155 $joinField = "{$alias}.{$t['field']}";
156 } else {
157 $joinField = $actor;
158 }
159
160 $alias = "actor_$key";
161 $tables[$alias] = 'actor';
162 $joins[$alias] = [ $join, "{$alias}.actor_id = {$joinField}" ];
163
164 if ( $this->stage === MIGRATION_NEW ) {
165 $fields[$key] = "{$alias}.actor_user";
166 $fields[$text] = "{$alias}.actor_name";
167 } else {
168 $fields[$key] = "COALESCE( {$alias}.actor_user, $key )";
169 $fields[$text] = "COALESCE( {$alias}.actor_name, $text )";
170 }
171 $fields[$actor] = $joinField;
172 }
173
174 $this->joinCache[$key] = [
175 'tables' => $tables,
176 'fields' => $fields,
177 'joins' => $joins,
178 ];
179 }
180
181 return $this->joinCache[$key];
182 }
183
193 public function getExistingActorId( IDatabase $db, UserIdentity $user ) {
194 $row = $db->selectRow(
195 'actor',
196 [ 'actor_id' ],
197 [ 'actor_name' => $user->getName() ],
198 __METHOD__
199 );
200 if ( $row === false ) {
201 return false;
202 }
203
204 return (int)$row->actor_id;
205 }
206
218 public function getNewActorId( $dbw, UserIdentity $user ) {
219 $q = [
220 'actor_user' => $user->getId() ?: null,
221 'actor_name' => (string)$user->getName(),
222 ];
223 if ( $dbw instanceof IDatabase ) {
224 if ( $q['actor_user'] === null && User::isUsableName( $q['actor_name'] ) ) {
226 'Cannot create an actor for a usable name that is not an existing user'
227 );
228 }
229 if ( $q['actor_name'] === '' ) {
230 throw new CannotCreateActorException( 'Cannot create an actor for a user with no name' );
231 }
232 $dbw->insert( 'actor', $q, __METHOD__, [ 'IGNORE' ] );
233 if ( $dbw->affectedRows() ) {
234 $actorId = (int)$dbw->insertId();
235 } else {
236 // Outdated cache?
237 // Use LOCK IN SHARE MODE to bypass any MySQL REPEATABLE-READ snapshot.
238 $actorId = (int)$dbw->selectField(
239 'actor',
240 'actor_id',
241 $q,
242 __METHOD__,
243 [ 'LOCK IN SHARE MODE' ]
244 );
245 if ( !$actorId ) {
247 "Cannot create actor ID for user_id={$user->getId()} user_name={$user->getName()}"
248 );
249 }
250 }
251 User::newFromName( (string)$user->getName(), false )->invalidateCache();
252 } else {
253 list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $dbw );
254 $db = wfGetDB( $index );
255 $actorId = (int)$db->selectField( 'actor', 'actor_id', $q, __METHOD__, $options );
256 }
257
258 return $actorId;
259 }
260
270 public function getInsertValues( IDatabase $dbw, $key, UserIdentity $user ) {
271 if ( isset( self::$tempTables[$key] ) ) {
272 throw new InvalidArgumentException( "Must use getInsertValuesWithTempTable() for $key" );
273 }
274
275 list( $text, $actor ) = self::getFieldNames( $key );
276 $ret = [];
277 if ( $this->stage <= MIGRATION_WRITE_BOTH ) {
278 $ret[$key] = $user->getId();
279 $ret[$text] = $user->getName();
280 }
281 if ( $this->stage >= MIGRATION_WRITE_BOTH ) {
282 // NOTE: Don't use $user->getActorId(), since that may be for the wrong wiki (T260485)
283 // TODO: Make User object wiki-aware and let it handle all cases (T260933)
284 $existingActorId = $this->getExistingActorId( $dbw, $user );
285 if ( $existingActorId !== false ) {
286 $ret[$actor] = $existingActorId;
287 } else {
288 $ret[$actor] = $this->getNewActorId( $dbw, $user );
289 }
290 }
291 return $ret;
292 }
293
306 public function getInsertValuesWithTempTable( IDatabase $dbw, $key, UserIdentity $user ) {
307 if ( isset( self::$formerTempTables[$key] ) ) {
308 wfDeprecated( __METHOD__ . " for $key", self::$formerTempTables[$key] );
309 } elseif ( !isset( self::$tempTables[$key] ) ) {
310 throw new InvalidArgumentException( "Must use getInsertValues() for $key" );
311 }
312
313 list( $text, $actor ) = self::getFieldNames( $key );
314 $ret = [];
315 $callback = null;
316 if ( $this->stage <= MIGRATION_WRITE_BOTH ) {
317 $ret[$key] = $user->getId();
318 $ret[$text] = $user->getName();
319 }
320 if ( $this->stage >= MIGRATION_WRITE_BOTH ) {
321 // We need to be able to assign an actor ID if none exists
322 if ( !$user instanceof User && !$user->getActorId() ) {
323 $user = User::newFromAnyId( $user->getId(), $user->getName(), null );
324 }
325 $id = $user->getActorId( $dbw );
326
327 if ( isset( self::$tempTables[$key] ) ) {
328 $func = __METHOD__;
329 $callback = function ( $pk, array $extra ) use ( $dbw, $key, $id, $func ) {
330 $t = self::$tempTables[$key];
331 $set = [ $t['field'] => $id ];
332 foreach ( $t['extra'] as $to => $from ) {
333 if ( !array_key_exists( $from, $extra ) ) {
334 throw new InvalidArgumentException( "$func callback: \$extra[$from] is not provided" );
335 }
336 $set[$to] = $extra[$from];
337 }
338 $dbw->upsert(
339 $t['table'],
340 [ $t['pk'] => $pk ] + $set,
341 [ $t['pk'] ],
342 $set,
343 $func
344 );
345 };
346 } else {
347 $ret[$actor] = $id;
348 $callback = function ( $pk, array $extra ) {
349 };
350 }
351 } elseif ( isset( self::$tempTables[$key] ) ) {
352 $func = __METHOD__;
353 $callback = function ( $pk, array $extra ) use ( $key, $func ) {
354 $t = self::$tempTables[$key];
355 foreach ( $t['extra'] as $to => $from ) {
356 if ( !array_key_exists( $from, $extra ) ) {
357 throw new InvalidArgumentException( "$func callback: \$extra[$from] is not provided" );
358 }
359 }
360 };
361 } else {
362 $callback = function ( $pk, array $extra ) {
363 };
364 }
365 return [ $ret, $callback ];
366 }
367
387 public function getWhere( IDatabase $db, $key, $users, $useId = true ) {
388 $tables = [];
389 $conds = [];
390 $joins = [];
391
392 if ( $users instanceof UserIdentity ) {
393 $users = [ $users ];
394 }
395
396 // Get information about all the passed users
397 $ids = [];
398 $names = [];
399 $actors = [];
400 foreach ( $users as $user ) {
401 if ( $useId && $user->getId() ) {
402 $ids[] = $user->getId();
403 } else {
404 $names[] = $user->getName();
405 }
406 $actorId = $user->getActorId();
407 if ( $actorId ) {
408 $actors[] = $actorId;
409 }
410 }
411
412 list( $text, $actor ) = self::getFieldNames( $key );
413
414 // Combine data into conditions to be ORed together
415 $actorNotEmpty = [];
416 if ( $this->stage === MIGRATION_OLD ) {
417 $actors = [];
418 $actorEmpty = [];
419 } elseif ( isset( self::$tempTables[$key] ) ) {
420 $t = self::$tempTables[$key];
421 $alias = "temp_$key";
422 $tables[$alias] = $t['table'];
423 $joins[$alias] = [
424 $this->stage === MIGRATION_NEW ? 'JOIN' : 'LEFT JOIN',
425 "{$alias}.{$t['pk']} = {$t['joinPK']}"
426 ];
427 $joinField = "{$alias}.{$t['field']}";
428 $actorEmpty = [ $joinField => null ];
429 if ( $this->stage !== MIGRATION_NEW ) {
430 // Otherwise the resulting test can evaluate to NULL, and
431 // NOT(NULL) is NULL rather than true.
432 $actorNotEmpty = [ "$joinField IS NOT NULL" ];
433 }
434 } else {
435 $joinField = $actor;
436 $actorEmpty = [ $joinField => 0 ];
437 }
438
439 if ( $actors ) {
440 $conds['actor'] = $db->makeList(
441 $actorNotEmpty + [ $joinField => $actors ], IDatabase::LIST_AND
442 );
443 }
444 if ( $this->stage < MIGRATION_NEW && $ids ) {
445 $conds['userid'] = $db->makeList(
446 $actorEmpty + [ $key => $ids ], IDatabase::LIST_AND
447 );
448 }
449 if ( $this->stage < MIGRATION_NEW && $names ) {
450 $conds['username'] = $db->makeList(
451 $actorEmpty + [ $text => $names ], IDatabase::LIST_AND
452 );
453 }
454
455 return [
456 'tables' => $tables,
457 'conds' => $conds ? $db->makeList( array_values( $conds ), IDatabase::LIST_OR ) : '1=0',
458 'orconds' => $conds,
459 'joins' => $joins,
460 ];
461 }
462
463}
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
This class handles the logic for the actor table migration.
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 ...
getNewActorId( $dbw, UserIdentity $user)
Attempt to assign an actor ID to the given user.
static newMigration()
Static constructor.
array null $joinCache
Cache for self::getJoin()
getInsertValuesWithTempTable(IDatabase $dbw, $key, UserIdentity $user)
Get UPDATE fields for the actor.
int $stage
One of the MIGRATION_* 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.
getWhere(IDatabase $db, $key, $users, $useId=true)
Get WHERE condition for the actor.
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:53
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition User.php:591
static newFromAnyId( $userId, $userName, $actorId)
Static factory method for creation from an ID, name, and/or actor ID.
Definition User.php:657
static isUsableName( $name)
Usernames which fail to pass this function will be blocked from user login and new account registrati...
Definition User.php:1018
getActorId(IDatabase $dbw=null)
Get the user's actor ID.
Definition User.php:2520
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition deferred.txt:11
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist & $tables
Definition hooks.txt:1015
This code would result in ircNotify being run twice when an article is and once for brion Hooks can return three possible true was required This is the default since MediaWiki *some string
Definition hooks.txt:181
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
Definition hooks.txt:2001
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses & $ret
Definition hooks.txt:2005
const MIGRATION_NEW
Definition Defines.php:305
const MIGRATION_WRITE_BOTH
Definition Defines.php:303
const MIGRATION_OLD
Definition Defines.php:302
Interface for objects representing user identity.
Basic database interface for live and lazy-loaded relation database handles.
Definition IDatabase.php:38
upsert( $table, array $rows, array $uniqueIndexes, array $set, $fname=__METHOD__)
INSERT ON DUPLICATE KEY UPDATE wrapper, upserts an array into a table.
selectRow( $table, $vars, $conds, $fname=__METHOD__, $options=[], $join_conds=[])
Single row SELECT wrapper.
makeList( $a, $mode=self::LIST_COMMA)
Makes an encoded list of strings from an array.