Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F1432367
batchVanishUsers.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
12 KB
Referenced Files
None
Subscribers
None
batchVanishUsers.php
View Options
<?php
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
*/
namespace
MediaWiki\Extension\CentralAuth\Maintenance
;
$IP
=
getenv
(
'MW_INSTALL_PATH'
);
if
(
$IP
===
false
)
{
$IP
=
__DIR__
.
'/../../..'
;
}
require_once
"$IP/maintenance/Maintenance.php"
;
use
InvalidArgumentException
;
use
MailAddress
;
use
MediaWiki\Extension\CentralAuth\GlobalRename\GlobalRenameRequest
;
use
MediaWiki\Extension\CentralAuth\User\CentralAuthUser
;
use
MediaWiki\Maintenance\Maintenance
;
use
MediaWiki\User\UserIdentity
;
use
UserMailer
;
use
Wikimedia\Message\MessageSpecifier
;
class
BatchVanishUsers
extends
Maintenance
{
public
function
__construct
()
{
parent
::
__construct
();
$this
->
requireExtension
(
'CentralAuth'
);
$this
->
addDescription
(
'Vanish users that are in a CSV containing vanish requests.'
);
$this
->
addOption
(
'data'
,
'Path to the file containing the vanish request data.'
,
true
,
true
,
'd'
);
$this
->
addOption
(
'performer'
,
'Performer of the vanish action.'
,
true
,
true
,
'p'
);
$this
->
addOption
(
'output'
,
'Path for the generated report. (default: output.csv)'
,
false
,
true
,
'o'
);
$this
->
addOption
(
'dry-run'
,
'Don
\'
t actually vanish the users, just report what it would do.'
);
}
public
function
execute
():
void
{
$csvPath
=
$this
->
getOption
(
'data'
);
$performer
=
$this
->
getOption
(
'performer'
);
$isDryRun
=
$this
->
getOption
(
'dry-run'
);
$outputPath
=
$this
->
getOption
(
'output'
,
'output.csv'
);
$services
=
$this
->
getServiceContainer
();
if
(
!
$services
->
getCentralIdLookupFactory
()->
getNonLocalLookup
()
)
{
$this
->
fatalError
(
'This script cannot be run when CentralAuth is disabled.'
);
}
$performerUser
=
CentralAuthUser
::
getInstanceByName
(
$performer
);
if
(
$performerUser
->
getId
()
===
0
)
{
$this
->
fatalError
(
"Performer with username {$performer} cannot be found.
\n
"
);
}
// Fetching UserIdentity from performer because GlobalRenameFactory uses both CA and UI
$performerIdentity
=
$services
->
getUserIdentityLookup
()->
getUserIdentityByName
(
$performer
);
// This error should never happen, we already found the CentralAuth performer
if
(
!
$performerIdentity
||
!
$performerIdentity
->
isRegistered
()
)
{
$this
->
fatalError
(
"Performed with username {$performer} cannot be found in UserIdentityLookup.
\n
"
);
}
// Load and parse CSV containing vanish requests from file.
$handle
=
fopen
(
$csvPath
,
'r'
);
if
(
!
$handle
)
{
$this
->
fatalError
(
"Unable to open vanish request data at provided path: {$csvPath}"
);
}
$vanishRequests
=
$this
->
parseUserVanishRequests
(
$handle
);
fclose
(
$handle
);
$outputHandle
=
fopen
(
$outputPath
,
'w'
);
if
(
!
$outputHandle
)
{
$this
->
fatalError
(
"Unable to create output file: {$outputPath}"
);
}
if
(
!
fputcsv
(
$outputHandle
,
[
'ticketId'
,
'result'
]
)
)
{
$this
->
fatalError
(
"Unable to write to output file: {$outputPath}"
);
}
$vanishRequestCount
=
count
(
$vanishRequests
);
$successCount
=
0
;
$failureCount
=
0
;
// Iterate through all of the vanish requests and add them to the queue
// one-by-one if they're valid.
foreach
(
$vanishRequests
as
$index
=>
$request
)
{
$current
=
$index
+
1
;
$messagePrefix
=
$isDryRun
?
"({$current}/{$vanishRequestCount}) (DRY RUN) "
:
"({$current}/{$vanishRequestCount}) "
;
$this
->
output
(
"{$messagePrefix}Submitting vanish request for user {$request['username']}
\n
"
);
$requestResult
=
$this
->
requestUserVanish
(
$request
,
$performerUser
,
$performerIdentity
);
if
(
$requestResult
[
'success'
]
)
{
$successCount
++;
}
else
{
fputcsv
(
$outputHandle
,
[
$request
[
'ticketLink'
],
$requestResult
[
'message'
]
]
);
$failureCount
++;
}
}
fclose
(
$outputHandle
);
// Print success and failure counts.
$this
->
output
(
"
\n
Sucessfully submitted {$successCount} vanish requests.
\n
"
);
$this
->
output
(
"Failed to submit {$failureCount} vanish requests.
\n
"
);
$this
->
output
(
"Report produced - {$outputPath}
\n
"
);
}
/**
* Parse a CSV file containing vanish requests.
*
* @param resource $handle file stream of a CSV with vanish requests
* @return array an array of valid vanish requests
*/
private
function
parseUserVanishRequests
(
$handle
):
array
{
$vanishRequests
=
[];
// Skip CSV header.
$data
=
fgets
(
$handle
);
if
(
$data
===
false
)
{
return
$vanishRequests
;
}
do
{
$data
=
fgetcsv
(
$handle
,
4096
,
','
);
if
(
$data
!==
false
)
{
$vanishRequests
[]
=
[
'createdDate'
=>
$data
[
0
],
'ticketId'
=>
$data
[
1
],
'ticketStatus'
=>
$data
[
2
],
'requesterEmail'
=>
$data
[
3
],
'ticketLink'
=>
$data
[
4
],
'globalRenamersLink'
=>
$data
[
5
],
'usernameLink'
=>
$data
[
6
],
'username'
=>
$data
[
7
],
'tags'
=>
$data
[
8
],
'duplicateTickets'
=>
$data
[
9
],
];
}
}
while
(
$data
!==
false
);
return
$vanishRequests
;
}
/**
* Submit a user vanish using provided information in the request.
*
* @param array $request
* @param CentralAuthUser $performer
* @param UserIdentity $uiPerformer
* @return array with keys:
* - "success" (bool) if the vanish action was successful
* - "message" (string) detail of the operation
*/
private
function
requestUserVanish
(
array
$request
,
CentralAuthUser
$performer
,
UserIdentity
$uiPerformer
):
array
{
$isDryRun
=
$this
->
getOption
(
'dry-run'
,
false
);
try
{
$username
=
$request
[
'username'
];
$causer
=
CentralAuthUser
::
getInstanceByName
(
$username
);
}
catch
(
InvalidArgumentException
$ex
)
{
$errorMessage
=
"Skipping user {$username} as that username is invalid."
;
$this
->
output
(
$errorMessage
.
"
\n
"
);
return
[
"success"
=>
false
,
"message"
=>
"no-user"
];
}
if
(
!
$causer
->
exists
()
||
!
$causer
->
isAttached
()
)
{
$errorMessage
=
"Skipping user {$username} as there is no CentralAuth user with that username."
;
$this
->
output
(
$errorMessage
.
"
\n
"
);
return
[
"success"
=>
false
,
"message"
=>
"no-user"
];
}
// isBlocked() is an expensive operation
// It is needed here and below to evaluate if the request is eligible for auto-vanish
// Whatever change in this also impacts the condition for auto-vanish below
$causerIsBlocked
=
$causer
->
isBlocked
();
if
(
$causerIsBlocked
)
{
$errorMessage
=
"{$username} - has blocks."
;
$this
->
output
(
$errorMessage
.
"
\n
"
);
return
[
"success"
=>
false
,
"message"
=>
"blocked"
];
}
$services
=
$this
->
getServiceContainer
();
$globalRenameRequestStore
=
$services
->
get
(
'CentralAuth.GlobalRenameRequestStore'
);
if
(
$globalRenameRequestStore
->
currentNameHasPendingRequest
(
$username
)
)
{
$errorMessage
=
"Skipping user {$username} - there is already a pending rename or vanish request for them."
;
$this
->
output
(
$errorMessage
.
"
\n
"
);
return
[
"success"
=>
false
,
"message"
=>
"duplicate"
];
}
$globalRenamersQueryParams
=
null
;
$parsedLink
=
parse_url
(
$request
[
'globalRenamersLink'
]
??
''
,
PHP_URL_QUERY
);
parse_str
(
$parsedLink
,
$globalRenamersQueryParams
);
$reason
=
urldecode
(
$globalRenamersQueryParams
[
'reason'
]
??
''
);
$decodedNewName
=
urldecode
(
$globalRenamersQueryParams
[
'newname'
]
??
''
);
$newName
=
$decodedNewName
===
''
?
null
:
$decodedNewName
;
// If new name couldn't be extracted, generate a random one
// Format should be `Renamed user <some_random_string>`
if
(
!
isset
(
$newName
)
)
{
$attempts
=
0
;
do
{
$candidate
=
wfRandomString
();
if
(
GlobalRenameRequest
::
isNameAvailable
(
$candidate
)->
isGood
()
)
{
$newName
=
"Renamed user {$candidate}"
;
$this
->
output
(
"New name not present in global_renamers_link. Generated '{$newName}'
\n
"
);
}
$attempts
++;
}
while
(
!
isset
(
$newName
)
&&
$attempts
<
5
);
}
if
(
!
isset
(
$newName
)
)
{
$errorMessage
=
"Skipping user {$username} as max attempts reached generating username."
;
$this
->
output
(
$errorMessage
.
"
\n
"
);
return
[
"success"
=>
false
,
"message"
=>
"error"
];
}
$request
=
$globalRenameRequestStore
->
newBlankRequest
()
->
setName
(
$username
)
->
setNewName
(
$newName
)
->
setReason
(
$reason
)
->
setComments
(
"Added automatically by maintenance/batchVanishUsers.php"
)
->
setPerformer
(
$performer
->
getId
()
)
->
setType
(
GlobalRenameRequest
::
VANISH
);
// If request can be auto-vanished, don't add to the queue
// - no edits, not blocked, and no logs
if
(
$causer
->
getGlobalEditCount
()
===
0
&&
// Commented because of lint, if causer has block(s) the function returns early (code above)
// $causerIsBlocked === false &&
!
$causer
->
hasPublicLogs
()
)
{
if
(
$isDryRun
)
{
return
[
"success"
=>
true
,
"message"
=>
"dry-auto-vanished"
];
}
$globalRenameFactory
=
$services
->
get
(
'CentralAuth.GlobalRenameFactory'
);
$requestArray
=
$request
->
toArray
();
// We need to add this two fields that are usually being provided by the Form
$requestArray
[
'movepages'
]
=
true
;
$requestArray
[
'suppressredirects'
]
=
true
;
$renameResult
=
$globalRenameFactory
->
newGlobalRenameUser
(
$uiPerformer
,
$causer
,
$newName
)
->
rename
(
$requestArray
);
if
(
!
$renameResult
->
isGood
()
)
{
$errorMessage
=
"Skipping user {$username} as there was a problem in the auto-vanish process."
;
$this
->
output
(
$errorMessage
.
"
\n
"
);
return
[
"success"
=>
false
,
"message"
=>
"error"
];
}
// We still want to leave a record that this happened, so change
// the status over to 'approved' for the subsequent save.
$request
->
setPerformer
(
$performer
->
getId
()
)
->
setComments
(
"Your username vanish request was processed successfully."
)
->
setStatus
(
GlobalRenameRequest
::
APPROVED
);
// Save the request to the database for it to be processed later.
if
(
!
$globalRenameRequestStore
->
save
(
$request
)
)
{
$errorMessage
=
"Skipping user {$username} as there was a problem in the auto-vanish process."
;
$this
->
output
(
$errorMessage
.
"
\n
"
);
return
[
"success"
=>
false
,
"message"
=>
"error"
];
}
$this
->
sendVanishingSuccessfulEmail
(
$causer
,
$request
);
return
[
"success"
=>
true
,
"message"
=>
"auto-vanished"
];
}
// Save the vanish request to the database as all validation has
// passed, but only if we're not in dry run mode.
if
(
!
$isDryRun
&&
!
$globalRenameRequestStore
->
save
(
$request
)
)
{
$errorMessage
=
"Skipping user {$username} as there was a problem saving the vanish request to the queue."
;
$this
->
output
(
$errorMessage
.
"
\n
"
);
return
[
"success"
=>
false
,
"message"
=>
"error"
];
}
return
[
"success"
=>
true
,
"message"
=>
"vanished"
];
}
/**
* Attempt to send a success email to the user whose vanish was fulfilled.
*
* TODO: https://phabricator.wikimedia.org/T369134 - refactor email sending
*
* @param CentralAuthUser $causer
* @param GlobalRenameRequest $request
* @return void
*/
private
function
sendVanishingSuccessfulEmail
(
CentralAuthUser
$causer
,
GlobalRenameRequest
$request
):
void
{
$bodyKey
=
'globalrenamequeue-vanish-email-body-approved-with-note'
;
$subject
=
$this
->
msg
(
'globalrenamequeue-vanish-email-subject-approved'
);
$body
=
$this
->
msg
(
$bodyKey
,
[
$request
->
getName
(),
$request
->
getComments
()
]
);
$from
=
new
MailAddress
(
$this
->
getConfig
()->
get
(
'PasswordSender'
),
$this
->
msg
(
'emailsender'
)
);
$to
=
new
MailAddress
(
$causer
->
getEmail
(),
$causer
->
getName
(),
''
);
// Users don't always have email addresses.
if
(
!
$to
->
address
)
{
return
;
}
// Attempt to send the email, and log an error if this fails.
$emailSendResult
=
UserMailer
::
send
(
$to
,
$from
,
$subject
,
$body
);
if
(
!
$emailSendResult
->
isOK
()
)
{
$this
->
output
(
$emailSendResult
->
getValue
()
.
"
\n
"
);
}
}
/**
* Get translated messages.
*
* @param string|string[]|MessageSpecifier $key
* @param mixed ...$params
* @return string
*/
private
function
msg
(
$key
,
...
$params
):
string
{
return
wfMessage
(
$key
,
...
$params
)->
inLanguage
(
'en'
)->
text
();
}
}
$maintClass
=
BatchVanishUsers
::
class
;
require_once
RUN_MAINTENANCE_IF_MAIN
;
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Sat, May 16, 21:41 (1 d, 9 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
16/f1/6aaebd30aa986197d812eca6406a
Default Alt Text
batchVanishUsers.php (12 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment