Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F2754028
migrateAccount.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
9 KB
Referenced Files
None
Subscribers
None
migrateAccount.php
View Options
<?php
namespace
MediaWiki\Extension\CentralAuth\Maintenance
;
$IP
=
getenv
(
'MW_INSTALL_PATH'
);
if
(
$IP
===
false
)
{
$IP
=
__DIR__
.
'/../../..'
;
}
require_once
"$IP/maintenance/Maintenance.php"
;
use
Exception
;
use
MediaWiki\Extension\CentralAuth\User\CentralAuthUser
;
use
MediaWiki\Maintenance\Maintenance
;
use
Wikimedia\Rdbms\IDBAccessObject
;
class
MigrateAccount
extends
Maintenance
{
/** @var float */
protected
$start
;
/** @var int */
protected
$partial
;
/** @var int */
protected
$migrated
;
/** @var int */
protected
$total
;
/** @var bool */
protected
$safe
;
/** @var bool */
protected
$autoMigrate
;
/** @var bool */
protected
$resetToken
;
/** @var bool */
protected
$suppressRC
;
public
function
__construct
()
{
parent
::
__construct
();
$this
->
requireExtension
(
'CentralAuth'
);
$this
->
addDescription
(
<<<
'TEXT'
Migrates
the
specified
usernames
to
a
global
account
if
email
matches
and
there
are
no
conflicts
.
Assumes
the
localuser
and
globaluser
tables
are
up
to
date
(
e
.
g
.
migratePass0
has
been
run
).
TEXT
);
$this
->
start
=
microtime
(
true
);
$this
->
partial
=
0
;
$this
->
migrated
=
0
;
$this
->
total
=
0
;
$this
->
safe
=
false
;
$this
->
autoMigrate
=
false
;
$this
->
resetToken
=
false
;
$this
->
suppressRC
=
false
;
$this
->
addOption
(
'auto'
,
'Extended migration: ALWAYS create a global account for the username where missing '
.
'and merge all the local accounts which match its email; the winner is picked using '
.
'CentralAuthUser::attemptAutoMigration defaults, or forced to "homewiki" where '
.
'specified by --userlist or --homewiki'
,
false
,
false
);
$this
->
addOption
(
'userlist'
,
'List of usernames to migrate in the format username
\t
homewiki, where
\t
homewiki is '
.
'optional and overrides the default winner if specified'
,
false
,
true
);
$this
->
addOption
(
'username'
,
'The username to migrate'
,
false
,
true
,
'u'
);
$this
->
addOption
(
'homewiki'
,
'The wiki to set as the homewiki. Can only be used with --username'
,
false
,
true
,
'h'
);
$this
->
addOption
(
'safe'
,
'Skip usernames used more than once across all wikis'
,
false
,
false
);
$this
->
addOption
(
'attachmissing'
,
'Attach matching local accounts to an existing global account'
,
false
,
false
);
$this
->
addOption
(
'attachbroken'
,
'Attach broken local accounts to the existing global account'
,
false
,
false
);
$this
->
addOption
(
'resettoken'
,
'Allows for the reset of auth tokens in certain circumstances'
,
false
,
false
);
$this
->
addOption
(
'suppressrc'
,
'Do not send entries to RC feed'
,
false
,
false
);
$this
->
setBatchSize
(
1000
);
}
public
function
execute
()
{
if
(
$this
->
getOption
(
'safe'
,
false
)
!==
false
)
{
$this
->
safe
=
true
;
}
if
(
$this
->
getOption
(
'auto'
,
false
)
!==
false
)
{
$this
->
autoMigrate
=
true
;
}
if
(
$this
->
getOption
(
'resettoken'
,
false
)
!==
false
)
{
$this
->
resetToken
=
true
;
}
if
(
$this
->
getOption
(
'suppressrc'
,
false
)
!==
false
)
{
$this
->
suppressRC
=
true
;
}
// Check to see if we are processing a single username
if
(
$this
->
getOption
(
'username'
,
false
)
!==
false
)
{
$username
=
$this
->
getOption
(
'username'
);
$homewiki
=
$this
->
getOption
(
'homewiki'
,
null
);
$this
->
migrate
(
$username
,
$homewiki
);
}
elseif
(
$this
->
getOption
(
'userlist'
,
false
)
!==
false
)
{
$list
=
$this
->
getOption
(
'userlist'
);
if
(
!
is_file
(
$list
)
)
{
$this
->
output
(
"ERROR - File not found: $list"
);
exit
(
1
);
}
$file
=
fopen
(
$list
,
'r'
);
if
(
$file
===
false
)
{
$this
->
output
(
"ERROR - Could not open file: $list"
);
exit
(
1
);
}
// phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition
while
(
strlen
(
$line
=
trim
(
fgets
(
$file
)
)
)
)
{
$values
=
explode
(
"
\t
"
,
$line
);
switch
(
count
(
$values
)
)
{
case
1
:
$this
->
migrate
(
$values
[
0
]
);
break
;
case
2
:
$this
->
migrate
(
$values
[
0
],
$values
[
1
]
);
break
;
default
:
$this
->
output
(
"ERROR: Invalid account specification: '$line'
\n
"
);
break
;
}
if
(
$this
->
total
%
$this
->
mBatchSize
==
0
)
{
$this
->
output
(
"Waiting for replicas to catch up ... "
);
$this
->
waitForReplication
();
$this
->
output
(
"done
\n
"
);
}
}
fclose
(
$file
);
}
else
{
$this
->
output
(
"ERROR - No username or list of usernames given
\n
"
);
exit
(
1
);
}
$this
->
migratePassOneReport
();
$this
->
output
(
"done.
\n
"
);
}
/**
* @param string $username
* @param string|null $homewiki
*/
private
function
migrate
(
$username
,
$homewiki
=
null
)
{
$this
->
total
++;
$this
->
output
(
"CentralAuth account migration for: "
.
$username
.
"
\n
"
);
$central
=
new
CentralAuthUser
(
$username
,
IDBAccessObject
::
READ_LATEST
);
try
{
$unattached
=
$central
->
queryUnattached
();
}
catch
(
Exception
$e
)
{
// This might happen due to localnames inconsistencies (T69350)
$this
->
output
(
"ERROR: Fetching unattached accounts for $username failed."
);
return
;
}
/**
* Migration with an existing global account
*/
if
(
$central
->
exists
()
)
{
$this
->
output
(
"INFO: A global account already exists for: $username
\n
"
);
if
(
$this
->
getOption
(
'attachmissing'
,
false
)
&&
$central
->
getEmailAuthenticationTimestamp
()
!==
null
)
{
foreach
(
$unattached
as
$wiki
=>
$local
)
{
if
(
$central
->
getEmail
()
===
$local
[
'email'
]
&&
$local
[
'emailAuthenticated'
]
!==
null
)
{
$this
->
output
(
"ATTACHING: $username@$wiki
\n
"
);
$central
->
attach
(
$wiki
,
'mail'
,
/** $sendToRC = */
!
$this
->
suppressRC
);
}
}
}
if
(
$this
->
getOption
(
'attachbroken'
,
false
)
)
{
// This option is for T63876 / T41996 where the account has
// an empty password and email set, and became unattached.
// Since there is no way an account can have an empty password manually
// it has to be due to a CentralAuth bug. So just attach it then.
// But just to be on the safe side, check that it also has 0 edits.
foreach
(
$unattached
as
$wiki
=>
$local
)
{
if
(
$local
[
'email'
]
===
''
&&
$local
[
'password'
]
===
''
&&
$local
[
'editCount'
]
===
'0'
)
{
$this
->
output
(
"ATTACHING: $username@$wiki
\n
"
);
// Ironically, the attachment is made due to lack of a password.
$central
->
attach
(
$wiki
,
'password'
,
/** $sendToRC = */
!
$this
->
suppressRC
);
}
}
}
}
else
{
/**
* Migration without an existing global account
*/
if
(
count
(
$unattached
)
==
0
)
{
$this
->
output
(
"ERROR: No local accounts found for: $username
\n
"
);
return
;
}
if
(
$this
->
safe
&&
count
(
$unattached
)
!==
1
)
{
$this
->
output
(
"ERROR: More than 1 local user account found for username: $username
\n
"
);
foreach
(
$unattached
as
$local
)
{
$this
->
output
(
"
\t
"
.
$central
->
getName
()
.
"@"
.
$local
[
'wiki'
]
.
"
\n
"
);
}
return
;
}
if
(
$homewiki
!==
null
)
{
if
(
!
array_key_exists
(
$homewiki
,
$unattached
)
)
{
$this
->
output
(
"ERROR: Unattached user not found for $username@$homewiki
\n
"
);
return
;
}
$this
->
output
(
"INFO: Setting homewiki for '$username' to $homewiki
\n
"
);
$central
->
mHomeWiki
=
$homewiki
;
}
// Check that all unattached (i.e. ALL) accounts have a confirmed email
// address and that the addresses are all the same. We are using this
// to match accounts to the same user, since we can't use the password.
$emailMatch
=
true
;
$email
=
null
;
foreach
(
$unattached
as
$local
)
{
if
(
$email
===
null
)
{
$email
=
$local
[
'email'
];
}
if
(
$local
[
'email'
]
===
$email
&&
$local
[
'emailAuthenticated'
]
!==
null
)
{
continue
;
}
$emailMatch
=
false
;
break
;
}
// All of the emails are the same and confirmed? Merge all the accounts.
// They aren't? Skip, or merge the winner if --auto was specified.
if
(
$emailMatch
)
{
$this
->
output
(
"Email addresses match and are confirmed for: $username
\n
"
);
$central
->
storeAndMigrate
(
[],
!
$this
->
suppressRC
);
}
else
{
if
(
isset
(
$central
->
mHomeWiki
)
||
$this
->
autoMigrate
)
{
$central
->
storeAndMigrate
(
[],
!
$this
->
suppressRC
);
}
else
{
$this
->
output
(
"ERROR: Auto migration is disabled and email addresses do "
.
"not match for: $username
\n
"
);
}
}
}
$unattachedAfter
=
$central
->
queryUnattached
();
if
(
count
(
$unattachedAfter
)
==
0
)
{
$this
->
migrated
++;
return
;
}
elseif
(
count
(
$unattachedAfter
)
>
0
&&
count
(
$unattachedAfter
)
<
count
(
$unattached
)
)
{
$this
->
partial
++;
$this
->
output
(
"INFO: Incomplete migration for '$username'
\n
"
);
}
if
(
$this
->
resetToken
)
{
$this
->
output
(
"INFO: Resetting CentralAuth auth token for '$username'
\n
"
);
$central
->
resetAuthToken
();
}
}
private
function
migratePassOneReport
()
{
$delta
=
microtime
(
true
)
-
$this
->
start
;
$this
->
output
(
sprintf
(
"%s processed %d usernames (%.1f/sec), %d (%.1f%%) fully migrated, %d (%.1f%%) "
.
"partially migrated
\n
"
,
wfTimestamp
(
TS_DB
),
$this
->
total
,
$this
->
total
/
$delta
,
$this
->
migrated
,
$this
->
total
>
0
?
(
$this
->
migrated
/
$this
->
total
*
100.0
)
:
0
,
$this
->
partial
,
$this
->
total
>
0
?
(
$this
->
partial
/
$this
->
total
*
100.0
)
:
0
)
);
}
}
$maintClass
=
MigrateAccount
::
class
;
require_once
RUN_MAINTENANCE_IF_MAIN
;
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Fri, Jul 3, 22:03 (7 h, 28 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
66/d4/34e5781bc5f89102e3f77c7670f7
Default Alt Text
migrateAccount.php (9 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment