2008-06-27 06:18:51 +00:00
|
|
|
<?php
|
|
|
|
|
2020-01-15 16:08:53 +00:00
|
|
|
use MediaWiki\Extension\AbuseFilter\AbuseFilterServices;
|
2020-06-03 00:43:22 +00:00
|
|
|
use MediaWiki\Extension\AbuseFilter\Hooks\AbuseFilterHookRunner;
|
2020-01-11 17:05:30 +00:00
|
|
|
use MediaWiki\Extension\AbuseFilter\VariableGenerator\VariableGenerator;
|
2016-06-13 11:53:50 +00:00
|
|
|
use MediaWiki\Logger\LoggerFactory;
|
|
|
|
use MediaWiki\MediaWikiServices;
|
2020-01-21 07:38:52 +00:00
|
|
|
use MediaWiki\Revision\RevisionRecord;
|
2019-06-27 22:55:20 +00:00
|
|
|
use Wikimedia\Rdbms\DBError;
|
2020-01-21 07:38:52 +00:00
|
|
|
use Wikimedia\Rdbms\IDatabase;
|
2016-06-13 11:53:50 +00:00
|
|
|
|
2013-10-24 22:10:36 +00:00
|
|
|
/**
|
Add a new class for methods related to running filters
Currently we strongly abuse (pardon the pun) the AbuseFilter class: its
purpose should be to hold static functions intended as generic utility
functions (e.g. to format messages, determine whether a filter is global
etc.), but we actually use it for all methods related to running filters.
This patch creates a new class, AbuseFilterRunner, containing all such
methods, which have been made non-static. This leads to several
improvements (also for related methods and the parser), and opens the
way to further improve the code.
Aside from making the code prettier, less global and easier to test,
this patch could also produce a performance improvement, although I
don't have tools to measure that.
Also note that many public methods have been removed, and almost any of
them has been made protected; a couple of them (the ones used from outside)
are left for back-compat, and will be removed in the future.
Change-Id: I2eab2e50356eeb5224446ee2d0df9c787ae95b80
2018-12-07 17:46:02 +00:00
|
|
|
* This class contains most of the business logic of AbuseFilter. It consists of
|
|
|
|
* static functions for generic use (mostly utility functions).
|
2013-10-24 22:10:36 +00:00
|
|
|
*/
|
2008-06-27 06:18:51 +00:00
|
|
|
class AbuseFilter {
|
2018-11-08 14:34:32 +00:00
|
|
|
/**
|
|
|
|
* @var int How long to keep profiling data in cache (in seconds)
|
|
|
|
*/
|
2008-07-17 02:43:45 +00:00
|
|
|
public static $statsStoragePeriod = 86400;
|
2016-06-28 22:50:38 +00:00
|
|
|
|
2018-12-12 11:04:21 +00:00
|
|
|
/**
|
|
|
|
* @var array [filter ID => stdClass|null] as retrieved from self::getFilter. ID could be either
|
2019-02-06 13:42:05 +00:00
|
|
|
* an integer or "<GLOBAL_FILTER_PREFIX><integer>"
|
2018-12-12 11:04:21 +00:00
|
|
|
*/
|
2016-06-28 22:50:38 +00:00
|
|
|
private static $filterCache = [];
|
|
|
|
|
2019-02-06 13:42:05 +00:00
|
|
|
/** @var string The prefix to use for global filters */
|
2020-01-21 11:13:11 +00:00
|
|
|
public const GLOBAL_FILTER_PREFIX = 'global-';
|
2019-02-06 13:42:05 +00:00
|
|
|
|
2020-08-08 23:04:29 +00:00
|
|
|
/**
|
2018-11-08 14:34:32 +00:00
|
|
|
* @var array Map of (action ID => string[])
|
|
|
|
* @fixme avoid global state here
|
|
|
|
*/
|
2018-04-04 21:14:25 +00:00
|
|
|
public static $tagsToSet = [];
|
2016-06-28 22:50:38 +00:00
|
|
|
|
2019-01-05 18:28:03 +00:00
|
|
|
/**
|
|
|
|
* @var array IDs of logged filters like [ page title => [ 'local' => [ids], 'global' => [ids] ] ].
|
|
|
|
* @fixme avoid global state
|
|
|
|
*/
|
|
|
|
public static $logIds = [];
|
|
|
|
|
2019-01-26 13:23:07 +00:00
|
|
|
/**
|
|
|
|
* @var string[] The FULL list of fields in the abuse_filter table
|
Add a new class for methods related to running filters
Currently we strongly abuse (pardon the pun) the AbuseFilter class: its
purpose should be to hold static functions intended as generic utility
functions (e.g. to format messages, determine whether a filter is global
etc.), but we actually use it for all methods related to running filters.
This patch creates a new class, AbuseFilterRunner, containing all such
methods, which have been made non-static. This leads to several
improvements (also for related methods and the parser), and opens the
way to further improve the code.
Aside from making the code prettier, less global and easier to test,
this patch could also produce a performance improvement, although I
don't have tools to measure that.
Also note that many public methods have been removed, and almost any of
them has been made protected; a couple of them (the ones used from outside)
are left for back-compat, and will be removed in the future.
Change-Id: I2eab2e50356eeb5224446ee2d0df9c787ae95b80
2018-12-07 17:46:02 +00:00
|
|
|
* @internal
|
2019-01-26 13:23:07 +00:00
|
|
|
*/
|
2020-01-21 11:13:11 +00:00
|
|
|
public const ALL_ABUSE_FILTER_FIELDS = [
|
2019-01-26 13:23:07 +00:00
|
|
|
'af_id',
|
|
|
|
'af_pattern',
|
|
|
|
'af_user',
|
|
|
|
'af_user_text',
|
|
|
|
'af_timestamp',
|
|
|
|
'af_enabled',
|
|
|
|
'af_comments',
|
|
|
|
'af_public_comments',
|
|
|
|
'af_hidden',
|
|
|
|
'af_hit_count',
|
|
|
|
'af_throttled',
|
|
|
|
'af_deleted',
|
|
|
|
'af_actions',
|
|
|
|
'af_global',
|
|
|
|
'af_group'
|
|
|
|
];
|
|
|
|
|
2019-11-16 15:32:36 +00:00
|
|
|
public const HISTORY_MAPPINGS = [
|
2009-02-07 09:34:11 +00:00
|
|
|
'af_pattern' => 'afh_pattern',
|
|
|
|
'af_user' => 'afh_user',
|
|
|
|
'af_user_text' => 'afh_user_text',
|
|
|
|
'af_timestamp' => 'afh_timestamp',
|
|
|
|
'af_comments' => 'afh_comments',
|
|
|
|
'af_public_comments' => 'afh_public_comments',
|
|
|
|
'af_deleted' => 'afh_deleted',
|
2012-05-06 06:44:45 +00:00
|
|
|
'af_id' => 'afh_filter',
|
|
|
|
'af_group' => 'afh_group',
|
2017-06-15 14:23:34 +00:00
|
|
|
];
|
2018-02-18 13:44:17 +00:00
|
|
|
|
2011-02-10 17:32:57 +00:00
|
|
|
/**
|
2020-02-28 18:36:32 +00:00
|
|
|
* @deprecated Since 1.35 Use VariableGenerator::addUserVars()
|
2017-01-02 11:41:29 +00:00
|
|
|
* @param User $user
|
2020-05-28 18:09:17 +00:00
|
|
|
* @param RecentChange|null $entry
|
2011-02-10 17:32:57 +00:00
|
|
|
* @return AbuseFilterVariableHolder
|
|
|
|
*/
|
2020-05-28 18:09:17 +00:00
|
|
|
public static function generateUserVars( User $user, RecentChange $entry = null ) {
|
2020-02-28 18:36:32 +00:00
|
|
|
wfDeprecated( __METHOD__, '1.35' );
|
2019-06-25 16:39:57 +00:00
|
|
|
$vars = new AbuseFilterVariableHolder();
|
|
|
|
$generator = new VariableGenerator( $vars );
|
|
|
|
return $generator->addUserVars( $user, $entry )->getVariableHolder();
|
2008-06-27 06:18:51 +00:00
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2012-03-11 20:40:04 +00:00
|
|
|
/**
|
2019-03-28 19:59:53 +00:00
|
|
|
* @param int $filterID The ID of the filter
|
|
|
|
* @param bool|int $global Whether the filter is global
|
2012-03-11 20:40:04 +00:00
|
|
|
* @return bool
|
|
|
|
*/
|
2019-03-28 19:59:53 +00:00
|
|
|
public static function filterHidden( $filterID, $global = false ) {
|
2019-06-27 22:55:20 +00:00
|
|
|
global $wgAbuseFilterCentralDB;
|
|
|
|
|
2019-02-06 12:59:34 +00:00
|
|
|
if ( $global ) {
|
2012-01-03 17:29:10 +00:00
|
|
|
if ( !$wgAbuseFilterCentralDB ) {
|
|
|
|
return false;
|
|
|
|
}
|
2019-06-27 22:55:20 +00:00
|
|
|
$dbr = self::getCentralDB( DB_REPLICA );
|
2012-01-03 17:29:10 +00:00
|
|
|
} else {
|
2017-08-30 02:51:39 +00:00
|
|
|
$dbr = wfGetDB( DB_REPLICA );
|
2012-01-03 17:29:10 +00:00
|
|
|
}
|
2019-06-27 22:55:20 +00:00
|
|
|
|
2009-10-07 13:57:06 +00:00
|
|
|
$hidden = $dbr->selectField(
|
|
|
|
'abuse_filter',
|
|
|
|
'af_hidden',
|
2019-03-28 19:59:53 +00:00
|
|
|
[ 'af_id' => $filterID ],
|
2009-10-07 13:57:06 +00:00
|
|
|
__METHOD__
|
|
|
|
);
|
2015-09-28 18:03:35 +00:00
|
|
|
|
2015-06-28 01:22:04 +00:00
|
|
|
return (bool)$hidden;
|
2009-03-17 13:18:33 +00:00
|
|
|
}
|
2009-01-28 23:54:41 +00:00
|
|
|
|
2011-02-10 17:32:57 +00:00
|
|
|
/**
|
2020-02-28 18:36:32 +00:00
|
|
|
* @deprecated Since 1.35 Use VariableGenerator::addTitleVars
|
2017-01-02 11:41:29 +00:00
|
|
|
* @param Title|null $title
|
|
|
|
* @param string $prefix
|
2020-05-28 18:09:17 +00:00
|
|
|
* @param RecentChange|null $entry
|
2011-02-10 17:32:57 +00:00
|
|
|
* @return AbuseFilterVariableHolder
|
|
|
|
*/
|
2020-05-28 18:09:17 +00:00
|
|
|
public static function generateTitleVars( $title, $prefix, RecentChange $entry = null ) {
|
2020-02-28 18:36:32 +00:00
|
|
|
wfDeprecated( __METHOD__, '1.35' );
|
2019-06-25 16:39:57 +00:00
|
|
|
$vars = new AbuseFilterVariableHolder();
|
|
|
|
if ( !( $title instanceof Title ) ) {
|
|
|
|
return $vars;
|
|
|
|
}
|
|
|
|
$generator = new VariableGenerator( $vars );
|
|
|
|
return $generator->addTitleVars( $title, $prefix, $entry )->getVariableHolder();
|
2008-06-27 06:18:51 +00:00
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2018-12-30 17:15:33 +00:00
|
|
|
/**
|
2020-02-28 18:36:32 +00:00
|
|
|
* @deprecated Since 1.35 Use VariableGenerator::addGenericVars
|
2018-12-30 17:15:33 +00:00
|
|
|
* @return AbuseFilterVariableHolder
|
|
|
|
*/
|
2019-12-17 15:06:44 +00:00
|
|
|
public static function generateGenericVars() {
|
2020-02-28 18:36:32 +00:00
|
|
|
wfDeprecated( __METHOD__, '1.35' );
|
2019-06-25 16:39:57 +00:00
|
|
|
$vars = new AbuseFilterVariableHolder();
|
|
|
|
$generator = new VariableGenerator( $vars );
|
2019-12-17 15:06:44 +00:00
|
|
|
return $generator->addGenericVars()->getVariableHolder();
|
2018-12-30 17:15:33 +00:00
|
|
|
}
|
|
|
|
|
2011-08-24 22:11:52 +00:00
|
|
|
/**
|
|
|
|
* Returns an associative array of filters which were tripped
|
|
|
|
*
|
2017-01-02 11:41:29 +00:00
|
|
|
* @param AbuseFilterVariableHolder $vars
|
2018-10-03 09:59:31 +00:00
|
|
|
* @param Title $title
|
2014-11-07 12:21:45 +00:00
|
|
|
* @param string $group The filter's group (as defined in $wgAbuseFilterValidGroups)
|
2018-04-03 15:34:03 +00:00
|
|
|
* @param string $mode 'execute' for edits and logs, 'stash' for cached matches
|
2016-06-28 22:50:38 +00:00
|
|
|
* @return bool[] Map of (integer filter ID => bool)
|
2019-08-04 15:39:13 +00:00
|
|
|
* @deprecated Since 1.34 See comment on AbuseFilterRunner::checkAllFilters
|
2011-08-24 22:11:52 +00:00
|
|
|
*/
|
2018-04-04 21:24:41 +00:00
|
|
|
public static function checkAllFilters(
|
2018-10-17 05:15:21 +00:00
|
|
|
AbuseFilterVariableHolder $vars,
|
2018-10-03 09:59:31 +00:00
|
|
|
Title $title,
|
2018-04-04 21:24:41 +00:00
|
|
|
$group = 'default',
|
2018-04-29 17:52:45 +00:00
|
|
|
$mode = 'execute'
|
|
|
|
) {
|
2019-08-21 10:04:10 +00:00
|
|
|
$parser = self::getDefaultParser( $vars );
|
Add a new class for methods related to running filters
Currently we strongly abuse (pardon the pun) the AbuseFilter class: its
purpose should be to hold static functions intended as generic utility
functions (e.g. to format messages, determine whether a filter is global
etc.), but we actually use it for all methods related to running filters.
This patch creates a new class, AbuseFilterRunner, containing all such
methods, which have been made non-static. This leads to several
improvements (also for related methods and the parser), and opens the
way to further improve the code.
Aside from making the code prettier, less global and easier to test,
this patch could also produce a performance improvement, although I
don't have tools to measure that.
Also note that many public methods have been removed, and almost any of
them has been made protected; a couple of them (the ones used from outside)
are left for back-compat, and will be removed in the future.
Change-Id: I2eab2e50356eeb5224446ee2d0df9c787ae95b80
2018-12-07 17:46:02 +00:00
|
|
|
$user = RequestContext::getMain()->getUser();
|
2009-10-07 13:57:06 +00:00
|
|
|
|
Add a new class for methods related to running filters
Currently we strongly abuse (pardon the pun) the AbuseFilter class: its
purpose should be to hold static functions intended as generic utility
functions (e.g. to format messages, determine whether a filter is global
etc.), but we actually use it for all methods related to running filters.
This patch creates a new class, AbuseFilterRunner, containing all such
methods, which have been made non-static. This leads to several
improvements (also for related methods and the parser), and opens the
way to further improve the code.
Aside from making the code prettier, less global and easier to test,
this patch could also produce a performance improvement, although I
don't have tools to measure that.
Also note that many public methods have been removed, and almost any of
them has been made protected; a couple of them (the ones used from outside)
are left for back-compat, and will be removed in the future.
Change-Id: I2eab2e50356eeb5224446ee2d0df9c787ae95b80
2018-12-07 17:46:02 +00:00
|
|
|
$runner = new AbuseFilterRunner( $user, $title, $vars, $group );
|
|
|
|
$runner->parser = $parser;
|
|
|
|
return $runner->checkAllFilters();
|
2017-09-25 20:23:55 +00:00
|
|
|
}
|
|
|
|
|
2016-04-08 13:51:54 +00:00
|
|
|
/**
|
2019-12-07 17:20:10 +00:00
|
|
|
* @param int|string $filter
|
2019-03-24 18:01:35 +00:00
|
|
|
* @internal
|
2016-04-08 13:51:54 +00:00
|
|
|
*/
|
2019-03-24 18:01:35 +00:00
|
|
|
public static function resetFilterProfile( $filter ) {
|
2018-10-18 17:30:15 +00:00
|
|
|
$stash = MediaWikiServices::getInstance()->getMainObjectStash();
|
2019-02-10 10:01:39 +00:00
|
|
|
$profileKey = self::filterProfileKey( $filter );
|
2016-04-08 13:51:54 +00:00
|
|
|
|
2019-02-10 10:01:39 +00:00
|
|
|
$stash->delete( $profileKey );
|
2016-04-08 13:51:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2019-02-10 10:01:39 +00:00
|
|
|
* Retrieve per-filter statistics.
|
|
|
|
*
|
2017-01-02 11:41:29 +00:00
|
|
|
* @param string $filter
|
2016-04-08 13:51:54 +00:00
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public static function getFilterProfile( $filter ) {
|
2018-10-18 17:30:15 +00:00
|
|
|
$stash = MediaWikiServices::getInstance()->getMainObjectStash();
|
2019-02-10 10:01:39 +00:00
|
|
|
$profile = $stash->get( self::filterProfileKey( $filter ) );
|
2016-04-08 13:51:54 +00:00
|
|
|
|
2019-02-10 10:01:39 +00:00
|
|
|
if ( $profile !== false ) {
|
|
|
|
$curCount = $profile['count'];
|
|
|
|
$curTotalTime = $profile['total-time'];
|
|
|
|
$curTotalConds = $profile['total-cond'];
|
|
|
|
} else {
|
2019-02-10 10:57:37 +00:00
|
|
|
return [ 0, 0, 0, 0 ];
|
2016-04-08 13:51:54 +00:00
|
|
|
}
|
|
|
|
|
2019-02-10 12:08:06 +00:00
|
|
|
// Return in milliseconds, rounded to 2dp
|
|
|
|
$avgTime = round( $curTotalTime / $curCount, 2 );
|
|
|
|
$avgCond = round( $curTotalConds / $curCount, 1 );
|
2016-04-08 13:51:54 +00:00
|
|
|
|
2019-02-10 12:08:06 +00:00
|
|
|
return [ $curCount, $profile['matches'], $avgTime, $avgCond ];
|
2016-04-08 13:51:54 +00:00
|
|
|
}
|
|
|
|
|
2011-08-24 22:11:52 +00:00
|
|
|
/**
|
2019-02-06 12:59:34 +00:00
|
|
|
* Utility function to split "<GLOBAL_FILTER_PREFIX>$index" to an array [ $id, $global ], where
|
|
|
|
* $id is $index casted to int, and $global is a boolean: true if the filter is global,
|
|
|
|
* false otherwise (i.e. if the $filter === $index). Note that the $index
|
|
|
|
* is always casted to int. Passing anything which isn't an integer-like value or a string
|
|
|
|
* in the shape "<GLOBAL_FILTER_PREFIX>integer" will throw.
|
|
|
|
* This reverses self::buildGlobalName
|
2011-08-24 22:11:52 +00:00
|
|
|
*
|
2019-02-03 15:01:58 +00:00
|
|
|
* @param string|int $filter
|
2019-02-06 12:59:34 +00:00
|
|
|
* @return array
|
|
|
|
* @throws InvalidArgumentException
|
|
|
|
*/
|
|
|
|
public static function splitGlobalName( $filter ) {
|
|
|
|
if ( preg_match( '/^' . self::GLOBAL_FILTER_PREFIX . '\d+$/', $filter ) === 1 ) {
|
|
|
|
$id = intval( substr( $filter, strlen( self::GLOBAL_FILTER_PREFIX ) ) );
|
|
|
|
return [ $id, true ];
|
|
|
|
} elseif ( is_numeric( $filter ) ) {
|
|
|
|
return [ (int)$filter, false ];
|
|
|
|
} else {
|
|
|
|
throw new InvalidArgumentException( "Invalid filter name: $filter" );
|
2009-03-30 06:12:12 +00:00
|
|
|
}
|
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2019-02-06 13:42:05 +00:00
|
|
|
/**
|
|
|
|
* Given a filter ID and a boolean indicating whether it's global, build a string like
|
|
|
|
* "<GLOBAL_FILTER_PREFIX>$ID". Note that, with global = false, $id is casted to string.
|
2019-02-06 12:59:34 +00:00
|
|
|
* This reverses self::splitGlobalName.
|
2019-02-06 13:42:05 +00:00
|
|
|
*
|
|
|
|
* @param int $id The filter ID
|
|
|
|
* @param bool $global Whether the filter is global
|
|
|
|
* @return string
|
2019-03-28 19:59:53 +00:00
|
|
|
* @todo Calling this method should be avoided wherever possible
|
2019-02-06 13:42:05 +00:00
|
|
|
*/
|
|
|
|
public static function buildGlobalName( $id, $global = true ) {
|
|
|
|
$prefix = $global ? self::GLOBAL_FILTER_PREFIX : '';
|
|
|
|
return "$prefix$id";
|
|
|
|
}
|
|
|
|
|
2012-03-11 20:40:04 +00:00
|
|
|
/**
|
2017-01-02 11:41:29 +00:00
|
|
|
* @param string[] $filters
|
2019-10-09 10:37:38 +00:00
|
|
|
* @return (string|array)[][][]
|
|
|
|
* @phan-return array<string,array<string,array{action:string,parameters:string[]}>>
|
2012-03-11 20:40:04 +00:00
|
|
|
*/
|
2009-03-30 06:12:12 +00:00
|
|
|
public static function getConsequencesForFilters( $filters ) {
|
2017-06-15 14:23:34 +00:00
|
|
|
$globalFilters = [];
|
|
|
|
$localFilters = [];
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2010-02-13 14:10:36 +00:00
|
|
|
foreach ( $filters as $filter ) {
|
2019-06-17 10:54:00 +00:00
|
|
|
list( $filterID, $global ) = self::splitGlobalName( $filter );
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2019-02-06 12:59:34 +00:00
|
|
|
if ( $global ) {
|
2019-06-17 10:54:00 +00:00
|
|
|
$globalFilters[] = $filterID;
|
2010-08-19 21:12:09 +00:00
|
|
|
} else {
|
2009-03-30 06:12:12 +00:00
|
|
|
$localFilters[] = $filter;
|
2010-08-19 21:12:09 +00:00
|
|
|
}
|
2009-03-30 06:12:12 +00:00
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2009-03-30 06:12:12 +00:00
|
|
|
// Load local filter info
|
2017-08-30 02:51:39 +00:00
|
|
|
$dbr = wfGetDB( DB_REPLICA );
|
2008-06-27 06:18:51 +00:00
|
|
|
// Retrieve the consequences.
|
2017-06-15 14:23:34 +00:00
|
|
|
$consequences = [];
|
2009-10-07 13:57:06 +00:00
|
|
|
|
|
|
|
if ( count( $localFilters ) ) {
|
2009-03-30 06:12:12 +00:00
|
|
|
$consequences = self::loadConsequencesFromDB( $dbr, $localFilters );
|
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
|
|
|
|
if ( count( $globalFilters ) ) {
|
2019-06-27 22:55:20 +00:00
|
|
|
$consequences += self::loadConsequencesFromDB(
|
|
|
|
self::getCentralDB( DB_REPLICA ),
|
2019-02-06 13:42:05 +00:00
|
|
|
$globalFilters,
|
|
|
|
self::GLOBAL_FILTER_PREFIX
|
|
|
|
);
|
2009-03-30 06:12:12 +00:00
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2009-03-30 06:12:12 +00:00
|
|
|
return $consequences;
|
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2012-03-11 20:40:04 +00:00
|
|
|
/**
|
2017-12-22 22:09:33 +00:00
|
|
|
* @param IDatabase $dbr
|
2017-01-02 11:41:29 +00:00
|
|
|
* @param string[] $filters
|
|
|
|
* @param string $prefix
|
2019-10-09 10:37:38 +00:00
|
|
|
* @return (string|array)[][][]
|
|
|
|
* @phan-return array<string,array<string,array{action:string,parameters:string[]}>>
|
2012-03-11 20:40:04 +00:00
|
|
|
*/
|
2018-10-17 05:15:21 +00:00
|
|
|
public static function loadConsequencesFromDB( IDatabase $dbr, $filters, $prefix = '' ) {
|
2017-06-15 14:23:34 +00:00
|
|
|
$actionsByFilter = [];
|
2010-02-13 14:10:36 +00:00
|
|
|
foreach ( $filters as $filter ) {
|
2017-06-15 14:23:34 +00:00
|
|
|
$actionsByFilter[$prefix . $filter] = [];
|
2009-03-30 06:12:12 +00:00
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
|
|
|
|
$res = $dbr->select(
|
2017-06-15 14:23:34 +00:00
|
|
|
[ 'abuse_filter_action', 'abuse_filter' ],
|
2009-10-07 13:57:06 +00:00
|
|
|
'*',
|
2017-06-15 14:23:34 +00:00
|
|
|
[ 'af_id' => $filters ],
|
2009-10-07 13:57:06 +00:00
|
|
|
__METHOD__,
|
2017-06-15 14:23:34 +00:00
|
|
|
[],
|
|
|
|
[ 'abuse_filter_action' => [ 'LEFT JOIN', 'afa_filter=af_id' ] ]
|
2009-10-07 13:57:06 +00:00
|
|
|
);
|
|
|
|
|
2009-01-23 19:23:19 +00:00
|
|
|
// Categorise consequences by filter.
|
2016-06-03 18:01:56 +00:00
|
|
|
global $wgAbuseFilterRestrictions;
|
2015-09-28 18:03:35 +00:00
|
|
|
foreach ( $res as $row ) {
|
2009-02-07 09:34:11 +00:00
|
|
|
if ( $row->af_throttled
|
2016-06-03 18:01:56 +00:00
|
|
|
&& !empty( $wgAbuseFilterRestrictions[$row->afa_consequence] )
|
2015-09-28 18:03:35 +00:00
|
|
|
) {
|
2019-02-16 14:21:33 +00:00
|
|
|
// Don't do the action, just log
|
|
|
|
$logger = LoggerFactory::getInstance( 'AbuseFilter' );
|
|
|
|
$logger->info(
|
|
|
|
'Filter {filter_id} is throttled, skipping action: {action}',
|
|
|
|
[
|
|
|
|
'filter_id' => $row->af_id,
|
|
|
|
'action' => $row->afa_consequence
|
|
|
|
]
|
|
|
|
);
|
2018-08-26 08:34:42 +00:00
|
|
|
} elseif ( $row->afa_filter !== $row->af_id ) {
|
2018-06-26 13:25:03 +00:00
|
|
|
// We probably got a NULL, as it's a LEFT JOIN. Don't add it.
|
2009-01-27 20:18:58 +00:00
|
|
|
} else {
|
2017-06-15 14:23:34 +00:00
|
|
|
$actionsByFilter[$prefix . $row->afa_filter][$row->afa_consequence] = [
|
2009-02-07 09:34:11 +00:00
|
|
|
'action' => $row->afa_consequence,
|
2018-04-04 11:46:58 +00:00
|
|
|
'parameters' => array_filter( explode( "\n", $row->afa_parameters ) )
|
2017-06-15 14:23:34 +00:00
|
|
|
];
|
2009-01-27 20:18:58 +00:00
|
|
|
}
|
2009-01-23 19:23:19 +00:00
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2009-03-30 06:12:12 +00:00
|
|
|
return $actionsByFilter;
|
|
|
|
}
|
|
|
|
|
2012-03-11 20:40:04 +00:00
|
|
|
/**
|
2017-01-02 11:41:29 +00:00
|
|
|
* @param AbuseFilterVariableHolder $vars
|
|
|
|
* @param Title $title
|
2014-11-07 12:21:45 +00:00
|
|
|
* @param string $group The filter's group (as defined in $wgAbuseFilterValidGroups)
|
2018-09-13 12:00:53 +00:00
|
|
|
* @param User $user The user performing the action
|
2013-01-08 14:52:49 +00:00
|
|
|
* @return Status
|
2019-08-04 15:39:13 +00:00
|
|
|
* @deprecated Since 1.34 Build an AbuseFilterRunner instance and call run() on that.
|
2012-03-11 20:40:04 +00:00
|
|
|
*/
|
2016-06-13 11:53:50 +00:00
|
|
|
public static function filterAction(
|
Add a new class for methods related to running filters
Currently we strongly abuse (pardon the pun) the AbuseFilter class: its
purpose should be to hold static functions intended as generic utility
functions (e.g. to format messages, determine whether a filter is global
etc.), but we actually use it for all methods related to running filters.
This patch creates a new class, AbuseFilterRunner, containing all such
methods, which have been made non-static. This leads to several
improvements (also for related methods and the parser), and opens the
way to further improve the code.
Aside from making the code prettier, less global and easier to test,
this patch could also produce a performance improvement, although I
don't have tools to measure that.
Also note that many public methods have been removed, and almost any of
them has been made protected; a couple of them (the ones used from outside)
are left for back-compat, and will be removed in the future.
Change-Id: I2eab2e50356eeb5224446ee2d0df9c787ae95b80
2018-12-07 17:46:02 +00:00
|
|
|
AbuseFilterVariableHolder $vars, Title $title, $group, User $user
|
2016-06-13 11:53:50 +00:00
|
|
|
) {
|
Add a new class for methods related to running filters
Currently we strongly abuse (pardon the pun) the AbuseFilter class: its
purpose should be to hold static functions intended as generic utility
functions (e.g. to format messages, determine whether a filter is global
etc.), but we actually use it for all methods related to running filters.
This patch creates a new class, AbuseFilterRunner, containing all such
methods, which have been made non-static. This leads to several
improvements (also for related methods and the parser), and opens the
way to further improve the code.
Aside from making the code prettier, less global and easier to test,
this patch could also produce a performance improvement, although I
don't have tools to measure that.
Also note that many public methods have been removed, and almost any of
them has been made protected; a couple of them (the ones used from outside)
are left for back-compat, and will be removed in the future.
Change-Id: I2eab2e50356eeb5224446ee2d0df9c787ae95b80
2018-12-07 17:46:02 +00:00
|
|
|
$runner = new AbuseFilterRunner( $user, $title, $vars, $group );
|
|
|
|
return $runner->run();
|
2009-01-23 19:23:19 +00:00
|
|
|
}
|
|
|
|
|
2016-06-28 22:50:38 +00:00
|
|
|
/**
|
2019-02-06 12:59:34 +00:00
|
|
|
* @param string $filter Filter ID (integer or "<GLOBAL_FILTER_PREFIX><integer>")
|
2018-12-12 11:04:21 +00:00
|
|
|
* @return stdClass|null DB row on success, null on failure
|
2016-06-28 22:50:38 +00:00
|
|
|
*/
|
2019-02-06 12:59:34 +00:00
|
|
|
public static function getFilter( $filter ) {
|
2016-06-28 22:50:38 +00:00
|
|
|
global $wgAbuseFilterCentralDB;
|
|
|
|
|
2019-02-06 12:59:34 +00:00
|
|
|
if ( !isset( self::$filterCache[$filter] ) ) {
|
2019-06-17 10:54:00 +00:00
|
|
|
list( $filterID, $global ) = self::splitGlobalName( $filter );
|
2019-02-06 12:59:34 +00:00
|
|
|
if ( $global ) {
|
2016-06-28 22:50:38 +00:00
|
|
|
// Global wiki filter
|
|
|
|
if ( !$wgAbuseFilterCentralDB ) {
|
2018-04-04 21:14:25 +00:00
|
|
|
return null;
|
2016-06-28 22:50:38 +00:00
|
|
|
}
|
2019-06-27 22:55:20 +00:00
|
|
|
$dbr = self::getCentralDB( DB_REPLICA );
|
2016-06-28 22:50:38 +00:00
|
|
|
} else {
|
|
|
|
// Local wiki filter
|
2017-08-30 02:51:39 +00:00
|
|
|
$dbr = wfGetDB( DB_REPLICA );
|
2016-06-28 22:50:38 +00:00
|
|
|
}
|
|
|
|
|
2018-06-26 13:25:03 +00:00
|
|
|
$row = $dbr->selectRow(
|
|
|
|
'abuse_filter',
|
2019-10-03 12:13:26 +00:00
|
|
|
self::ALL_ABUSE_FILTER_FIELDS,
|
2019-06-17 10:54:00 +00:00
|
|
|
[ 'af_id' => $filterID ],
|
2018-06-26 13:25:03 +00:00
|
|
|
__METHOD__
|
|
|
|
);
|
2019-02-06 12:59:34 +00:00
|
|
|
self::$filterCache[$filter] = $row ?: null;
|
2016-06-28 22:50:38 +00:00
|
|
|
}
|
|
|
|
|
2019-02-06 12:59:34 +00:00
|
|
|
return self::$filterCache[$filter];
|
2016-06-28 22:50:38 +00:00
|
|
|
}
|
|
|
|
|
2019-08-11 12:29:37 +00:00
|
|
|
/**
|
|
|
|
* Checks whether the given object represents a full abuse_filter DB row
|
|
|
|
* @param stdClass $row
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public static function isFullAbuseFilterRow( stdClass $row ) {
|
|
|
|
$actual = array_keys( get_object_vars( $row ) );
|
|
|
|
|
|
|
|
if (
|
|
|
|
count( $actual ) !== count( self::ALL_ABUSE_FILTER_FIELDS )
|
|
|
|
|| array_diff( self::ALL_ABUSE_FILTER_FIELDS, $actual )
|
|
|
|
) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-01-26 13:23:07 +00:00
|
|
|
/**
|
|
|
|
* Saves an abuse_filter row in cache
|
|
|
|
* @param string $id Filter ID (integer or "<GLOBAL_FILTER_PREFIX><integer>")
|
|
|
|
* @param stdClass $row A full abuse_filter row to save
|
|
|
|
* @throws UnexpectedValueException if the row is not full
|
|
|
|
*/
|
|
|
|
public static function cacheFilter( $id, $row ) {
|
|
|
|
// Check that all fields have been passed, otherwise using self::getFilter for this
|
|
|
|
// row will return partial data.
|
2019-08-11 12:29:37 +00:00
|
|
|
if ( !self::isFullAbuseFilterRow( $row ) ) {
|
2019-01-26 13:23:07 +00:00
|
|
|
throw new UnexpectedValueException( 'The specified row must be a full abuse_filter row.' );
|
|
|
|
}
|
|
|
|
self::$filterCache[$id] = $row;
|
|
|
|
}
|
|
|
|
|
2009-10-07 13:57:06 +00:00
|
|
|
/**
|
|
|
|
* Store a var dump to External Storage or the text table
|
|
|
|
* Some of this code is stolen from Revision::insertOn and friends
|
2011-08-24 22:11:52 +00:00
|
|
|
*
|
2017-01-02 11:41:29 +00:00
|
|
|
* @param AbuseFilterVariableHolder $vars
|
|
|
|
* @param bool $global
|
2011-08-24 22:11:52 +00:00
|
|
|
*
|
2019-01-06 14:20:10 +00:00
|
|
|
* @return int The insert ID.
|
2009-10-07 13:57:06 +00:00
|
|
|
*/
|
2018-10-17 05:15:21 +00:00
|
|
|
public static function storeVarDump( AbuseFilterVariableHolder $vars, $global = false ) {
|
2010-08-19 21:12:09 +00:00
|
|
|
global $wgCompressRevisions;
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2013-01-07 00:02:41 +00:00
|
|
|
// Get all variables yet set and compute old and new wikitext if not yet done
|
|
|
|
// as those are needed for the diff view on top of the abuse log pages
|
2017-06-15 14:23:34 +00:00
|
|
|
$vars = $vars->dumpAllVars( [ 'old_wikitext', 'new_wikitext' ] );
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2013-01-07 00:02:41 +00:00
|
|
|
// Vars is an array with native PHP data types (non-objects) now
|
2019-01-06 14:20:10 +00:00
|
|
|
$text = FormatJson::encode( $vars );
|
|
|
|
$flags = [ 'utf-8' ];
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2018-10-03 15:55:06 +00:00
|
|
|
if ( $wgCompressRevisions && function_exists( 'gzdeflate' ) ) {
|
|
|
|
$text = gzdeflate( $text );
|
|
|
|
$flags[] = 'gzip';
|
2009-02-27 03:06:19 +00:00
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2018-06-26 13:25:03 +00:00
|
|
|
// Store to ExternalStore if applicable
|
2009-03-30 06:12:12 +00:00
|
|
|
global $wgDefaultExternalStore, $wgAbuseFilterCentralDB;
|
2009-10-07 13:57:06 +00:00
|
|
|
if ( $wgDefaultExternalStore ) {
|
2010-08-19 21:12:09 +00:00
|
|
|
if ( $global ) {
|
2009-03-30 06:12:12 +00:00
|
|
|
$text = ExternalStore::insertToForeignDefault( $text, $wgAbuseFilterCentralDB );
|
2010-08-19 21:12:09 +00:00
|
|
|
} else {
|
2009-03-30 06:12:12 +00:00
|
|
|
$text = ExternalStore::insertToDefault( $text );
|
2010-08-19 21:12:09 +00:00
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2019-01-06 14:20:10 +00:00
|
|
|
$flags[] = 'external';
|
2009-02-27 03:06:19 +00:00
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2009-02-27 03:06:19 +00:00
|
|
|
// Store to text table
|
2010-08-19 21:12:09 +00:00
|
|
|
if ( $global ) {
|
2019-06-27 22:55:20 +00:00
|
|
|
$dbw = self::getCentralDB( DB_MASTER );
|
2010-08-19 21:12:09 +00:00
|
|
|
} else {
|
2009-03-30 06:12:12 +00:00
|
|
|
$dbw = wfGetDB( DB_MASTER );
|
2010-08-19 21:12:09 +00:00
|
|
|
}
|
2009-02-27 03:06:19 +00:00
|
|
|
$dbw->insert( 'text',
|
2017-06-15 14:23:34 +00:00
|
|
|
[
|
2015-09-28 18:03:35 +00:00
|
|
|
'old_text' => $text,
|
2009-02-27 03:06:19 +00:00
|
|
|
'old_flags' => implode( ',', $flags ),
|
2017-06-15 14:23:34 +00:00
|
|
|
], __METHOD__
|
2009-02-27 03:06:19 +00:00
|
|
|
);
|
2015-09-28 18:03:35 +00:00
|
|
|
|
2015-02-04 18:25:21 +00:00
|
|
|
return $dbw->insertId();
|
2009-02-27 03:06:19 +00:00
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieve a var dump from External Storage or the text table
|
|
|
|
* Some of this code is stolen from Revision::loadText et al
|
2011-08-24 22:11:52 +00:00
|
|
|
*
|
2017-10-06 18:52:31 +00:00
|
|
|
* @param string $stored_dump
|
2011-08-24 22:11:52 +00:00
|
|
|
*
|
2020-03-03 18:03:02 +00:00
|
|
|
* @return AbuseFilterVariableHolder
|
2009-10-07 13:57:06 +00:00
|
|
|
*/
|
2020-03-03 18:03:02 +00:00
|
|
|
public static function loadVarDump( $stored_dump ) : AbuseFilterVariableHolder {
|
2019-01-06 14:20:10 +00:00
|
|
|
// Backward compatibility for (old) blobs stored in the abuse_filter_log table
|
|
|
|
if ( !is_numeric( $stored_dump ) &&
|
|
|
|
substr( $stored_dump, 0, strlen( 'stored-text:' ) ) !== 'stored-text:' &&
|
|
|
|
substr( $stored_dump, 0, strlen( 'tt:' ) ) !== 'tt:'
|
|
|
|
) {
|
2015-03-21 23:13:02 +00:00
|
|
|
$data = unserialize( $stored_dump );
|
2019-02-24 14:55:19 +00:00
|
|
|
return is_array( $data ) ? AbuseFilterVariableHolder::newFromArray( $data ) : $data;
|
2009-02-27 03:06:19 +00:00
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2019-01-06 14:20:10 +00:00
|
|
|
if ( is_numeric( $stored_dump ) ) {
|
|
|
|
$text_id = (int)$stored_dump;
|
|
|
|
} elseif ( strpos( $stored_dump, 'stored-text:' ) !== false ) {
|
|
|
|
$text_id = (int)str_replace( 'stored-text:', '', $stored_dump );
|
|
|
|
} elseif ( strpos( $stored_dump, 'tt:' ) !== false ) {
|
|
|
|
$text_id = (int)str_replace( 'tt:', '', $stored_dump );
|
|
|
|
} else {
|
|
|
|
throw new LogicException( "Cannot understand format: $stored_dump" );
|
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2017-08-30 02:51:39 +00:00
|
|
|
$dbr = wfGetDB( DB_REPLICA );
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2009-02-27 03:06:19 +00:00
|
|
|
$text_row = $dbr->selectRow(
|
2009-10-07 13:57:06 +00:00
|
|
|
'text',
|
2017-06-15 14:23:34 +00:00
|
|
|
[ 'old_text', 'old_flags' ],
|
|
|
|
[ 'old_id' => $text_id ],
|
2009-10-07 13:57:06 +00:00
|
|
|
__METHOD__
|
|
|
|
);
|
|
|
|
|
|
|
|
if ( !$text_row ) {
|
2019-01-06 14:20:10 +00:00
|
|
|
$logger = LoggerFactory::getInstance( 'AbuseFilter' );
|
|
|
|
$logger->warning( __METHOD__ . ": no text row found for input $stored_dump." );
|
2009-02-27 03:06:19 +00:00
|
|
|
return new AbuseFilterVariableHolder;
|
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2019-01-06 14:20:10 +00:00
|
|
|
$flags = $text_row->old_flags === '' ? [] : explode( ',', $text_row->old_flags );
|
2009-02-27 03:06:19 +00:00
|
|
|
$text = $text_row->old_text;
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2009-02-27 03:06:19 +00:00
|
|
|
if ( in_array( 'external', $flags ) ) {
|
|
|
|
$text = ExternalStore::fetchFromURL( $text );
|
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2009-02-27 03:06:19 +00:00
|
|
|
if ( in_array( 'gzip', $flags ) ) {
|
|
|
|
$text = gzinflate( $text );
|
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2019-01-06 14:20:10 +00:00
|
|
|
$obj = FormatJson::decode( $text, true );
|
|
|
|
if ( $obj === null ) {
|
|
|
|
// Temporary code until all rows will be JSON-encoded
|
|
|
|
$obj = unserialize( $text );
|
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2019-01-06 14:20:10 +00:00
|
|
|
if ( in_array( 'nativeDataArray', $flags ) ||
|
|
|
|
// Temporary condition: we don't add the flag anymore, but the updateVarDump
|
|
|
|
// script could be still running and we cannot assume that this branch is the default.
|
|
|
|
( is_array( $obj ) && array_key_exists( 'action', $obj ) )
|
|
|
|
) {
|
2013-01-07 00:02:41 +00:00
|
|
|
$vars = $obj;
|
2019-02-24 14:55:19 +00:00
|
|
|
$obj = AbuseFilterVariableHolder::newFromArray( $vars );
|
Rewrite the VariableHolder code to translate deprecated variables
The current code was more of a subpar, temporary solution. However, we
need a stable solution in case more variables will be deprecated in the
future (T213006 fixes the problem for the past deprecation round). So,
instead of setting a hacky property, directly translate all variables
when loading the var dump. This is not only stable, but has a couple
micro-performance advantages:
- Calling getDeprecatedVariables happens only once when loading the
dump, and not every time a variable is accessed
- No checks are needed when retrieving a variable,
because names can always assumed to be new
Some simple benchmarks reveals a runtime reduction of 8-15% compared to
the old code (8% when it had varsVersion = 2, 15% for varsVersion = 1),
which comes at no cost together with increased readability and
stability. It ain't much, but it's honest work.
Change-Id: Ib32a92c4ad939790633aa63eb3ef8d4629488bea
2020-09-21 11:54:23 +00:00
|
|
|
$obj->translateDeprecatedVars();
|
2013-01-07 00:02:41 +00:00
|
|
|
}
|
|
|
|
|
2009-02-27 03:06:19 +00:00
|
|
|
return $obj;
|
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2019-01-01 15:35:01 +00:00
|
|
|
/**
|
|
|
|
* Get an identifier for the given action to be used in self::$tagsToSet
|
|
|
|
*
|
|
|
|
* @param string $action The name of the current action, as used by AbuseFilter (e.g. 'edit'
|
|
|
|
* or 'createaccount')
|
|
|
|
* @param Title $title The title where the current action is executed on. This is the user page
|
|
|
|
* for account creations.
|
|
|
|
* @param string $username Of the user executing the action (as returned by User::getName()).
|
|
|
|
* For account creation, this is the name of the new account.
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public static function getTaggingActionId( $action, Title $title, $username ) {
|
|
|
|
return implode(
|
|
|
|
'-',
|
|
|
|
[
|
|
|
|
$title->getPrefixedText(),
|
|
|
|
$username,
|
|
|
|
$action
|
|
|
|
]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2016-06-28 22:50:38 +00:00
|
|
|
/**
|
|
|
|
* @param array[] $tagsByAction Map of (integer => string[])
|
|
|
|
*/
|
Add a new class for methods related to running filters
Currently we strongly abuse (pardon the pun) the AbuseFilter class: its
purpose should be to hold static functions intended as generic utility
functions (e.g. to format messages, determine whether a filter is global
etc.), but we actually use it for all methods related to running filters.
This patch creates a new class, AbuseFilterRunner, containing all such
methods, which have been made non-static. This leads to several
improvements (also for related methods and the parser), and opens the
way to further improve the code.
Aside from making the code prettier, less global and easier to test,
this patch could also produce a performance improvement, although I
don't have tools to measure that.
Also note that many public methods have been removed, and almost any of
them has been made protected; a couple of them (the ones used from outside)
are left for back-compat, and will be removed in the future.
Change-Id: I2eab2e50356eeb5224446ee2d0df9c787ae95b80
2018-12-07 17:46:02 +00:00
|
|
|
public static function bufferTagsToSetByAction( array $tagsByAction ) {
|
2019-08-26 13:01:09 +00:00
|
|
|
foreach ( $tagsByAction as $actionID => $tags ) {
|
|
|
|
if ( !isset( self::$tagsToSet[ $actionID ] ) ) {
|
|
|
|
self::$tagsToSet[ $actionID ] = $tags;
|
|
|
|
} else {
|
|
|
|
self::$tagsToSet[ $actionID ] = array_unique(
|
|
|
|
array_merge( self::$tagsToSet[ $actionID ], $tags )
|
|
|
|
);
|
2016-06-28 22:50:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-08-03 21:55:35 +00:00
|
|
|
/**
|
2014-11-07 12:21:45 +00:00
|
|
|
* @param string $group The filter's group (as defined in $wgAbuseFilterValidGroups)
|
2017-01-02 11:41:29 +00:00
|
|
|
* @return string
|
2012-08-03 21:55:35 +00:00
|
|
|
*/
|
|
|
|
public static function getGlobalRulesKey( $group ) {
|
|
|
|
global $wgAbuseFilterIsCentral, $wgAbuseFilterCentralDB;
|
|
|
|
|
2018-10-21 07:40:57 +00:00
|
|
|
$cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
|
2012-08-03 21:55:35 +00:00
|
|
|
if ( !$wgAbuseFilterIsCentral ) {
|
2018-10-21 07:40:57 +00:00
|
|
|
return $cache->makeGlobalKey( 'abusefilter', 'rules', $wgAbuseFilterCentralDB, $group );
|
2012-08-03 21:55:35 +00:00
|
|
|
}
|
|
|
|
|
2018-10-21 07:40:57 +00:00
|
|
|
return $cache->makeKey( 'abusefilter', 'rules', $group );
|
2012-08-03 21:55:35 +00:00
|
|
|
}
|
|
|
|
|
2019-07-06 23:35:03 +00:00
|
|
|
/**
|
|
|
|
* Gets the autopromotion block status for the given user
|
|
|
|
*
|
|
|
|
* @param User $target
|
|
|
|
* @return int
|
|
|
|
*/
|
|
|
|
public static function getAutoPromoteBlockStatus( User $target ) {
|
|
|
|
$store = ObjectCache::getInstance( 'db-replicated' );
|
|
|
|
|
|
|
|
return (int)$store->get( self::autoPromoteBlockKey( $store, $target ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Blocks autopromotion for the given user
|
|
|
|
*
|
|
|
|
* @param User $target
|
|
|
|
* @param string $msg The message to show in the log
|
2019-09-15 04:09:02 +00:00
|
|
|
* @param int $duration Duration for which autopromotion is blocked, in seconds
|
2019-07-06 23:35:03 +00:00
|
|
|
* @return bool True on success, false on failure
|
|
|
|
*/
|
2019-09-15 04:09:02 +00:00
|
|
|
public static function blockAutoPromote( User $target, $msg, int $duration ) {
|
2019-07-06 23:35:03 +00:00
|
|
|
$store = ObjectCache::getInstance( 'db-replicated' );
|
2019-08-11 18:34:50 +00:00
|
|
|
if ( !$store->set(
|
|
|
|
self::autoPromoteBlockKey( $store, $target ),
|
|
|
|
1,
|
2019-09-15 04:09:02 +00:00
|
|
|
$duration
|
2019-08-11 18:34:50 +00:00
|
|
|
) ) {
|
2019-07-06 23:35:03 +00:00
|
|
|
// Failed to set key
|
2019-08-11 18:34:50 +00:00
|
|
|
$logger = LoggerFactory::getInstance( 'AbuseFilter' );
|
|
|
|
$logger->warning(
|
|
|
|
"Failed to block autopromotion for $target. Error: " . $store->getLastError()
|
|
|
|
);
|
2019-07-06 23:35:03 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$logEntry = new ManualLogEntry( 'rights', 'blockautopromote' );
|
2019-08-11 18:34:50 +00:00
|
|
|
$performer = self::getFilterUser();
|
2019-07-06 23:35:03 +00:00
|
|
|
$logEntry->setPerformer( $performer );
|
|
|
|
$logEntry->setTarget( $target->getUserPage() );
|
2019-08-11 18:34:50 +00:00
|
|
|
|
2019-07-06 23:35:03 +00:00
|
|
|
$logEntry->setParameters( [
|
2019-09-15 04:09:02 +00:00
|
|
|
'7::duration' => $duration,
|
2019-08-11 18:34:50 +00:00
|
|
|
// These parameters are unused in our message, but some parts of the code check for them
|
2019-07-06 23:35:03 +00:00
|
|
|
'4::oldgroups' => [],
|
|
|
|
'5::newgroups' => []
|
|
|
|
] );
|
|
|
|
$logEntry->setComment( $msg );
|
|
|
|
$logEntry->publish( $logEntry->insert() );
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-07-16 12:10:36 +00:00
|
|
|
/**
|
|
|
|
* Unblocks autopromotion for the given user
|
|
|
|
*
|
|
|
|
* @param User $target
|
|
|
|
* @param User $performer
|
|
|
|
* @param string $msg The message to show in the log
|
|
|
|
* @return bool True on success, false on failure
|
|
|
|
*/
|
|
|
|
public static function unblockAutopromote( User $target, User $performer, $msg ) {
|
2019-07-06 23:35:03 +00:00
|
|
|
// Immediately expire (delete) the key, failing if it does not exist
|
|
|
|
$store = ObjectCache::getInstance( 'db-replicated' );
|
|
|
|
$expireAt = time() - $store::TTL_HOUR;
|
|
|
|
if ( !$store->changeTTL( self::autoPromoteBlockKey( $store, $target ), $expireAt ) ) {
|
|
|
|
// Key did not exist to begin with; nothing to do
|
2018-07-16 12:10:36 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$logEntry = new ManualLogEntry( 'rights', 'restoreautopromote' );
|
|
|
|
$logEntry->setTarget( Title::makeTitle( NS_USER, $target->getName() ) );
|
|
|
|
$logEntry->setComment( $msg );
|
|
|
|
// These parameters are unused in our message, but some parts of the code check for them
|
|
|
|
$logEntry->setParameters( [
|
|
|
|
'4::oldgroups' => [],
|
|
|
|
'5::newgroups' => []
|
|
|
|
] );
|
|
|
|
$logEntry->setPerformer( $performer );
|
|
|
|
$logEntry->publish( $logEntry->insert() );
|
2019-07-06 23:35:03 +00:00
|
|
|
|
2018-07-16 12:10:36 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2012-03-11 20:40:04 +00:00
|
|
|
/**
|
2019-07-06 23:35:03 +00:00
|
|
|
* @param BagOStuff $store
|
|
|
|
* @param User $target
|
2017-01-02 11:41:29 +00:00
|
|
|
* @return string
|
2019-08-10 13:17:20 +00:00
|
|
|
* @internal
|
2012-03-11 20:40:04 +00:00
|
|
|
*/
|
2019-08-10 13:17:20 +00:00
|
|
|
public static function autoPromoteBlockKey( BagOStuff $store, User $target ) {
|
2019-07-06 23:35:03 +00:00
|
|
|
return $store->makeKey( 'abusefilter', 'block-autopromote', $target->getId() );
|
2008-06-27 06:18:51 +00:00
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2012-10-23 13:14:34 +00:00
|
|
|
/**
|
2018-12-10 16:56:02 +00:00
|
|
|
* @param string $type The value to get, either "threshold", "count" or "age"
|
2014-11-07 12:21:45 +00:00
|
|
|
* @param string $group The filter's group (as defined in $wgAbuseFilterValidGroups)
|
2012-10-23 13:14:34 +00:00
|
|
|
* @return mixed
|
|
|
|
*/
|
2018-12-10 16:56:02 +00:00
|
|
|
public static function getEmergencyValue( $type, $group ) {
|
|
|
|
switch ( $type ) {
|
|
|
|
case 'threshold':
|
|
|
|
global $wgAbuseFilterEmergencyDisableThreshold;
|
|
|
|
$value = $wgAbuseFilterEmergencyDisableThreshold;
|
|
|
|
break;
|
|
|
|
case 'count':
|
|
|
|
global $wgAbuseFilterEmergencyDisableCount;
|
|
|
|
$value = $wgAbuseFilterEmergencyDisableCount;
|
|
|
|
break;
|
|
|
|
case 'age':
|
|
|
|
global $wgAbuseFilterEmergencyDisableAge;
|
|
|
|
$value = $wgAbuseFilterEmergencyDisableAge;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw new InvalidArgumentException( '$type must be either "threshold", "count" or "age"' );
|
|
|
|
}
|
|
|
|
|
|
|
|
return $value[$group] ?? $value['default'];
|
2012-10-23 13:14:34 +00:00
|
|
|
}
|
|
|
|
|
2019-02-10 10:01:39 +00:00
|
|
|
/**
|
|
|
|
* Get the memcache access key used to store per-filter profiling data.
|
|
|
|
*
|
|
|
|
* @param string|int $filter
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public static function filterProfileKey( $filter ) {
|
|
|
|
$cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
|
2019-02-10 12:08:06 +00:00
|
|
|
return $cache->makeKey( 'abusefilter-profile', 'v3', $filter );
|
2019-02-10 10:01:39 +00:00
|
|
|
}
|
|
|
|
|
2012-03-11 20:40:04 +00:00
|
|
|
/**
|
2015-04-01 04:51:16 +00:00
|
|
|
* Memcache access key used to store overall profiling data for rule groups
|
|
|
|
*
|
|
|
|
* @param string $group
|
2017-01-02 11:41:29 +00:00
|
|
|
* @return string
|
2012-03-11 20:40:04 +00:00
|
|
|
*/
|
2015-04-01 04:51:16 +00:00
|
|
|
public static function filterProfileGroupKey( $group ) {
|
|
|
|
$cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
|
|
|
|
return $cache->makeKey( 'abusefilter-profile', 'group', $group );
|
2008-07-17 02:43:45 +00:00
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2012-03-11 20:40:04 +00:00
|
|
|
/**
|
|
|
|
* @return User
|
|
|
|
*/
|
2019-12-07 17:20:10 +00:00
|
|
|
public static function getFilterUser() : User {
|
2015-09-17 15:31:51 +00:00
|
|
|
$username = wfMessage( 'abusefilter-blocker' )->inContentLanguage()->text();
|
2017-08-08 12:03:56 +00:00
|
|
|
$user = User::newSystemUser( $username, [ 'steal' => true ] );
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2018-12-17 15:02:24 +00:00
|
|
|
if ( !$user ) {
|
|
|
|
// User name is invalid. Don't throw because this is a system message, easy
|
|
|
|
// to change and make wrong either by mistake or intentionally to break the site.
|
|
|
|
wfWarn(
|
|
|
|
'The AbuseFilter user\'s name is invalid. Please change it in ' .
|
|
|
|
'MediaWiki:abusefilter-blocker'
|
|
|
|
);
|
|
|
|
// Use the default name to avoid breaking other stuff. This should have no harm,
|
|
|
|
// aside from blocks temporarily attributed to another user.
|
|
|
|
$defaultName = wfMessage( 'abusefilter-blocker' )->inLanguage( 'en' )->text();
|
|
|
|
$user = User::newSystemUser( $defaultName, [ 'steal' => true ] );
|
|
|
|
}
|
2019-12-07 17:20:10 +00:00
|
|
|
'@phan-var User $user';
|
2018-12-17 15:02:24 +00:00
|
|
|
|
2017-04-16 07:25:47 +00:00
|
|
|
// Promote user to 'sysop' so it doesn't look
|
|
|
|
// like an unprivileged account is blocking users
|
|
|
|
if ( !in_array( 'sysop', $user->getGroups() ) ) {
|
|
|
|
$user->addGroup( 'sysop' );
|
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2008-07-09 07:02:13 +00:00
|
|
|
return $user;
|
|
|
|
}
|
2009-01-23 22:49:13 +00:00
|
|
|
|
2018-03-30 06:55:03 +00:00
|
|
|
/**
|
|
|
|
* Extract values for syntax highlight
|
|
|
|
*
|
|
|
|
* @param bool $canEdit
|
|
|
|
* @return array
|
|
|
|
*/
|
2020-01-15 16:08:53 +00:00
|
|
|
public static function getAceConfig( bool $canEdit ): array {
|
|
|
|
$keywordsManager = AbuseFilterServices::getKeywordsManager();
|
|
|
|
$values = $keywordsManager->getBuilderValues();
|
|
|
|
$deprecatedVars = $keywordsManager->getDeprecatedVariables();
|
2018-06-27 18:05:52 +00:00
|
|
|
|
|
|
|
$builderVariables = implode( '|', array_keys( $values['vars'] ) );
|
2019-11-16 15:32:36 +00:00
|
|
|
$builderFunctions = implode( '|', array_keys( AbuseFilterParser::FUNCTIONS ) );
|
|
|
|
// AbuseFilterTokenizer::KEYWORDS also includes constants (true, false and null),
|
2018-04-05 13:58:03 +00:00
|
|
|
// but Ace redefines these constants afterwards so this will not be an issue
|
2019-11-16 15:32:36 +00:00
|
|
|
$builderKeywords = implode( '|', AbuseFilterTokenizer::KEYWORDS );
|
2018-06-27 18:05:52 +00:00
|
|
|
// Extract operators from tokenizer like we do in AbuseFilterParserTest
|
|
|
|
$operators = implode( '|', array_map( function ( $op ) {
|
|
|
|
return preg_quote( $op, '/' );
|
2019-11-16 15:32:36 +00:00
|
|
|
}, AbuseFilterTokenizer::OPERATORS ) );
|
2018-06-27 18:05:52 +00:00
|
|
|
$deprecatedVariables = implode( '|', array_keys( $deprecatedVars ) );
|
2020-01-15 16:08:53 +00:00
|
|
|
$disabledVariables = implode( '|', array_keys( $keywordsManager->getDisabledVariables() ) );
|
2018-04-05 13:58:03 +00:00
|
|
|
|
2018-03-30 06:55:03 +00:00
|
|
|
return [
|
|
|
|
'variables' => $builderVariables,
|
|
|
|
'functions' => $builderFunctions,
|
2018-04-05 13:58:03 +00:00
|
|
|
'keywords' => $builderKeywords,
|
2018-06-27 18:05:52 +00:00
|
|
|
'operators' => $operators,
|
|
|
|
'deprecated' => $deprecatedVariables,
|
|
|
|
'disabled' => $disabledVariables,
|
2018-03-30 06:55:03 +00:00
|
|
|
'aceReadOnly' => !$canEdit
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
2018-05-02 19:24:46 +00:00
|
|
|
/**
|
|
|
|
* Check whether a filter is allowed to use a tag
|
|
|
|
*
|
|
|
|
* @param string $tag Tag name
|
|
|
|
* @return Status
|
|
|
|
*/
|
2018-07-06 14:43:12 +00:00
|
|
|
public static function isAllowedTag( $tag ) {
|
2018-05-02 19:24:46 +00:00
|
|
|
$tagNameStatus = ChangeTags::isTagNameValid( $tag );
|
|
|
|
|
|
|
|
if ( !$tagNameStatus->isGood() ) {
|
|
|
|
return $tagNameStatus;
|
|
|
|
}
|
|
|
|
|
|
|
|
$finalStatus = Status::newGood();
|
|
|
|
|
|
|
|
$canAddStatus =
|
|
|
|
ChangeTags::canAddTagsAccompanyingChange(
|
|
|
|
[ $tag ]
|
|
|
|
);
|
|
|
|
|
|
|
|
if ( $canAddStatus->isGood() ) {
|
|
|
|
return $finalStatus;
|
|
|
|
}
|
|
|
|
|
2018-07-06 14:43:12 +00:00
|
|
|
if ( $tag === 'abusefilter-condition-limit' ) {
|
2018-07-09 23:00:59 +00:00
|
|
|
$finalStatus->fatal( 'abusefilter-tag-reserved' );
|
2018-07-06 14:43:12 +00:00
|
|
|
return $finalStatus;
|
|
|
|
}
|
|
|
|
|
2018-05-02 19:24:46 +00:00
|
|
|
$alreadyDefinedTags = [];
|
|
|
|
AbuseFilterHooks::onListDefinedTags( $alreadyDefinedTags );
|
|
|
|
|
|
|
|
if ( in_array( $tag, $alreadyDefinedTags, true ) ) {
|
|
|
|
return $finalStatus;
|
|
|
|
}
|
|
|
|
|
|
|
|
$canCreateTagStatus = ChangeTags::canCreateTag( $tag );
|
|
|
|
if ( $canCreateTagStatus->isGood() ) {
|
|
|
|
return $finalStatus;
|
|
|
|
}
|
|
|
|
|
|
|
|
$finalStatus->fatal( 'abusefilter-edit-bad-tags' );
|
|
|
|
return $finalStatus;
|
|
|
|
}
|
|
|
|
|
2018-09-09 10:14:31 +00:00
|
|
|
/**
|
|
|
|
* Validate throttle parameters
|
|
|
|
*
|
|
|
|
* @param array $params Throttle parameters
|
|
|
|
* @return null|string Null on success, a string with the error message on failure
|
|
|
|
*/
|
|
|
|
public static function checkThrottleParameters( $params ) {
|
2019-05-15 14:14:12 +00:00
|
|
|
list( $throttleCount, $throttlePeriod ) = explode( ',', $params[1], 2 );
|
2018-09-09 10:14:31 +00:00
|
|
|
$throttleGroups = array_slice( $params, 2 );
|
|
|
|
$validGroups = [
|
|
|
|
'ip',
|
|
|
|
'user',
|
|
|
|
'range',
|
|
|
|
'creationdate',
|
|
|
|
'editcount',
|
|
|
|
'site',
|
|
|
|
'page'
|
|
|
|
];
|
|
|
|
|
|
|
|
$error = null;
|
|
|
|
if ( preg_match( '/^[1-9][0-9]*$/', $throttleCount ) === 0 ) {
|
|
|
|
$error = 'abusefilter-edit-invalid-throttlecount';
|
|
|
|
} elseif ( preg_match( '/^[1-9][0-9]*$/', $throttlePeriod ) === 0 ) {
|
|
|
|
$error = 'abusefilter-edit-invalid-throttleperiod';
|
|
|
|
} elseif ( !$throttleGroups ) {
|
|
|
|
$error = 'abusefilter-edit-empty-throttlegroups';
|
|
|
|
} else {
|
|
|
|
$valid = true;
|
|
|
|
// Groups should be unique in three ways: no direct duplicates like 'user' and 'user',
|
|
|
|
// no duplicated subgroups, not even shuffled ('ip,user' and 'user,ip') and no duplicates
|
|
|
|
// within subgroups ('user,ip,user')
|
|
|
|
$uniqueGroups = [];
|
|
|
|
$uniqueSubGroups = true;
|
|
|
|
// Every group should be valid, and subgroups should have valid groups inside
|
|
|
|
foreach ( $throttleGroups as $group ) {
|
|
|
|
if ( strpos( $group, ',' ) !== false ) {
|
|
|
|
$subGroups = explode( ',', $group );
|
|
|
|
if ( $subGroups !== array_unique( $subGroups ) ) {
|
|
|
|
$uniqueSubGroups = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
foreach ( $subGroups as $subGroup ) {
|
|
|
|
if ( !in_array( $subGroup, $validGroups ) ) {
|
|
|
|
$valid = false;
|
|
|
|
break 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sort( $subGroups );
|
|
|
|
$uniqueGroups[] = implode( ',', $subGroups );
|
|
|
|
} else {
|
|
|
|
if ( !in_array( $group, $validGroups ) ) {
|
|
|
|
$valid = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
$uniqueGroups[] = $group;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !$valid ) {
|
|
|
|
$error = 'abusefilter-edit-invalid-throttlegroups';
|
|
|
|
} elseif ( !$uniqueSubGroups || $uniqueGroups !== array_unique( $uniqueGroups ) ) {
|
|
|
|
$error = 'abusefilter-edit-duplicated-throttlegroups';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $error;
|
|
|
|
}
|
|
|
|
|
2018-05-02 19:24:46 +00:00
|
|
|
/**
|
2019-03-01 14:24:30 +00:00
|
|
|
* Checks whether user input for the filter editing form is valid and if so saves the filter.
|
|
|
|
* Returns a Status object which can be:
|
|
|
|
* - Good with [ new_filter_id, history_id ] as value if the filter was successfully saved
|
|
|
|
* - Good with value = false if everything went fine but the filter is unchanged
|
|
|
|
* - OK with errors if a validation error occurred
|
|
|
|
* - Fatal in case of a permission-related error
|
2018-05-02 19:24:46 +00:00
|
|
|
*
|
2019-08-27 14:07:52 +00:00
|
|
|
* @param ContextSource $context
|
2018-05-02 19:24:46 +00:00
|
|
|
* @param int|string $filter
|
|
|
|
* @param stdClass $newRow
|
|
|
|
* @param array $actions
|
2019-08-26 13:01:09 +00:00
|
|
|
* @param IDatabase $dbw DB_MASTER Where the filter should be saved
|
2018-05-02 19:24:46 +00:00
|
|
|
* @return Status
|
|
|
|
*/
|
2019-08-26 13:01:09 +00:00
|
|
|
public static function saveFilter(
|
2019-08-27 14:07:52 +00:00
|
|
|
ContextSource $context,
|
2019-08-26 13:01:09 +00:00
|
|
|
$filter,
|
|
|
|
$newRow,
|
|
|
|
$actions,
|
|
|
|
IDatabase $dbw
|
|
|
|
) {
|
2018-05-02 19:24:46 +00:00
|
|
|
$validationStatus = Status::newGood();
|
2019-08-27 14:07:52 +00:00
|
|
|
$request = $context->getRequest();
|
|
|
|
$user = $context->getUser();
|
2018-05-02 19:24:46 +00:00
|
|
|
|
2018-06-26 13:25:03 +00:00
|
|
|
// Check the syntax
|
2020-09-19 22:30:14 +00:00
|
|
|
$syntaxerr = self::getDefaultParser()->checkSyntax( $request->getVal( 'wpFilterRules' ) );
|
2018-05-02 19:24:46 +00:00
|
|
|
if ( $syntaxerr !== true ) {
|
|
|
|
$validationStatus->error( 'abusefilter-edit-badsyntax', $syntaxerr[0] );
|
|
|
|
return $validationStatus;
|
|
|
|
}
|
|
|
|
// Check for missing required fields (title and pattern)
|
|
|
|
$missing = [];
|
|
|
|
if ( !$request->getVal( 'wpFilterRules' ) ||
|
|
|
|
trim( $request->getVal( 'wpFilterRules' ) ) === '' ) {
|
2019-08-27 14:07:52 +00:00
|
|
|
$missing[] = $context->msg( 'abusefilter-edit-field-conditions' )->escaped();
|
2018-05-02 19:24:46 +00:00
|
|
|
}
|
|
|
|
if ( !$request->getVal( 'wpFilterDescription' ) ) {
|
2019-08-27 14:07:52 +00:00
|
|
|
$missing[] = $context->msg( 'abusefilter-edit-field-description' )->escaped();
|
2018-05-02 19:24:46 +00:00
|
|
|
}
|
|
|
|
if ( count( $missing ) !== 0 ) {
|
2019-08-27 14:07:52 +00:00
|
|
|
$missing = $context->getLanguage()->commaList( $missing );
|
2018-05-02 19:24:46 +00:00
|
|
|
$validationStatus->error( 'abusefilter-edit-missingfields', $missing );
|
|
|
|
return $validationStatus;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Don't allow setting as deleted an active filter
|
2018-08-26 08:34:42 +00:00
|
|
|
if ( $request->getCheck( 'wpFilterEnabled' ) && $request->getCheck( 'wpFilterDeleted' ) ) {
|
2018-05-02 19:24:46 +00:00
|
|
|
$validationStatus->error( 'abusefilter-edit-deleting-enabled' );
|
|
|
|
return $validationStatus;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we've activated the 'tag' option, check the arguments for validity.
|
2018-09-27 13:58:55 +00:00
|
|
|
if ( isset( $actions['tag'] ) ) {
|
|
|
|
if ( count( $actions['tag'] ) === 0 ) {
|
2018-09-03 12:03:33 +00:00
|
|
|
$validationStatus->error( 'tags-create-no-name' );
|
|
|
|
return $validationStatus;
|
|
|
|
}
|
2018-09-27 13:58:55 +00:00
|
|
|
foreach ( $actions['tag'] as $tag ) {
|
2018-05-02 19:24:46 +00:00
|
|
|
$status = self::isAllowedTag( $tag );
|
|
|
|
|
|
|
|
if ( !$status->isGood() ) {
|
|
|
|
$err = $status->getErrors();
|
|
|
|
$msg = $err[0]['message'];
|
|
|
|
$validationStatus->error( $msg );
|
|
|
|
return $validationStatus;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-03 09:22:41 +00:00
|
|
|
// Warning and disallow message cannot be empty
|
2018-09-27 13:58:55 +00:00
|
|
|
if ( isset( $actions['warn'] ) && $actions['warn'][0] === '' ) {
|
2018-09-03 09:22:41 +00:00
|
|
|
$validationStatus->error( 'abusefilter-edit-invalid-warn-message' );
|
|
|
|
return $validationStatus;
|
2018-09-27 13:58:55 +00:00
|
|
|
} elseif ( isset( $actions['disallow'] ) && $actions['disallow'][0] === '' ) {
|
2018-09-03 09:22:41 +00:00
|
|
|
$validationStatus->error( 'abusefilter-edit-invalid-disallow-message' );
|
|
|
|
return $validationStatus;
|
|
|
|
}
|
|
|
|
|
2018-09-09 10:14:31 +00:00
|
|
|
// If 'throttle' is selected, check its parameters
|
2018-09-27 13:58:55 +00:00
|
|
|
if ( isset( $actions['throttle'] ) ) {
|
|
|
|
$throttleCheck = self::checkThrottleParameters( $actions['throttle'] );
|
2018-09-09 10:14:31 +00:00
|
|
|
if ( $throttleCheck !== null ) {
|
|
|
|
$validationStatus->error( $throttleCheck );
|
|
|
|
return $validationStatus;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-27 14:07:52 +00:00
|
|
|
$availableActions = array_keys(
|
|
|
|
array_filter( $context->getConfig()->get( 'AbuseFilterActions' ) )
|
|
|
|
);
|
2018-05-02 19:24:46 +00:00
|
|
|
$differences = self::compareVersions(
|
|
|
|
[ $newRow, $actions ],
|
2019-08-26 13:01:09 +00:00
|
|
|
[ $newRow->mOriginalRow, $newRow->mOriginalActions ],
|
|
|
|
$availableActions
|
2018-05-02 19:24:46 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
// Don't allow adding a new global rule, or updating a
|
|
|
|
// rule that is currently global, without permissions.
|
2019-08-27 09:40:01 +00:00
|
|
|
if (
|
|
|
|
!self::canEditFilter( $user, $newRow ) ||
|
|
|
|
!self::canEditFilter( $user, $newRow->mOriginalRow )
|
|
|
|
) {
|
2018-05-02 19:24:46 +00:00
|
|
|
$validationStatus->fatal( 'abusefilter-edit-notallowed-global' );
|
|
|
|
return $validationStatus;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Don't allow custom messages on global rules
|
2014-10-04 14:42:46 +00:00
|
|
|
if ( $newRow->af_global == 1 && (
|
|
|
|
$request->getVal( 'wpFilterWarnMessage' ) !== 'abusefilter-warning' ||
|
|
|
|
$request->getVal( 'wpFilterDisallowMessage' ) !== 'abusefilter-disallowed'
|
|
|
|
) ) {
|
2018-05-02 19:24:46 +00:00
|
|
|
$validationStatus->fatal( 'abusefilter-edit-notallowed-global-custom-msg' );
|
|
|
|
return $validationStatus;
|
|
|
|
}
|
|
|
|
|
|
|
|
$origActions = $newRow->mOriginalActions;
|
|
|
|
$wasGlobal = (bool)$newRow->mOriginalRow->af_global;
|
|
|
|
|
|
|
|
unset( $newRow->mOriginalRow );
|
|
|
|
unset( $newRow->mOriginalActions );
|
|
|
|
|
|
|
|
// Check for non-changes
|
|
|
|
if ( !count( $differences ) ) {
|
|
|
|
$validationStatus->setResult( true, false );
|
|
|
|
return $validationStatus;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check for restricted actions
|
2019-08-27 14:07:52 +00:00
|
|
|
$restrictions = $context->getConfig()->get( 'AbuseFilterRestrictions' );
|
2018-05-02 19:24:46 +00:00
|
|
|
if ( count( array_intersect_key(
|
2018-07-05 17:57:30 +00:00
|
|
|
array_filter( $restrictions ),
|
2018-09-27 13:58:55 +00:00
|
|
|
array_merge( $actions, $origActions )
|
2018-05-02 19:24:46 +00:00
|
|
|
) )
|
2019-09-18 21:48:40 +00:00
|
|
|
&& !MediaWikiServices::getInstance()->getPermissionManager()
|
|
|
|
->userHasRight( $user, 'abusefilter-modify-restricted' )
|
2018-05-02 19:24:46 +00:00
|
|
|
) {
|
|
|
|
$validationStatus->error( 'abusefilter-edit-restricted' );
|
|
|
|
return $validationStatus;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Everything went fine, so let's save the filter
|
|
|
|
list( $new_id, $history_id ) =
|
2019-08-27 14:07:52 +00:00
|
|
|
self::doSaveFilter( $newRow, $differences, $filter, $actions, $wasGlobal, $context, $dbw );
|
2018-05-02 19:24:46 +00:00
|
|
|
$validationStatus->setResult( true, [ $new_id, $history_id ] );
|
|
|
|
return $validationStatus;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Saves new filter's info to DB
|
|
|
|
*
|
|
|
|
* @param stdClass $newRow
|
|
|
|
* @param array $differences
|
2019-02-24 05:28:59 +00:00
|
|
|
* @param int|string $filter
|
2018-05-02 19:24:46 +00:00
|
|
|
* @param array $actions
|
|
|
|
* @param bool $wasGlobal
|
2019-08-27 14:07:52 +00:00
|
|
|
* @param ContextSource $context
|
2019-08-26 13:01:09 +00:00
|
|
|
* @param IDatabase $dbw DB_MASTER where the filter will be saved
|
2018-05-02 19:24:46 +00:00
|
|
|
* @return int[] first element is new ID, second is history ID
|
|
|
|
*/
|
|
|
|
private static function doSaveFilter(
|
|
|
|
$newRow,
|
|
|
|
$differences,
|
|
|
|
$filter,
|
|
|
|
$actions,
|
|
|
|
$wasGlobal,
|
2019-08-27 14:07:52 +00:00
|
|
|
ContextSource $context,
|
2019-08-26 13:01:09 +00:00
|
|
|
IDatabase $dbw
|
2018-05-02 19:24:46 +00:00
|
|
|
) {
|
2019-08-27 14:07:52 +00:00
|
|
|
$user = $context->getUser();
|
2018-05-02 19:24:46 +00:00
|
|
|
|
|
|
|
// Convert from object to array
|
|
|
|
$newRow = get_object_vars( $newRow );
|
|
|
|
|
|
|
|
// Set last modifier.
|
2018-11-28 11:31:40 +00:00
|
|
|
$newRow['af_timestamp'] = $dbw->timestamp();
|
2018-07-05 17:57:30 +00:00
|
|
|
$newRow['af_user'] = $user->getId();
|
|
|
|
$newRow['af_user_text'] = $user->getName();
|
2018-05-02 19:24:46 +00:00
|
|
|
|
|
|
|
$dbw->startAtomic( __METHOD__ );
|
|
|
|
|
|
|
|
// Insert MAIN row.
|
2018-08-26 08:34:42 +00:00
|
|
|
if ( $filter === 'new' ) {
|
2019-03-01 14:53:46 +00:00
|
|
|
$new_id = null;
|
2018-05-02 19:24:46 +00:00
|
|
|
$is_new = true;
|
|
|
|
} else {
|
|
|
|
$new_id = $filter;
|
|
|
|
$is_new = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reset throttled marker, if we're re-enabling it.
|
|
|
|
$newRow['af_throttled'] = $newRow['af_throttled'] && !$newRow['af_enabled'];
|
|
|
|
$newRow['af_id'] = $new_id;
|
|
|
|
|
2018-06-26 13:25:03 +00:00
|
|
|
// T67807: integer 1's & 0's might be better understood than booleans
|
2018-05-02 19:24:46 +00:00
|
|
|
$newRow['af_enabled'] = (int)$newRow['af_enabled'];
|
|
|
|
$newRow['af_hidden'] = (int)$newRow['af_hidden'];
|
|
|
|
$newRow['af_throttled'] = (int)$newRow['af_throttled'];
|
|
|
|
$newRow['af_deleted'] = (int)$newRow['af_deleted'];
|
|
|
|
$newRow['af_global'] = (int)$newRow['af_global'];
|
|
|
|
|
2020-04-17 22:10:27 +00:00
|
|
|
$dbw->replace( 'abuse_filter', 'af_id', $newRow, __METHOD__ );
|
2018-05-02 19:24:46 +00:00
|
|
|
|
|
|
|
if ( $is_new ) {
|
|
|
|
$new_id = $dbw->insertId();
|
|
|
|
}
|
2019-12-07 17:20:10 +00:00
|
|
|
'@phan-var int $new_id';
|
2018-05-02 19:24:46 +00:00
|
|
|
|
2019-08-27 14:07:52 +00:00
|
|
|
$availableActions = $context->getConfig()->get( 'AbuseFilterActions' );
|
2018-05-02 19:24:46 +00:00
|
|
|
$actionsRows = [];
|
2018-07-05 17:57:30 +00:00
|
|
|
foreach ( array_filter( $availableActions ) as $action => $_ ) {
|
2018-05-02 19:24:46 +00:00
|
|
|
// Check if it's set
|
2018-09-27 13:58:55 +00:00
|
|
|
$enabled = isset( $actions[$action] );
|
2018-05-02 19:24:46 +00:00
|
|
|
|
|
|
|
if ( $enabled ) {
|
2018-09-27 13:58:55 +00:00
|
|
|
$parameters = $actions[$action];
|
2018-09-05 09:35:07 +00:00
|
|
|
if ( $action === 'throttle' && $parameters[0] === 'new' ) {
|
|
|
|
// FIXME: Do we really need to keep the filter ID inside throttle parameters?
|
|
|
|
// We'd save space, keep things simpler and avoid this hack. Note: if removing
|
|
|
|
// it, a maintenance script will be necessary to clean up the table.
|
|
|
|
$parameters[0] = $new_id;
|
|
|
|
}
|
2018-05-02 19:24:46 +00:00
|
|
|
|
|
|
|
$thisRow = [
|
|
|
|
'afa_filter' => $new_id,
|
|
|
|
'afa_consequence' => $action,
|
|
|
|
'afa_parameters' => implode( "\n", $parameters )
|
|
|
|
];
|
|
|
|
$actionsRows[] = $thisRow;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a history row
|
|
|
|
$afh_row = [];
|
|
|
|
|
2019-11-16 15:32:36 +00:00
|
|
|
foreach ( self::HISTORY_MAPPINGS as $af_col => $afh_col ) {
|
2018-05-02 19:24:46 +00:00
|
|
|
$afh_row[$afh_col] = $newRow[$af_col];
|
|
|
|
}
|
|
|
|
|
2018-09-27 13:58:55 +00:00
|
|
|
$afh_row['afh_actions'] = serialize( $actions );
|
2018-05-02 19:24:46 +00:00
|
|
|
|
|
|
|
$afh_row['afh_changed_fields'] = implode( ',', $differences );
|
|
|
|
|
|
|
|
$flags = [];
|
|
|
|
if ( $newRow['af_hidden'] ) {
|
|
|
|
$flags[] = 'hidden';
|
|
|
|
}
|
|
|
|
if ( $newRow['af_enabled'] ) {
|
|
|
|
$flags[] = 'enabled';
|
|
|
|
}
|
|
|
|
if ( $newRow['af_deleted'] ) {
|
|
|
|
$flags[] = 'deleted';
|
|
|
|
}
|
|
|
|
if ( $newRow['af_global'] ) {
|
|
|
|
$flags[] = 'global';
|
|
|
|
}
|
|
|
|
|
|
|
|
$afh_row['afh_flags'] = implode( ',', $flags );
|
|
|
|
|
|
|
|
$afh_row['afh_filter'] = $new_id;
|
|
|
|
|
|
|
|
// Do the update
|
|
|
|
$dbw->insert( 'abuse_filter_history', $afh_row, __METHOD__ );
|
|
|
|
$history_id = $dbw->insertId();
|
2018-08-26 08:34:42 +00:00
|
|
|
if ( $filter !== 'new' ) {
|
2018-05-02 19:24:46 +00:00
|
|
|
$dbw->delete(
|
|
|
|
'abuse_filter_action',
|
|
|
|
[ 'afa_filter' => $filter ],
|
|
|
|
__METHOD__
|
|
|
|
);
|
|
|
|
}
|
|
|
|
$dbw->insert( 'abuse_filter_action', $actionsRows, __METHOD__ );
|
|
|
|
|
|
|
|
$dbw->endAtomic( __METHOD__ );
|
|
|
|
|
|
|
|
// Invalidate cache if this was a global rule
|
|
|
|
if ( $wasGlobal || $newRow['af_global'] ) {
|
|
|
|
$group = 'default';
|
2018-08-26 08:34:42 +00:00
|
|
|
if ( isset( $newRow['af_group'] ) && $newRow['af_group'] !== '' ) {
|
2018-05-02 19:24:46 +00:00
|
|
|
$group = $newRow['af_group'];
|
|
|
|
}
|
|
|
|
|
|
|
|
$globalRulesKey = self::getGlobalRulesKey( $group );
|
2018-10-18 17:30:15 +00:00
|
|
|
MediaWikiServices::getInstance()->getMainWANObjectCache()->touchCheckKey( $globalRulesKey );
|
2018-05-02 19:24:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Logging
|
|
|
|
$subtype = $filter === 'new' ? 'create' : 'modify';
|
|
|
|
$logEntry = new ManualLogEntry( 'abusefilter', $subtype );
|
2018-07-05 17:57:30 +00:00
|
|
|
$logEntry->setPerformer( $user );
|
2019-12-07 17:20:10 +00:00
|
|
|
$logEntry->setTarget(
|
|
|
|
SpecialPage::getTitleFor( SpecialAbuseFilter::PAGE_NAME, (string)$new_id )
|
|
|
|
);
|
2018-05-02 19:24:46 +00:00
|
|
|
$logEntry->setParameters( [
|
|
|
|
'historyId' => $history_id,
|
|
|
|
'newId' => $new_id
|
|
|
|
] );
|
2019-08-26 13:01:09 +00:00
|
|
|
$logid = $logEntry->insert( $dbw );
|
2018-05-02 19:24:46 +00:00
|
|
|
$logEntry->publish( $logid );
|
|
|
|
|
|
|
|
// Purge the tag list cache so the fetchAllTags hook applies tag changes
|
|
|
|
if ( isset( $actions['tag'] ) ) {
|
|
|
|
AbuseFilterHooks::purgeTagCache();
|
|
|
|
}
|
|
|
|
|
|
|
|
self::resetFilterProfile( $new_id );
|
|
|
|
return [ $new_id, $history_id ];
|
|
|
|
}
|
|
|
|
|
2009-10-07 13:57:06 +00:00
|
|
|
/**
|
|
|
|
* Each version is expected to be an array( $row, $actions )
|
|
|
|
* Returns an array of fields that are different.
|
2011-08-24 22:11:52 +00:00
|
|
|
*
|
2017-08-04 23:14:10 +00:00
|
|
|
* @param array $version_1
|
|
|
|
* @param array $version_2
|
2019-08-26 13:01:09 +00:00
|
|
|
* @param string[] $availableActions All actions enabled in the AF config
|
2011-08-24 22:11:52 +00:00
|
|
|
*
|
|
|
|
* @return array
|
2009-10-07 13:57:06 +00:00
|
|
|
*/
|
2019-08-26 13:01:09 +00:00
|
|
|
public static function compareVersions(
|
|
|
|
array $version_1,
|
|
|
|
array $version_2,
|
|
|
|
array $availableActions
|
|
|
|
) {
|
2017-06-15 14:23:34 +00:00
|
|
|
$compareFields = [
|
2009-10-07 13:57:06 +00:00
|
|
|
'af_public_comments',
|
|
|
|
'af_pattern',
|
|
|
|
'af_comments',
|
|
|
|
'af_deleted',
|
|
|
|
'af_enabled',
|
2009-03-30 06:12:12 +00:00
|
|
|
'af_hidden',
|
|
|
|
'af_global',
|
2012-05-06 06:44:45 +00:00
|
|
|
'af_group',
|
2017-06-15 14:23:34 +00:00
|
|
|
];
|
|
|
|
$differences = [];
|
2009-01-26 22:31:02 +00:00
|
|
|
|
2009-10-07 13:57:06 +00:00
|
|
|
list( $row1, $actions1 ) = $version_1;
|
|
|
|
list( $row2, $actions2 ) = $version_2;
|
2009-01-26 22:31:02 +00:00
|
|
|
|
2010-02-13 14:10:36 +00:00
|
|
|
foreach ( $compareFields as $field ) {
|
2012-09-27 10:10:10 +00:00
|
|
|
if ( !isset( $row2->$field ) || $row1->$field != $row2->$field ) {
|
2009-01-26 22:31:02 +00:00
|
|
|
$differences[] = $field;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-26 13:01:09 +00:00
|
|
|
foreach ( $availableActions as $action ) {
|
2009-10-07 13:57:06 +00:00
|
|
|
if ( !isset( $actions1[$action] ) && !isset( $actions2[$action] ) ) {
|
2009-01-26 22:31:02 +00:00
|
|
|
// They're both unset
|
2009-10-07 13:57:06 +00:00
|
|
|
} elseif ( isset( $actions1[$action] ) && isset( $actions2[$action] ) ) {
|
2018-06-26 13:25:03 +00:00
|
|
|
// They're both set. Double check needed, e.g. per T180194
|
2018-09-27 13:58:55 +00:00
|
|
|
if ( array_diff( $actions1[$action], $actions2[$action] ) ||
|
|
|
|
array_diff( $actions2[$action], $actions1[$action] ) ) {
|
2009-01-26 22:31:02 +00:00
|
|
|
// Different parameters
|
|
|
|
$differences[] = 'actions';
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// One's unset, one's set.
|
|
|
|
$differences[] = 'actions';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return array_unique( $differences );
|
|
|
|
}
|
|
|
|
|
2012-03-11 20:40:04 +00:00
|
|
|
/**
|
2017-08-04 23:14:10 +00:00
|
|
|
* @param stdClass $row
|
2012-03-11 20:40:04 +00:00
|
|
|
* @return array
|
|
|
|
*/
|
2018-04-04 21:14:25 +00:00
|
|
|
public static function translateFromHistory( $row ) {
|
2018-06-26 13:25:03 +00:00
|
|
|
// Manually translate into an abuse_filter row.
|
2017-08-07 23:22:48 +00:00
|
|
|
$af_row = new stdClass;
|
2009-01-26 22:31:02 +00:00
|
|
|
|
2019-11-16 15:32:36 +00:00
|
|
|
foreach ( self::HISTORY_MAPPINGS as $af_col => $afh_col ) {
|
2009-01-26 22:31:02 +00:00
|
|
|
$af_row->$af_col = $row->$afh_col;
|
|
|
|
}
|
|
|
|
|
2018-04-04 21:14:25 +00:00
|
|
|
// Process flags
|
2009-01-26 22:31:02 +00:00
|
|
|
$af_row->af_deleted = 0;
|
|
|
|
$af_row->af_hidden = 0;
|
|
|
|
$af_row->af_enabled = 0;
|
|
|
|
|
2018-06-26 13:25:03 +00:00
|
|
|
if ( $row->afh_flags !== '' ) {
|
|
|
|
$flags = explode( ',', $row->afh_flags );
|
|
|
|
foreach ( $flags as $flag ) {
|
|
|
|
$col_name = "af_$flag";
|
|
|
|
$af_row->$col_name = 1;
|
|
|
|
}
|
2009-01-26 22:31:02 +00:00
|
|
|
}
|
|
|
|
|
2018-04-04 21:14:25 +00:00
|
|
|
// Process actions
|
2018-09-27 13:58:55 +00:00
|
|
|
$actionsRaw = unserialize( $row->afh_actions );
|
|
|
|
$actionsOutput = is_array( $actionsRaw ) ? $actionsRaw : [];
|
2009-01-26 22:31:02 +00:00
|
|
|
|
2018-09-27 13:58:55 +00:00
|
|
|
return [ $af_row, $actionsOutput ];
|
2009-01-26 22:31:02 +00:00
|
|
|
}
|
2009-01-28 01:26:38 +00:00
|
|
|
|
2012-03-11 20:40:04 +00:00
|
|
|
/**
|
2017-08-04 23:14:10 +00:00
|
|
|
* @param string $action
|
2020-08-25 19:33:37 +00:00
|
|
|
* @param MessageLocalizer|null $localizer
|
2020-06-02 08:13:17 +00:00
|
|
|
* @return string HTML
|
2012-03-11 20:40:04 +00:00
|
|
|
*/
|
2020-08-25 19:33:37 +00:00
|
|
|
public static function getActionDisplay( $action, MessageLocalizer $localizer = null ) {
|
|
|
|
$msgCallback = $localizer != null ? [ $localizer, 'msg' ] : 'wfMessage';
|
2013-08-18 06:35:16 +00:00
|
|
|
// Give grep a chance to find the usages:
|
|
|
|
// abusefilter-action-tag, abusefilter-action-throttle, abusefilter-action-warn,
|
|
|
|
// abusefilter-action-blockautopromote, abusefilter-action-block, abusefilter-action-degroup,
|
|
|
|
// abusefilter-action-rangeblock, abusefilter-action-disallow
|
2020-08-25 19:33:37 +00:00
|
|
|
$display = $msgCallback( "abusefilter-action-$action" )->escaped();
|
|
|
|
$display = $msgCallback( "abusefilter-action-$action" )->rawParams( $display )->isDisabled()
|
2018-07-04 15:04:05 +00:00
|
|
|
? htmlspecialchars( $action )
|
|
|
|
: $display;
|
2015-09-28 18:03:35 +00:00
|
|
|
|
2009-01-28 01:26:38 +00:00
|
|
|
return $display;
|
|
|
|
}
|
2009-01-28 23:54:41 +00:00
|
|
|
|
2018-03-26 18:41:20 +00:00
|
|
|
/**
|
|
|
|
* @param mixed $var
|
|
|
|
* @param string $indent
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public static function formatVar( $var, string $indent = '' ) {
|
|
|
|
if ( $var === [] ) {
|
|
|
|
return '[]';
|
|
|
|
} elseif ( is_array( $var ) ) {
|
|
|
|
$ret = '[';
|
|
|
|
$indent .= "\t";
|
|
|
|
foreach ( $var as $key => $val ) {
|
|
|
|
$ret .= "\n$indent" . self::formatVar( $key, $indent ) .
|
|
|
|
' => ' . self::formatVar( $val, $indent ) . ',';
|
|
|
|
}
|
|
|
|
// Strip trailing commas
|
|
|
|
return substr( $ret, 0, -1 ) . "\n" . substr( $indent, 0, -1 ) . ']';
|
|
|
|
} elseif ( is_string( $var ) ) {
|
|
|
|
// Don't escape the string (specifically backslashes) to avoid displaying wrong stuff
|
|
|
|
return "'$var'";
|
|
|
|
} elseif ( $var === null ) {
|
|
|
|
return 'null';
|
|
|
|
} elseif ( is_float( $var ) ) {
|
|
|
|
// Don't let float precision produce weirdness
|
|
|
|
return (string)$var;
|
|
|
|
}
|
|
|
|
return var_export( $var, true );
|
|
|
|
}
|
|
|
|
|
2012-03-11 20:40:04 +00:00
|
|
|
/**
|
2017-01-02 11:41:29 +00:00
|
|
|
* @param AbuseFilterVariableHolder|array $vars
|
2016-09-17 07:03:42 +00:00
|
|
|
* @param IContextSource $context
|
2012-03-11 20:40:04 +00:00
|
|
|
* @return string
|
|
|
|
*/
|
2016-09-17 07:03:42 +00:00
|
|
|
public static function buildVarDumpTable( $vars, IContextSource $context ) {
|
2009-02-26 12:15:14 +00:00
|
|
|
// Export all values
|
2010-08-19 21:12:09 +00:00
|
|
|
if ( $vars instanceof AbuseFilterVariableHolder ) {
|
2009-02-26 12:15:14 +00:00
|
|
|
$vars = $vars->exportAllVars();
|
2010-08-19 21:12:09 +00:00
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2009-01-29 22:44:31 +00:00
|
|
|
$output = '';
|
|
|
|
|
2009-10-07 13:57:06 +00:00
|
|
|
$output .=
|
2017-06-15 14:23:34 +00:00
|
|
|
Xml::openElement( 'table', [ 'class' => 'mw-abuselog-details' ] ) .
|
2009-10-07 13:57:06 +00:00
|
|
|
Xml::openElement( 'tbody' ) .
|
2009-02-07 09:34:11 +00:00
|
|
|
"\n";
|
|
|
|
|
2009-10-07 13:57:06 +00:00
|
|
|
$header =
|
2016-09-17 07:03:42 +00:00
|
|
|
Xml::element( 'th', null, $context->msg( 'abusefilter-log-details-var' )->text() ) .
|
|
|
|
Xml::element( 'th', null, $context->msg( 'abusefilter-log-details-val' )->text() );
|
2009-01-30 15:40:59 +00:00
|
|
|
$output .= Xml::tags( 'tr', null, $header ) . "\n";
|
2009-01-29 22:44:31 +00:00
|
|
|
|
2012-07-24 16:34:28 +00:00
|
|
|
if ( !count( $vars ) ) {
|
|
|
|
$output .= Xml::closeElement( 'tbody' ) . Xml::closeElement( 'table' );
|
2015-09-28 18:03:35 +00:00
|
|
|
|
2012-07-24 16:34:28 +00:00
|
|
|
return $output;
|
|
|
|
}
|
|
|
|
|
2020-01-15 16:08:53 +00:00
|
|
|
$keywordsManager = AbuseFilterServices::getKeywordsManager();
|
2009-01-29 22:44:31 +00:00
|
|
|
// Now, build the body of the table.
|
2010-02-13 14:10:36 +00:00
|
|
|
foreach ( $vars as $key => $value ) {
|
2009-10-07 13:57:06 +00:00
|
|
|
$key = strtolower( $key );
|
|
|
|
|
2020-01-15 16:08:53 +00:00
|
|
|
$varMsgKey = $keywordsManager->getMessageKeyForVar( $key );
|
|
|
|
if ( $varMsgKey ) {
|
|
|
|
$keyDisplay = $context->msg( $varMsgKey )->parse() .
|
2018-03-26 18:41:20 +00:00
|
|
|
' ' . Html::element( 'code', [], $context->msg( 'parentheses' )->rawParams( $key )->text() );
|
2009-01-29 22:44:31 +00:00
|
|
|
} else {
|
2018-03-26 18:41:20 +00:00
|
|
|
$keyDisplay = Html::element( 'code', [], $key );
|
2009-01-29 22:44:31 +00:00
|
|
|
}
|
|
|
|
|
2020-01-21 07:38:52 +00:00
|
|
|
if ( $value === null ) {
|
2009-01-30 15:40:59 +00:00
|
|
|
$value = '';
|
2016-01-06 20:17:41 +00:00
|
|
|
}
|
2018-03-26 18:41:20 +00:00
|
|
|
$value = Html::element(
|
|
|
|
'div',
|
|
|
|
[ 'class' => 'mw-abuselog-var-value' ],
|
|
|
|
self::formatVar( $value )
|
|
|
|
);
|
2009-01-29 22:44:31 +00:00
|
|
|
|
2009-10-07 13:57:06 +00:00
|
|
|
$trow =
|
2017-06-15 14:23:34 +00:00
|
|
|
Xml::tags( 'td', [ 'class' => 'mw-abuselog-var' ], $keyDisplay ) .
|
|
|
|
Xml::tags( 'td', [ 'class' => 'mw-abuselog-var-value' ], $value );
|
2010-02-13 14:10:36 +00:00
|
|
|
$output .=
|
2009-10-07 13:57:06 +00:00
|
|
|
Xml::tags( 'tr',
|
2017-06-15 14:23:34 +00:00
|
|
|
[ 'class' => "mw-abuselog-details-$key mw-abuselog-value" ], $trow
|
2009-02-07 09:34:11 +00:00
|
|
|
) . "\n";
|
2009-01-29 22:44:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
$output .= Xml::closeElement( 'tbody' ) . Xml::closeElement( 'table' );
|
2015-09-28 18:03:35 +00:00
|
|
|
|
2009-01-29 22:44:31 +00:00
|
|
|
return $output;
|
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2012-03-11 20:40:04 +00:00
|
|
|
/**
|
2017-01-02 11:41:29 +00:00
|
|
|
* @param string $action
|
|
|
|
* @param string[] $parameters
|
2018-10-14 09:39:36 +00:00
|
|
|
* @param Language $lang
|
2017-01-02 11:41:29 +00:00
|
|
|
* @return string
|
2012-03-11 20:40:04 +00:00
|
|
|
*/
|
2018-10-14 09:39:36 +00:00
|
|
|
public static function formatAction( $action, $parameters, $lang ) {
|
2018-03-14 16:50:58 +00:00
|
|
|
if ( count( $parameters ) === 0 ||
|
|
|
|
( $action === 'block' && count( $parameters ) !== 3 ) ) {
|
2017-07-23 07:03:40 +00:00
|
|
|
$displayAction = self::getActionDisplay( $action );
|
2009-03-12 05:04:39 +00:00
|
|
|
} else {
|
2018-02-20 12:36:32 +00:00
|
|
|
if ( $action === 'block' ) {
|
|
|
|
// Needs to be treated separately since the message is more complex
|
2018-05-03 16:59:37 +00:00
|
|
|
$messages = [
|
|
|
|
wfMessage( 'abusefilter-block-anon' )->escaped() .
|
|
|
|
wfMessage( 'colon-separator' )->escaped() .
|
2018-10-14 09:39:36 +00:00
|
|
|
$lang->translateBlockExpiry( $parameters[1] ),
|
2018-05-03 16:59:37 +00:00
|
|
|
wfMessage( 'abusefilter-block-user' )->escaped() .
|
|
|
|
wfMessage( 'colon-separator' )->escaped() .
|
2018-10-14 09:39:36 +00:00
|
|
|
$lang->translateBlockExpiry( $parameters[2] )
|
2018-05-03 16:59:37 +00:00
|
|
|
];
|
|
|
|
if ( $parameters[0] === 'blocktalk' ) {
|
|
|
|
$messages[] = wfMessage( 'abusefilter-block-talk' )->escaped();
|
|
|
|
}
|
2018-10-14 09:39:36 +00:00
|
|
|
$displayAction = $lang->commaList( $messages );
|
2018-05-08 20:04:05 +00:00
|
|
|
} elseif ( $action === 'throttle' ) {
|
|
|
|
array_shift( $parameters );
|
|
|
|
list( $actions, $time ) = explode( ',', array_shift( $parameters ) );
|
2018-09-09 10:14:31 +00:00
|
|
|
|
2019-02-11 14:53:34 +00:00
|
|
|
// Join comma-separated groups in a commaList with a final "and", and convert to messages.
|
|
|
|
// Messages used here: abusefilter-throttle-ip, abusefilter-throttle-user,
|
|
|
|
// abusefilter-throttle-site, abusefilter-throttle-creationdate, abusefilter-throttle-editcount
|
|
|
|
// abusefilter-throttle-range, abusefilter-throttle-page, abusefilter-throttle-none
|
|
|
|
foreach ( $parameters as &$val ) {
|
|
|
|
if ( strpos( $val, ',' ) !== false ) {
|
|
|
|
$subGroups = explode( ',', $val );
|
|
|
|
foreach ( $subGroups as &$group ) {
|
|
|
|
$msg = wfMessage( "abusefilter-throttle-$group" );
|
|
|
|
// We previously accepted literally everything in this field, so old entries
|
|
|
|
// may have weird stuff.
|
|
|
|
$group = $msg->exists() ? $msg->text() : $group;
|
2018-09-09 10:14:31 +00:00
|
|
|
}
|
2019-02-11 14:53:34 +00:00
|
|
|
unset( $group );
|
|
|
|
$val = $lang->listToText( $subGroups );
|
|
|
|
} else {
|
|
|
|
$msg = wfMessage( "abusefilter-throttle-$val" );
|
|
|
|
$val = $msg->exists() ? $msg->text() : $val;
|
2018-09-09 10:14:31 +00:00
|
|
|
}
|
|
|
|
}
|
2019-02-11 14:53:34 +00:00
|
|
|
unset( $val );
|
|
|
|
$groups = $lang->semicolonList( $parameters );
|
|
|
|
|
2018-05-08 20:04:05 +00:00
|
|
|
$displayAction = self::getActionDisplay( $action ) .
|
|
|
|
wfMessage( 'colon-separator' )->escaped() .
|
|
|
|
wfMessage( 'abusefilter-throttle-details' )->params( $actions, $time, $groups )->escaped();
|
2018-02-20 12:36:32 +00:00
|
|
|
} else {
|
2018-03-10 09:02:11 +00:00
|
|
|
$displayAction = self::getActionDisplay( $action ) .
|
2012-09-02 11:07:02 +00:00
|
|
|
wfMessage( 'colon-separator' )->escaped() .
|
2018-10-14 09:39:36 +00:00
|
|
|
$lang->semicolonList( array_map( 'htmlspecialchars', $parameters ) );
|
2018-02-20 12:36:32 +00:00
|
|
|
}
|
2009-03-12 05:04:39 +00:00
|
|
|
}
|
2015-09-28 18:03:35 +00:00
|
|
|
|
2009-03-12 05:04:39 +00:00
|
|
|
return $displayAction;
|
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2012-03-11 20:40:04 +00:00
|
|
|
/**
|
2017-01-02 11:41:29 +00:00
|
|
|
* @param string $value
|
2018-10-14 09:39:36 +00:00
|
|
|
* @param Language $lang
|
2012-03-11 20:40:04 +00:00
|
|
|
* @return string
|
|
|
|
*/
|
2018-10-14 09:39:36 +00:00
|
|
|
public static function formatFlags( $value, $lang ) {
|
2009-03-12 05:04:39 +00:00
|
|
|
$flags = array_filter( explode( ',', $value ) );
|
2017-06-15 14:23:34 +00:00
|
|
|
$flags_display = [];
|
2010-02-13 14:10:36 +00:00
|
|
|
foreach ( $flags as $flag ) {
|
2018-05-01 12:13:57 +00:00
|
|
|
$flags_display[] = wfMessage( "abusefilter-history-$flag" )->escaped();
|
2009-03-12 05:04:39 +00:00
|
|
|
}
|
2015-09-28 18:03:35 +00:00
|
|
|
|
2018-10-14 09:39:36 +00:00
|
|
|
return $lang->commaList( $flags_display );
|
2009-03-12 05:04:39 +00:00
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2012-03-11 20:40:04 +00:00
|
|
|
/**
|
2019-02-06 12:59:34 +00:00
|
|
|
* @param int $filterID
|
2017-01-02 11:41:29 +00:00
|
|
|
* @return string
|
2012-03-11 20:40:04 +00:00
|
|
|
*/
|
2018-04-04 21:14:25 +00:00
|
|
|
public static function getGlobalFilterDescription( $filterID ) {
|
2009-03-30 06:12:12 +00:00
|
|
|
global $wgAbuseFilterCentralDB;
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2010-08-19 21:12:09 +00:00
|
|
|
if ( !$wgAbuseFilterCentralDB ) {
|
2012-03-11 20:40:04 +00:00
|
|
|
return '';
|
2010-08-19 21:12:09 +00:00
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2016-12-26 10:09:37 +00:00
|
|
|
static $cache = [];
|
|
|
|
if ( isset( $cache[$filterID] ) ) {
|
|
|
|
return $cache[$filterID];
|
|
|
|
}
|
|
|
|
|
2019-06-27 22:55:20 +00:00
|
|
|
$fdb = self::getCentralDB( DB_REPLICA );
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2016-12-26 10:09:37 +00:00
|
|
|
$cache[$filterID] = $fdb->selectField(
|
2009-10-07 13:57:06 +00:00
|
|
|
'abuse_filter',
|
|
|
|
'af_public_comments',
|
2017-06-15 14:23:34 +00:00
|
|
|
[ 'af_id' => $filterID ],
|
2009-10-07 13:57:06 +00:00
|
|
|
__METHOD__
|
|
|
|
);
|
2016-12-26 10:09:37 +00:00
|
|
|
|
|
|
|
return $cache[$filterID];
|
2009-03-30 06:12:12 +00:00
|
|
|
}
|
2012-05-06 06:44:45 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Gives either the user-specified name for a group,
|
|
|
|
* or spits the input back out
|
2014-11-07 12:21:45 +00:00
|
|
|
* @param string $group The filter's group (as defined in $wgAbuseFilterValidGroups)
|
2017-01-02 11:41:29 +00:00
|
|
|
* @return string A name for that filter group, or the input.
|
2012-05-06 06:44:45 +00:00
|
|
|
*/
|
2018-04-04 21:14:25 +00:00
|
|
|
public static function nameGroup( $group ) {
|
2013-08-18 06:35:16 +00:00
|
|
|
// Give grep a chance to find the usages: abusefilter-group-default
|
2012-05-06 06:44:45 +00:00
|
|
|
$msg = "abusefilter-group-$group";
|
2015-09-28 18:03:35 +00:00
|
|
|
|
|
|
|
return wfMessage( $msg )->exists() ? wfMessage( $msg )->escaped() : $group;
|
2012-05-06 06:44:45 +00:00
|
|
|
}
|
2012-11-20 15:16:58 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Look up some text of a revision from its revision id
|
|
|
|
*
|
|
|
|
* Note that this is really *some* text, we do not make *any* guarantee
|
|
|
|
* that this text will be even close to what the user actually sees, or
|
|
|
|
* that the form is fit for any intended purpose.
|
|
|
|
*
|
|
|
|
* Note also that if the revision for any reason is not an Revision
|
|
|
|
* the function returns with an empty string.
|
|
|
|
*
|
2018-11-12 16:04:11 +00:00
|
|
|
* For now, this returns all the revision's slots, concatenated together.
|
|
|
|
* In future, this will be replaced by a better solution. See T208769 for
|
|
|
|
* discussion.
|
|
|
|
*
|
|
|
|
* @internal
|
2019-06-25 16:39:57 +00:00
|
|
|
* @todo Move elsewhere. VariableGenerator is a good candidate
|
2018-11-12 16:04:11 +00:00
|
|
|
*
|
2020-01-08 16:46:24 +00:00
|
|
|
* @param RevisionRecord|null $revision a valid revision
|
2018-11-12 16:04:11 +00:00
|
|
|
* @param User $user the user instance to check for privileged access
|
|
|
|
* @return string the content of the revision as some kind of string,
|
2015-09-28 18:03:35 +00:00
|
|
|
* or an empty string if it can not be found
|
2012-11-20 15:16:58 +00:00
|
|
|
*/
|
2020-01-08 16:46:24 +00:00
|
|
|
public static function revisionToString( ?RevisionRecord $revision, User $user ) {
|
|
|
|
if ( !$revision ) {
|
2012-11-20 15:16:58 +00:00
|
|
|
return '';
|
|
|
|
}
|
2015-11-23 10:00:25 +00:00
|
|
|
|
2018-11-12 16:04:11 +00:00
|
|
|
$strings = [];
|
|
|
|
|
|
|
|
foreach ( $revision->getSlotRoles() as $role ) {
|
|
|
|
$content = $revision->getContent( $role, RevisionRecord::FOR_THIS_USER, $user );
|
|
|
|
if ( $content === null ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
$strings[$role] = self::contentToString( $content );
|
2012-11-20 15:16:58 +00:00
|
|
|
}
|
2015-11-23 10:00:25 +00:00
|
|
|
|
2018-11-12 16:04:11 +00:00
|
|
|
$result = implode( "\n\n", $strings );
|
2012-11-20 15:16:58 +00:00
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
2013-01-04 15:37:56 +00:00
|
|
|
/**
|
|
|
|
* Converts the given Content object to a string.
|
|
|
|
*
|
|
|
|
* This uses Content::getNativeData() if $content is an instance of TextContent,
|
|
|
|
* or Content::getTextForSearchIndex() otherwise.
|
|
|
|
*
|
|
|
|
* The hook 'AbuseFilter::contentToString' can be used to override this
|
|
|
|
* behavior.
|
|
|
|
*
|
2018-11-12 16:04:11 +00:00
|
|
|
* @internal
|
2019-06-25 16:39:57 +00:00
|
|
|
* @todo Move elsewhere. VariableGenerator is a good candidate
|
2018-11-12 16:04:11 +00:00
|
|
|
*
|
2013-01-04 15:37:56 +00:00
|
|
|
* @param Content $content
|
|
|
|
*
|
|
|
|
* @return string a suitable string representation of the content.
|
|
|
|
*/
|
2018-04-04 21:14:25 +00:00
|
|
|
public static function contentToString( Content $content ) {
|
2013-01-04 15:37:56 +00:00
|
|
|
$text = null;
|
|
|
|
|
2020-06-03 00:43:22 +00:00
|
|
|
$hookRunner = AbuseFilterHookRunner::getRunner();
|
|
|
|
if ( $hookRunner->onAbuseFilterContentToString(
|
|
|
|
$content,
|
|
|
|
$text
|
|
|
|
) ) {
|
2013-01-04 15:37:56 +00:00
|
|
|
$text = $content instanceof TextContent
|
2019-03-22 21:46:57 +00:00
|
|
|
? $content->getText()
|
2015-09-28 18:03:35 +00:00
|
|
|
: $content->getTextForSearchIndex();
|
2013-01-04 15:37:56 +00:00
|
|
|
}
|
|
|
|
|
2018-05-10 08:34:57 +00:00
|
|
|
// T22310
|
|
|
|
$text = TextContent::normalizeLineEndings( (string)$text );
|
2013-01-04 15:37:56 +00:00
|
|
|
return $text;
|
|
|
|
}
|
|
|
|
|
2017-10-06 18:52:31 +00:00
|
|
|
/**
|
2012-12-30 03:59:33 +00:00
|
|
|
* Get the history ID of the first change to a given filter
|
|
|
|
*
|
2017-10-06 18:52:31 +00:00
|
|
|
* @param int $filterID Filter id
|
2019-06-17 11:00:39 +00:00
|
|
|
* @return string
|
2012-12-30 03:59:33 +00:00
|
|
|
*/
|
|
|
|
public static function getFirstFilterChange( $filterID ) {
|
2017-06-15 14:23:34 +00:00
|
|
|
static $firstChanges = [];
|
2012-12-30 03:59:33 +00:00
|
|
|
|
2015-09-28 18:03:35 +00:00
|
|
|
if ( !isset( $firstChanges[$filterID] ) ) {
|
2017-08-30 02:51:39 +00:00
|
|
|
$dbr = wfGetDB( DB_REPLICA );
|
2019-06-17 11:00:39 +00:00
|
|
|
$historyID = $dbr->selectField(
|
2012-12-30 03:59:33 +00:00
|
|
|
'abuse_filter_history',
|
|
|
|
'afh_id',
|
2017-06-15 14:23:34 +00:00
|
|
|
[
|
2012-12-30 03:59:33 +00:00
|
|
|
'afh_filter' => $filterID,
|
2017-06-15 14:23:34 +00:00
|
|
|
],
|
2012-12-30 03:59:33 +00:00
|
|
|
__METHOD__,
|
2017-06-15 14:23:34 +00:00
|
|
|
[ 'ORDER BY' => 'afh_timestamp ASC' ]
|
2012-12-30 03:59:33 +00:00
|
|
|
);
|
2019-06-17 11:00:39 +00:00
|
|
|
$firstChanges[$filterID] = $historyID;
|
2012-12-30 03:59:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return $firstChanges[$filterID];
|
|
|
|
}
|
2019-06-27 22:55:20 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @param int $index DB_MASTER/DB_REPLICA
|
|
|
|
* @return IDatabase
|
|
|
|
* @throws DBerror
|
|
|
|
* @throws RuntimeException
|
|
|
|
*/
|
|
|
|
public static function getCentralDB( $index ) {
|
|
|
|
global $wgAbuseFilterCentralDB;
|
|
|
|
|
|
|
|
if ( !is_string( $wgAbuseFilterCentralDB ) ) {
|
|
|
|
throw new RuntimeException( '$wgAbuseFilterCentralDB is not configured' );
|
|
|
|
}
|
|
|
|
|
|
|
|
return MediaWikiServices::getInstance()
|
|
|
|
->getDBLoadBalancerFactory()
|
2019-07-09 22:27:50 +00:00
|
|
|
->getMainLB( $wgAbuseFilterCentralDB )
|
2019-06-27 22:55:20 +00:00
|
|
|
->getConnectionRef( $index, [], $wgAbuseFilterCentralDB );
|
|
|
|
}
|
2019-08-27 09:40:01 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @param User $user
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public static function canEdit( User $user ) {
|
|
|
|
$block = $user->getBlock();
|
2019-09-18 21:48:40 +00:00
|
|
|
$permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
|
2019-08-27 09:40:01 +00:00
|
|
|
|
|
|
|
return (
|
|
|
|
!( $block && $block->isSitewide() ) &&
|
2019-09-18 21:48:40 +00:00
|
|
|
$permissionManager->userHasRight( $user, 'abusefilter-modify' )
|
2019-08-27 09:40:01 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param User $user
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public static function canEditGlobal( User $user ) {
|
2019-09-18 21:48:40 +00:00
|
|
|
return MediaWikiServices::getInstance()->getPermissionManager()
|
|
|
|
->userHasRight( $user, 'abusefilter-modify-global' );
|
2019-08-27 09:40:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Whether the user can edit the given filter.
|
|
|
|
*
|
|
|
|
* @param User $user
|
|
|
|
* @param object $row Filter row
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public static function canEditFilter( User $user, $row ) {
|
|
|
|
return (
|
|
|
|
self::canEdit( $user ) &&
|
|
|
|
!( isset( $row->af_global ) && $row->af_global == 1 && !self::canEditGlobal( $user ) )
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param User $user
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public static function canViewPrivate( User $user ) {
|
2019-09-18 21:48:40 +00:00
|
|
|
return MediaWikiServices::getInstance()->getPermissionManager()
|
|
|
|
->userHasAnyRight( $user, 'abusefilter-modify', 'abusefilter-view-private' );
|
2019-08-27 09:40:01 +00:00
|
|
|
}
|
2019-08-21 10:04:10 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Get a parser instance using default options. This should mostly be intended as a wrapper
|
|
|
|
* around $wgAbuseFilterParserClass and for choosing the right type of cache. It also has the
|
|
|
|
* benefit of typehinting the return value, thus making IDEs and static analysis tools happier.
|
|
|
|
*
|
|
|
|
* @param AbuseFilterVariableHolder|null $vars
|
|
|
|
* @return AbuseFilterParser
|
|
|
|
* @throws InvalidArgumentException if $wgAbuseFilterParserClass is not valid
|
|
|
|
*/
|
|
|
|
public static function getDefaultParser(
|
|
|
|
AbuseFilterVariableHolder $vars = null
|
|
|
|
) : AbuseFilterParser {
|
|
|
|
global $wgAbuseFilterParserClass;
|
|
|
|
|
|
|
|
$allowedValues = [ AbuseFilterParser::class, AbuseFilterCachingParser::class ];
|
|
|
|
if ( !in_array( $wgAbuseFilterParserClass, $allowedValues ) ) {
|
|
|
|
throw new InvalidArgumentException(
|
|
|
|
"Invalid value $wgAbuseFilterParserClass for \$wgAbuseFilterParserClass."
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
$contLang = MediaWikiServices::getInstance()->getContentLanguage();
|
|
|
|
$cache = ObjectCache::getLocalServerInstance( 'hash' );
|
|
|
|
$logger = LoggerFactory::getInstance( 'AbuseFilter' );
|
2020-01-15 16:08:53 +00:00
|
|
|
$keywordsManager = AbuseFilterServices::getKeywordsManager();
|
|
|
|
return new $wgAbuseFilterParserClass( $contLang, $cache, $logger, $keywordsManager, $vars );
|
2019-08-21 10:04:10 +00:00
|
|
|
}
|
2020-01-08 16:46:24 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Shortcut for checking whether $user can view the given revision, with mask
|
|
|
|
* SUPPRESSED_ALL.
|
|
|
|
*
|
|
|
|
* @note This assumes that a revision with the given ID exists
|
|
|
|
*
|
|
|
|
* @param RevisionRecord $revRec
|
|
|
|
* @param User $user
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public static function userCanViewRev( RevisionRecord $revRec, User $user ) : bool {
|
|
|
|
return $revRec->audienceCan(
|
|
|
|
RevisionRecord::SUPPRESSED_ALL,
|
|
|
|
RevisionRecord::FOR_THIS_USER,
|
|
|
|
$user
|
|
|
|
);
|
|
|
|
}
|
2008-06-27 06:18:51 +00:00
|
|
|
}
|