mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/AbuseFilter.git
synced 2024-11-15 02:03:53 +00:00
45d80bc7e5
- Depend on a generic IContextSource rather than SpecialAbuseFilter (lower coupling); - Inject a LinkRenderer (IContextSource doesn't have a ::getLinkRenderer method) - Add a helper method in SpecialAbuseFilter to get the page title, that can also be used elsewhere (and the name constant can be made private now) - Pull down the mFilter property (and rename it to just 'filter') to classes that actually need it. Some classes didn't need this at all and the types were different among subclasses Now the only cause of coupling between the View classes and SpecialAbuseFilter is the static call in getTitle. Change-Id: I3df0c3a7621f0cc9a64a16b0a402a15aae2d5d73
365 lines
10 KiB
PHP
365 lines
10 KiB
PHP
<?php
|
|
|
|
use MediaWiki\Block\DatabaseBlock;
|
|
use MediaWiki\Extension\AbuseFilter\AbuseFilterServices;
|
|
|
|
class AbuseFilterViewRevert extends AbuseFilterView {
|
|
/** @var int */
|
|
private $filter;
|
|
/**
|
|
* @var string The start time of the lookup period
|
|
*/
|
|
public $origPeriodStart;
|
|
/**
|
|
* @var string The end time of the lookup period
|
|
*/
|
|
public $origPeriodEnd;
|
|
/**
|
|
* @var string|null The same as $origPeriodStart
|
|
*/
|
|
public $mPeriodStart;
|
|
/**
|
|
* @var string|null The same as $origPeriodEnd
|
|
*/
|
|
public $mPeriodEnd;
|
|
/**
|
|
* @var string|null The reason provided for the revert
|
|
*/
|
|
public $mReason;
|
|
|
|
/**
|
|
* Shows the page
|
|
*/
|
|
public function show() {
|
|
$afPermManager = AbuseFilterServices::getPermissionManager();
|
|
$lang = $this->getLanguage();
|
|
|
|
$user = $this->getUser();
|
|
$out = $this->getOutput();
|
|
|
|
if ( !$afPermManager->canRevertFilterActions( $user ) ) {
|
|
throw new PermissionsError( 'abusefilter-revert' );
|
|
}
|
|
|
|
$block = $user->getBlock();
|
|
if ( $block && $block->isSitewide() ) {
|
|
throw new UserBlockedError( $block );
|
|
}
|
|
|
|
$this->loadParameters();
|
|
|
|
if ( $this->attemptRevert() ) {
|
|
return;
|
|
}
|
|
|
|
$filter = $this->filter;
|
|
|
|
$out->addWikiMsg( 'abusefilter-revert-intro', Message::numParam( $filter ) );
|
|
$out->setPageTitle( $this->msg( 'abusefilter-revert-title' )->numParams( $filter ) );
|
|
|
|
// First, the search form. Limit dates to avoid huge queries
|
|
$RCMaxAge = $this->getConfig()->get( 'RCMaxAge' );
|
|
$min = wfTimestamp( TS_ISO_8601, time() - $RCMaxAge );
|
|
$max = wfTimestampNow();
|
|
$filterLink =
|
|
$this->linkRenderer->makeLink(
|
|
$this->getTitle( $filter ),
|
|
$lang->formatNum( $filter )
|
|
);
|
|
$searchFields = [];
|
|
$searchFields['filterid'] = [
|
|
'type' => 'info',
|
|
'default' => $filterLink,
|
|
'raw' => true,
|
|
'label-message' => 'abusefilter-revert-filter'
|
|
];
|
|
$searchFields['periodstart'] = [
|
|
'type' => 'datetime',
|
|
'name' => 'wpPeriodStart',
|
|
'default' => $this->origPeriodStart,
|
|
'label-message' => 'abusefilter-revert-periodstart',
|
|
'min' => $min,
|
|
'max' => $max
|
|
];
|
|
$searchFields['periodend'] = [
|
|
'type' => 'datetime',
|
|
'name' => 'wpPeriodEnd',
|
|
'default' => $this->origPeriodEnd,
|
|
'label-message' => 'abusefilter-revert-periodend',
|
|
'min' => $min,
|
|
'max' => $max
|
|
];
|
|
|
|
HTMLForm::factory( 'ooui', $searchFields, $this->getContext() )
|
|
->addHiddenField( 'submit', 1 )
|
|
->setAction( $this->getTitle( "revert/$filter" )->getLocalURL() )
|
|
->setWrapperLegendMsg( 'abusefilter-revert-search-legend' )
|
|
->setSubmitTextMsg( 'abusefilter-revert-search' )
|
|
->setMethod( 'post' )
|
|
->prepareForm()
|
|
->displayForm( false );
|
|
|
|
if ( $this->mSubmit ) {
|
|
// Add a summary of everything that will be reversed.
|
|
$out->addWikiMsg( 'abusefilter-revert-preview-intro' );
|
|
|
|
// Look up all of them.
|
|
$results = $this->doLookup();
|
|
$list = [];
|
|
|
|
$context = $this->getContext();
|
|
foreach ( $results as $result ) {
|
|
$displayActions = [];
|
|
foreach ( $result['actions'] as $action ) {
|
|
$displayActions[] = AbuseFilter::getActionDisplay( $action, $context );
|
|
}
|
|
|
|
$msg = $this->msg( 'abusefilter-revert-preview-item' )
|
|
->params(
|
|
$lang->timeanddate( $result['timestamp'], true )
|
|
)->rawParams(
|
|
Linker::userLink( $result['userid'], $result['user'] )
|
|
)->params(
|
|
$result['action']
|
|
)->rawParams(
|
|
$this->linkRenderer->makeLink( $result['title'] )
|
|
)->params(
|
|
$lang->commaList( $displayActions )
|
|
)->rawParams(
|
|
$this->linkRenderer->makeLink(
|
|
SpecialPage::getTitleFor( 'AbuseLog' ),
|
|
$this->msg( 'abusefilter-log-detailslink' )->text(),
|
|
[],
|
|
[ 'details' => $result['id'] ]
|
|
)
|
|
)->params( $result['user'] )->parse();
|
|
$list[] = Xml::tags( 'li', null, $msg );
|
|
}
|
|
|
|
$out->addHTML( Xml::tags( 'ul', null, implode( "\n", $list ) ) );
|
|
|
|
// Add a button down the bottom.
|
|
$confirmForm = [];
|
|
$confirmForm['edittoken'] = [
|
|
'type' => 'hidden',
|
|
'name' => 'editToken',
|
|
'default' => $user->getEditToken( "abusefilter-revert-$filter" )
|
|
];
|
|
$confirmForm['title'] = [
|
|
'type' => 'hidden',
|
|
'name' => 'title',
|
|
'default' => $this->getTitle( "revert/$filter" )->getPrefixedDBkey()
|
|
];
|
|
$confirmForm['wpPeriodStart'] = [
|
|
'type' => 'hidden',
|
|
'name' => 'wpPeriodStart',
|
|
'default' => $this->origPeriodStart
|
|
];
|
|
$confirmForm['wpPeriodEnd'] = [
|
|
'type' => 'hidden',
|
|
'name' => 'wpPeriodEnd',
|
|
'default' => $this->origPeriodEnd
|
|
];
|
|
$confirmForm['reason'] = [
|
|
'type' => 'text',
|
|
'label-message' => 'abusefilter-revert-reasonfield',
|
|
'name' => 'wpReason',
|
|
'id' => 'wpReason',
|
|
];
|
|
HTMLForm::factory( 'ooui', $confirmForm, $this->getContext() )
|
|
->setAction( $this->getTitle( "revert/$filter" )->getLocalURL() )
|
|
->setWrapperLegendMsg( 'abusefilter-revert-confirm-legend' )
|
|
->setSubmitTextMsg( 'abusefilter-revert-confirm' )
|
|
->setMethod( 'post' )
|
|
->prepareForm()
|
|
->displayForm( false );
|
|
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return array[]
|
|
*/
|
|
public function doLookup() {
|
|
$periodStart = $this->mPeriodStart;
|
|
$periodEnd = $this->mPeriodEnd;
|
|
$filter = $this->filter;
|
|
|
|
$conds = [ 'afl_filter' => $filter ];
|
|
|
|
$dbr = wfGetDB( DB_REPLICA );
|
|
|
|
if ( $periodStart !== null ) {
|
|
$conds[] = 'afl_timestamp >= ' . $dbr->addQuotes( $dbr->timestamp( $periodStart ) );
|
|
}
|
|
if ( $periodEnd !== null ) {
|
|
$conds[] = 'afl_timestamp <= ' . $dbr->addQuotes( $dbr->timestamp( $periodEnd ) );
|
|
}
|
|
|
|
$selectFields = [
|
|
'afl_id',
|
|
'afl_user',
|
|
'afl_user_text',
|
|
'afl_action',
|
|
'afl_actions',
|
|
'afl_var_dump',
|
|
'afl_timestamp',
|
|
'afl_namespace',
|
|
'afl_title',
|
|
'afl_wiki',
|
|
];
|
|
$res = $dbr->select( 'abuse_filter_log', $selectFields, $conds, __METHOD__ );
|
|
|
|
$results = [];
|
|
foreach ( $res as $row ) {
|
|
// Don't revert if there was no action, or the action was global
|
|
if ( !$row->afl_actions || $row->afl_wiki != null ) {
|
|
continue;
|
|
}
|
|
|
|
$actions = explode( ',', $row->afl_actions );
|
|
$reversibleActions = [ 'block', 'blockautopromote', 'degroup' ];
|
|
$currentReversibleActions = array_intersect( $actions, $reversibleActions );
|
|
if ( count( $currentReversibleActions ) ) {
|
|
$results[] = [
|
|
'id' => $row->afl_id,
|
|
'actions' => $currentReversibleActions,
|
|
'user' => $row->afl_user_text,
|
|
'userid' => $row->afl_user,
|
|
'vars' => AbuseFilter::loadVarDump( $row->afl_var_dump ),
|
|
'title' => Title::makeTitle( $row->afl_namespace, $row->afl_title ),
|
|
'action' => $row->afl_action,
|
|
'timestamp' => $row->afl_timestamp
|
|
];
|
|
}
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* Loads parameters from request
|
|
*/
|
|
public function loadParameters() {
|
|
$request = $this->getRequest();
|
|
|
|
$this->filter = (int)$this->mParams[1];
|
|
$this->origPeriodStart = $request->getText( 'wpPeriodStart' );
|
|
$this->mPeriodStart = strtotime( $this->origPeriodStart ) ?: null;
|
|
$this->origPeriodEnd = $request->getText( 'wpPeriodEnd' );
|
|
$this->mPeriodEnd = strtotime( $this->origPeriodEnd ) ?: null;
|
|
$this->mSubmit = $request->getBool( 'submit' );
|
|
$this->mReason = $request->getVal( 'wpReason' );
|
|
}
|
|
|
|
/**
|
|
* @return bool
|
|
*/
|
|
public function attemptRevert() {
|
|
$filter = $this->filter;
|
|
$token = $this->getRequest()->getVal( 'editToken' );
|
|
if ( !$this->getUser()->matchEditToken( $token, "abusefilter-revert-$filter" ) ) {
|
|
return false;
|
|
}
|
|
|
|
$results = $this->doLookup();
|
|
foreach ( $results as $result ) {
|
|
$actions = $result['actions'];
|
|
foreach ( $actions as $action ) {
|
|
$this->revertAction( $action, $result );
|
|
}
|
|
}
|
|
$this->getOutput()->wrapWikiMsg(
|
|
'<p class="success">$1</p>',
|
|
[
|
|
'abusefilter-revert-success',
|
|
$filter,
|
|
$this->getLanguage()->formatNum( $filter )
|
|
]
|
|
);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @param string $action
|
|
* @param array $result
|
|
* @return bool
|
|
* @throws MWException
|
|
*/
|
|
public function revertAction( $action, $result ) {
|
|
switch ( $action ) {
|
|
case 'block':
|
|
$block = DatabaseBlock::newFromTarget( $result['user'] );
|
|
if ( !( $block && $block->getBy() === AbuseFilter::getFilterUser()->getId() ) ) {
|
|
// Not blocked by abuse filter
|
|
return false;
|
|
}
|
|
$block->delete();
|
|
$logEntry = new ManualLogEntry( 'block', 'unblock' );
|
|
$logEntry->setTarget( Title::makeTitle( NS_USER, $result['user'] ) );
|
|
$logEntry->setComment(
|
|
$this->msg(
|
|
'abusefilter-revert-reason', $this->filter, $this->mReason
|
|
)->inContentLanguage()->text()
|
|
);
|
|
$logEntry->setPerformer( $this->getUser() );
|
|
$logEntry->publish( $logEntry->insert() );
|
|
return true;
|
|
case 'blockautopromote':
|
|
$target = User::newFromId( $result['userid'] );
|
|
$msg = $this->msg(
|
|
'abusefilter-revert-reason', $this->filter, $this->mReason
|
|
)->inContentLanguage()->text();
|
|
|
|
return AbuseFilter::unblockAutopromote( $target, $this->getUser(), $msg );
|
|
case 'degroup':
|
|
// Pull the user's groups from the vars.
|
|
$oldGroups = $result['vars']->getVar( 'user_groups' )->toNative();
|
|
$oldGroups = array_diff( $oldGroups, User::getImplicitGroups() );
|
|
|
|
$rows = [];
|
|
foreach ( $oldGroups as $group ) {
|
|
$rows[] = [
|
|
'ug_user' => $result['userid'],
|
|
'ug_group' => $group
|
|
];
|
|
}
|
|
|
|
// Cheat a little bit. User::addGroup repeatedly is too slow.
|
|
$user = User::newFromId( $result['userid'] );
|
|
$currentGroups = $user->getGroups();
|
|
$newGroups = array_merge( $oldGroups, $currentGroups );
|
|
|
|
// Don't do anything if there are no groups to add.
|
|
if ( !count( array_diff( $newGroups, $currentGroups ) ) ) {
|
|
return false;
|
|
}
|
|
|
|
$dbw = wfGetDB( DB_MASTER );
|
|
$dbw->insert( 'user_groups', $rows, __METHOD__, [ 'IGNORE' ] );
|
|
$user->invalidateCache();
|
|
|
|
$logEntry = new ManualLogEntry( 'rights', 'rights' );
|
|
$logEntry->setTarget( $user->getUserPage() );
|
|
$logEntry->setPerformer( $this->getUser() );
|
|
$logEntry->setComment(
|
|
$this->msg(
|
|
'abusefilter-revert-reason',
|
|
$this->filter,
|
|
$this->mReason
|
|
)->inContentLanguage()->text()
|
|
);
|
|
$logEntry->setParameters( [
|
|
'4::oldgroups' => $currentGroups,
|
|
'5::newgroups' => $newGroups
|
|
] );
|
|
$logEntry->publish( $logEntry->insert() );
|
|
|
|
return true;
|
|
}
|
|
|
|
throw new MWException( 'Invalid action' . $action );
|
|
}
|
|
}
|