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;
|
2020-01-21 07:38:52 +00:00
|
|
|
use MediaWiki\Revision\RevisionRecord;
|
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 {
|
2016-06-28 22:50:38 +00:00
|
|
|
|
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
|
|
|
|
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-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
|
|
|
|
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-12-16 16:19:48 +00:00
|
|
|
$parser = AbuseFilterServices::getParserFactory()->newParser( $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
|
|
|
}
|
|
|
|
|
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
|
2020-12-02 18:08:37 +00:00
|
|
|
* @phan-return array{0:int,1:bool}
|
2019-02-06 12:59:34 +00:00
|
|
|
* @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-03-17 15:43:19 +00:00
|
|
|
* @return array[][]
|
2020-10-17 11:50:21 +00:00
|
|
|
* @deprecated since 1.36 Use ConsequencesLookup
|
2012-03-11 20:40:04 +00:00
|
|
|
*/
|
2019-03-17 15:43:19 +00:00
|
|
|
public static function getConsequencesForFilters( $filters ) {
|
2020-10-17 11:50:21 +00:00
|
|
|
return AbuseFilterServices::getConsequencesLookup()->getConsequencesForFilters( $filters );
|
2009-03-30 06:12:12 +00:00
|
|
|
}
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
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 ) {
|
2020-10-16 22:29:41 +00:00
|
|
|
$dbw = AbuseFilterServices::getCentralDBManager()->getConnection( 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
|
|
|
*
|
2019-01-06 17:49:22 +00:00
|
|
|
* @param string $storedID "tt:/$id"
|
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
|
|
|
*/
|
2019-01-06 17:49:22 +00:00
|
|
|
public static function loadVarDump( $storedID ) : AbuseFilterVariableHolder {
|
|
|
|
$textID = (int)str_replace( 'tt:', '', $storedID );
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2017-08-30 02:51:39 +00:00
|
|
|
$dbr = wfGetDB( DB_REPLICA );
|
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' ],
|
2019-01-06 17:49:22 +00:00
|
|
|
[ 'old_id' => $textID ],
|
2009-10-07 13:57:06 +00:00
|
|
|
__METHOD__
|
|
|
|
);
|
|
|
|
|
|
|
|
if ( !$text_row ) {
|
2019-01-06 14:20:10 +00:00
|
|
|
$logger = LoggerFactory::getInstance( 'AbuseFilter' );
|
2019-01-06 17:49:22 +00:00
|
|
|
$logger->warning( __METHOD__ . ": no text row found for input $storedID." );
|
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 17:49:22 +00:00
|
|
|
$vars = FormatJson::decode( $text, true );
|
|
|
|
$obj = AbuseFilterVariableHolder::newFromArray( $vars );
|
|
|
|
$obj->translateDeprecatedVars();
|
2009-02-27 03:06:19 +00:00
|
|
|
return $obj;
|
|
|
|
}
|
2009-10-07 13:57:06 +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-10-05 21:39:23 +00:00
|
|
|
$msg = $msgCallback( "abusefilter-action-$action" );
|
|
|
|
return $msg->isDisabled() ? htmlspecialchars( $action ) : $msg->escaped();
|
2009-01-28 01:26:38 +00:00
|
|
|
}
|
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-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
|
|
|
|
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
|
|
|
}
|