2008-06-27 06:18:51 +00:00
|
|
|
<?php
|
|
|
|
|
2014-03-25 15:57:01 +00:00
|
|
|
use MediaWiki\Linker\LinkRenderer;
|
2018-10-12 09:39:38 +00:00
|
|
|
use MediaWiki\Logger\LoggerFactory;
|
2016-12-07 23:32:41 +00:00
|
|
|
use MediaWiki\MediaWikiServices;
|
2019-01-10 17:32:54 +00:00
|
|
|
use MediaWiki\Revision\MutableRevisionRecord;
|
|
|
|
use MediaWiki\Revision\SlotRecord;
|
2018-03-12 14:00:49 +00:00
|
|
|
use Wikimedia\Rdbms\Database;
|
2018-08-25 12:44:01 +00:00
|
|
|
use Wikimedia\Rdbms\IMaintainableDatabase;
|
2016-05-15 15:35:33 +00:00
|
|
|
|
2008-06-27 06:18:51 +00:00
|
|
|
class AbuseFilterHooks {
|
2015-03-17 13:00:14 +00:00
|
|
|
const FETCH_ALL_TAGS_KEY = 'abusefilter-fetch-all-tags';
|
|
|
|
|
2018-12-09 16:58:57 +00:00
|
|
|
/** @var WikiPage|null Make sure edit filter & edit save hooks match */
|
|
|
|
private static $lastEditPage = null;
|
2011-08-24 22:11:52 +00:00
|
|
|
|
2016-06-03 18:01:56 +00:00
|
|
|
/**
|
|
|
|
* Called right after configuration has been loaded.
|
|
|
|
*/
|
|
|
|
public static function onRegistration() {
|
2019-01-25 13:47:03 +00:00
|
|
|
global $wgAuthManagerAutoConfig, $wgActionFilteredLogs, $wgAbuseFilterProfile,
|
2019-01-07 15:23:21 +00:00
|
|
|
$wgAbuseFilterProfiling, $wgAbuseFilterPrivateLog, $wgAbuseFilterForceSummary,
|
|
|
|
$wgGroupPermissions;
|
2019-01-24 12:33:31 +00:00
|
|
|
|
|
|
|
// @todo Remove this in a future release (added in 1.33)
|
2019-01-25 13:47:03 +00:00
|
|
|
if ( isset( $wgAbuseFilterProfile ) || isset( $wgAbuseFilterProfiling ) ) {
|
|
|
|
wfWarn( '$wgAbuseFilterProfile and $wgAbuseFilterProfiling have been removed and ' .
|
|
|
|
'profiling is now enabled by default.' );
|
2019-01-24 12:33:31 +00:00
|
|
|
}
|
2016-05-15 15:35:33 +00:00
|
|
|
|
2019-01-07 15:23:21 +00:00
|
|
|
if ( isset( $wgAbuseFilterPrivateLog ) ) {
|
|
|
|
global $wgAbuseFilterLogPrivateDetailsAccess;
|
|
|
|
$wgAbuseFilterLogPrivateDetailsAccess = $wgAbuseFilterPrivateLog;
|
|
|
|
wfWarn( '$wgAbuseFilterPrivateLog has been renamed to $wgAbuseFilterLogPrivateDetailsAccess. ' .
|
|
|
|
'Please make the change in your settings; the format is identical.'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
if ( isset( $wgAbuseFilterForceSummary ) ) {
|
|
|
|
global $wgAbuseFilterPrivateDetailsForceReason;
|
|
|
|
$wgAbuseFilterPrivateDetailsForceReason = $wgAbuseFilterForceSummary;
|
|
|
|
wfWarn( '$wgAbuseFilterForceSummary has been renamed to ' .
|
|
|
|
'$wgAbuseFilterPrivateDetailsForceReason. Please make the change in your settings; ' .
|
|
|
|
'the format is identical.'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
$found = false;
|
|
|
|
foreach ( $wgGroupPermissions as &$perms ) {
|
|
|
|
if ( array_key_exists( 'abusefilter-private', $perms ) ) {
|
|
|
|
$perms['abusefilter-privatedetails'] = $perms[ 'abusefilter-private' ];
|
|
|
|
unset( $perms[ 'abusefilter-private' ] );
|
|
|
|
$found = true;
|
|
|
|
}
|
|
|
|
if ( array_key_exists( 'abusefilter-private-log', $perms ) ) {
|
|
|
|
$perms['abusefilter-privatedetails-log'] = $perms[ 'abusefilter-private-log' ];
|
|
|
|
unset( $perms[ 'abusefilter-private-log' ] );
|
|
|
|
$found = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
unset( $perms );
|
|
|
|
|
|
|
|
if ( $found ) {
|
|
|
|
wfWarn( 'The group permissions "abusefilter-private-log" and "abusefilter-private" have ' .
|
|
|
|
'been renamed, respectively, to "abusefilter-privatedetails-log" and ' .
|
2019-08-13 09:09:39 +00:00
|
|
|
'"abusefilter-privatedetails". Please update the names in your settings.'
|
2019-01-07 15:23:21 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2017-08-08 12:03:56 +00:00
|
|
|
$wgAuthManagerAutoConfig['preauth'][AbuseFilterPreAuthenticationProvider::class] = [
|
|
|
|
'class' => AbuseFilterPreAuthenticationProvider::class,
|
2018-04-04 21:14:25 +00:00
|
|
|
// Run after normal preauth providers to keep the log cleaner
|
|
|
|
'sort' => 5,
|
2017-08-08 12:03:56 +00:00
|
|
|
];
|
2017-02-15 18:12:31 +00:00
|
|
|
|
|
|
|
$wgActionFilteredLogs['suppress'] = array_merge(
|
|
|
|
$wgActionFilteredLogs['suppress'],
|
|
|
|
// Message: log-action-filter-suppress-abuselog
|
|
|
|
[ 'abuselog' => [ 'hide-afl', 'unhide-afl' ] ]
|
|
|
|
);
|
2018-07-16 12:10:36 +00:00
|
|
|
$wgActionFilteredLogs['rights'] = array_merge(
|
|
|
|
$wgActionFilteredLogs['rights'],
|
|
|
|
// Messages: log-action-filter-rights-blockautopromote,
|
|
|
|
// log-action-filter-rights-restoreautopromote
|
|
|
|
[
|
|
|
|
'blockautopromote' => [ 'blockautopromote' ],
|
|
|
|
'restoreautopromote' => [ 'restoreautopromote' ]
|
|
|
|
]
|
|
|
|
);
|
2016-06-03 18:01:56 +00:00
|
|
|
}
|
|
|
|
|
2013-01-04 15:37:56 +00:00
|
|
|
/**
|
2016-04-07 05:06:02 +00:00
|
|
|
* Entry point for the EditFilterMergedContent hook.
|
2013-01-04 15:37:56 +00:00
|
|
|
*
|
|
|
|
* @param IContextSource $context the context of the edit
|
|
|
|
* @param Content $content the new Content generated by the edit
|
2015-09-28 18:03:35 +00:00
|
|
|
* @param Status $status Error message to return
|
2013-01-04 15:37:56 +00:00
|
|
|
* @param string $summary Edit summary for page
|
|
|
|
* @param User $user the user performing the edit
|
|
|
|
* @param bool $minoredit whether this is a minor edit according to the user.
|
2019-01-10 17:32:54 +00:00
|
|
|
* @param string $slot slot role for the content
|
2013-01-04 15:37:56 +00:00
|
|
|
*/
|
|
|
|
public static function onEditFilterMergedContent( IContextSource $context, Content $content,
|
2019-01-10 17:32:54 +00:00
|
|
|
Status $status, $summary, User $user, $minoredit, $slot = SlotRecord::MAIN
|
2017-07-08 18:49:13 +00:00
|
|
|
) {
|
2018-10-03 14:38:41 +00:00
|
|
|
// @todo is there any real point in passing this in?
|
2016-05-13 04:17:51 +00:00
|
|
|
$text = AbuseFilter::contentToString( $content );
|
2013-01-04 15:37:56 +00:00
|
|
|
|
Actually return errors for action=edit API
Setting 'apiHookResult' results in a "successful" response; if we want
to report an error, we need to use ApiMessage. We already were doing
this for action=upload. Now our action=edit API responses will be
consistent with MediaWiki and other extensions, and will be able to
take advantage of errorformat=html.
Since this breaks compatibility anyway, also remove some redundant
backwards-compatibility values from the output.
To avoid user interface regressions in VisualEditor, the changes
I3b9c4fef (in VE) and I106dbd3c (in MediaWiki) should be merged first.
Before:
{
"edit": {
"code": "abusefilter-disallowed",
"message": {
"key": "abusefilter-disallowed",
"params": [ ... ]
},
"abusefilter": { ... },
"info": "Hit AbuseFilter: Test filter disallow",
"warning": "This action has been automatically identified ...",
"result": "Failure"
}
}
After:
{
"errors": [
{
"code": "abusefilter-disallowed",
"data": {
"abusefilter": { ... },
},
"module": "edit",
"*": "This action has been automatically identified ..."
}
],
"*": "See http://localhost:3080/w/api.php for API usage. ..."
}
For comparison, a 'readonly' error:
{
"errors": [
{
"code": "readonly",
"data": {
"readonlyreason": "foo bar"
},
"module": "main",
"*": "The wiki is currently in read-only mode."
}
],
"*": "See http://localhost:3080/w/api.php for API usage. ..."
}
Bug: T229539
Depends-On: I106dbd3cbdbf7082b1d1f1c1106ece6b19c22a86
Depends-On: I3b9c4fefc0869ef7999c21cef754434febd852ec
Change-Id: I5424de387cbbcc9c85026b8cfeaf01635eee34a0
2019-08-01 01:48:08 +00:00
|
|
|
$filterResult = self::filterEdit( $context, $content, $text, $summary, $slot );
|
2015-09-28 18:03:35 +00:00
|
|
|
|
Actually return errors for action=edit API
Setting 'apiHookResult' results in a "successful" response; if we want
to report an error, we need to use ApiMessage. We already were doing
this for action=upload. Now our action=edit API responses will be
consistent with MediaWiki and other extensions, and will be able to
take advantage of errorformat=html.
Since this breaks compatibility anyway, also remove some redundant
backwards-compatibility values from the output.
To avoid user interface regressions in VisualEditor, the changes
I3b9c4fef (in VE) and I106dbd3c (in MediaWiki) should be merged first.
Before:
{
"edit": {
"code": "abusefilter-disallowed",
"message": {
"key": "abusefilter-disallowed",
"params": [ ... ]
},
"abusefilter": { ... },
"info": "Hit AbuseFilter: Test filter disallow",
"warning": "This action has been automatically identified ...",
"result": "Failure"
}
}
After:
{
"errors": [
{
"code": "abusefilter-disallowed",
"data": {
"abusefilter": { ... },
},
"module": "edit",
"*": "This action has been automatically identified ..."
}
],
"*": "See http://localhost:3080/w/api.php for API usage. ..."
}
For comparison, a 'readonly' error:
{
"errors": [
{
"code": "readonly",
"data": {
"readonlyreason": "foo bar"
},
"module": "main",
"*": "The wiki is currently in read-only mode."
}
],
"*": "See http://localhost:3080/w/api.php for API usage. ..."
}
Bug: T229539
Depends-On: I106dbd3cbdbf7082b1d1f1c1106ece6b19c22a86
Depends-On: I3b9c4fefc0869ef7999c21cef754434febd852ec
Change-Id: I5424de387cbbcc9c85026b8cfeaf01635eee34a0
2019-08-01 01:48:08 +00:00
|
|
|
if ( !$filterResult->isOK() ) {
|
2016-06-16 15:40:36 +00:00
|
|
|
// Produce a useful error message for API edits
|
Actually return errors for action=edit API
Setting 'apiHookResult' results in a "successful" response; if we want
to report an error, we need to use ApiMessage. We already were doing
this for action=upload. Now our action=edit API responses will be
consistent with MediaWiki and other extensions, and will be able to
take advantage of errorformat=html.
Since this breaks compatibility anyway, also remove some redundant
backwards-compatibility values from the output.
To avoid user interface regressions in VisualEditor, the changes
I3b9c4fef (in VE) and I106dbd3c (in MediaWiki) should be merged first.
Before:
{
"edit": {
"code": "abusefilter-disallowed",
"message": {
"key": "abusefilter-disallowed",
"params": [ ... ]
},
"abusefilter": { ... },
"info": "Hit AbuseFilter: Test filter disallow",
"warning": "This action has been automatically identified ...",
"result": "Failure"
}
}
After:
{
"errors": [
{
"code": "abusefilter-disallowed",
"data": {
"abusefilter": { ... },
},
"module": "edit",
"*": "This action has been automatically identified ..."
}
],
"*": "See http://localhost:3080/w/api.php for API usage. ..."
}
For comparison, a 'readonly' error:
{
"errors": [
{
"code": "readonly",
"data": {
"readonlyreason": "foo bar"
},
"module": "main",
"*": "The wiki is currently in read-only mode."
}
],
"*": "See http://localhost:3080/w/api.php for API usage. ..."
}
Bug: T229539
Depends-On: I106dbd3cbdbf7082b1d1f1c1106ece6b19c22a86
Depends-On: I3b9c4fefc0869ef7999c21cef754434febd852ec
Change-Id: I5424de387cbbcc9c85026b8cfeaf01635eee34a0
2019-08-01 01:48:08 +00:00
|
|
|
$filterResultApi = self::getApiStatus( $filterResult );
|
|
|
|
$status->merge( $filterResultApi );
|
2016-04-07 05:06:02 +00:00
|
|
|
}
|
2013-01-04 15:37:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2019-06-25 10:43:54 +00:00
|
|
|
* Get variables for filtering an edit.
|
|
|
|
* @todo Consider splitting into a separate class
|
|
|
|
* @see self::filterEdit for more parameter docs
|
|
|
|
* @internal Until we'll find the right place for it
|
2013-01-04 15:37:56 +00:00
|
|
|
*
|
2019-06-25 10:43:54 +00:00
|
|
|
* @param Title $title
|
|
|
|
* @param User $user
|
|
|
|
* @param Content $content
|
|
|
|
* @param string $text
|
|
|
|
* @param string $summary
|
|
|
|
* @param string $slot
|
|
|
|
* @param WikiPage|null $page
|
|
|
|
* @return AbuseFilterVariableHolder|null
|
2013-01-04 15:37:56 +00:00
|
|
|
*/
|
2019-06-25 10:43:54 +00:00
|
|
|
public static function getEditVars(
|
|
|
|
Title $title,
|
|
|
|
User $user,
|
|
|
|
Content $content,
|
|
|
|
$text,
|
|
|
|
$summary,
|
|
|
|
$slot,
|
|
|
|
WikiPage $page = null
|
2017-07-08 18:49:13 +00:00
|
|
|
) {
|
2019-01-10 17:32:54 +00:00
|
|
|
$oldContent = null;
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2019-06-25 10:43:54 +00:00
|
|
|
if ( $page !== null ) {
|
2019-01-10 17:32:54 +00:00
|
|
|
$oldRevision = $page->getRevision();
|
|
|
|
if ( !$oldRevision ) {
|
2019-06-25 10:43:54 +00:00
|
|
|
return null;
|
2012-05-03 23:35:23 +00:00
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2019-01-10 17:32:54 +00:00
|
|
|
$oldContent = $oldRevision->getContent( Revision::RAW );
|
|
|
|
$oldAfText = AbuseFilter::revisionToString( $oldRevision, $user );
|
|
|
|
|
|
|
|
// XXX: Recreate what the new revision will probably be so we can get the full AF
|
|
|
|
// text for all slots
|
|
|
|
$oldRevRecord = $oldRevision->getRevisionRecord();
|
|
|
|
$newRevision = MutableRevisionRecord::newFromParentRevision( $oldRevRecord );
|
|
|
|
$newRevision->setContent( $slot, $content );
|
|
|
|
$text = AbuseFilter::revisionToString( $newRevision, $user );
|
2011-10-25 12:46:05 +00:00
|
|
|
|
2013-01-04 15:37:56 +00:00
|
|
|
// Cache article object so we can share a parse operation
|
|
|
|
$articleCacheKey = $title->getNamespace() . ':' . $title->getText();
|
|
|
|
AFComputedVariable::$articleCache[$articleCacheKey] = $page;
|
2014-09-10 21:33:27 +00:00
|
|
|
|
|
|
|
// Don't trigger for null edits.
|
2019-09-01 09:23:31 +00:00
|
|
|
if ( $oldContent ) {
|
2014-09-10 21:33:27 +00:00
|
|
|
// Compare Content objects if available
|
2019-01-10 17:32:54 +00:00
|
|
|
if ( $content->equals( $oldContent ) ) {
|
2019-06-25 10:43:54 +00:00
|
|
|
return null;
|
2016-09-13 08:40:21 +00:00
|
|
|
}
|
2018-08-26 08:34:42 +00:00
|
|
|
} elseif ( strcmp( $oldAfText, $text ) === 0 ) {
|
2014-09-10 21:33:27 +00:00
|
|
|
// Otherwise, compare strings
|
2019-06-25 10:43:54 +00:00
|
|
|
return null;
|
2014-09-10 21:33:27 +00:00
|
|
|
}
|
2013-01-04 15:37:56 +00:00
|
|
|
} else {
|
2019-01-10 23:21:28 +00:00
|
|
|
$oldAfText = '';
|
2013-01-04 15:37:56 +00:00
|
|
|
}
|
|
|
|
|
2019-06-25 10:43:54 +00:00
|
|
|
return self::newVariableHolderForEdit(
|
2019-08-04 13:06:22 +00:00
|
|
|
$user, $title, $page, $summary, $content, $text, $oldAfText, $oldContent
|
2013-03-06 06:21:55 +00:00
|
|
|
);
|
2019-06-25 10:43:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Implementation for EditFilterMergedContent hook.
|
|
|
|
*
|
|
|
|
* @param IContextSource $context the context of the edit
|
|
|
|
* @param Content $content the new Content generated by the edit
|
|
|
|
* @param string $text new page content (subject of filtering)
|
|
|
|
* @param string $summary Edit summary for page
|
|
|
|
* @param string $slot slot role for the content
|
|
|
|
* @return Status
|
|
|
|
*/
|
|
|
|
public static function filterEdit( IContextSource $context, Content $content, $text,
|
Actually return errors for action=edit API
Setting 'apiHookResult' results in a "successful" response; if we want
to report an error, we need to use ApiMessage. We already were doing
this for action=upload. Now our action=edit API responses will be
consistent with MediaWiki and other extensions, and will be able to
take advantage of errorformat=html.
Since this breaks compatibility anyway, also remove some redundant
backwards-compatibility values from the output.
To avoid user interface regressions in VisualEditor, the changes
I3b9c4fef (in VE) and I106dbd3c (in MediaWiki) should be merged first.
Before:
{
"edit": {
"code": "abusefilter-disallowed",
"message": {
"key": "abusefilter-disallowed",
"params": [ ... ]
},
"abusefilter": { ... },
"info": "Hit AbuseFilter: Test filter disallow",
"warning": "This action has been automatically identified ...",
"result": "Failure"
}
}
After:
{
"errors": [
{
"code": "abusefilter-disallowed",
"data": {
"abusefilter": { ... },
},
"module": "edit",
"*": "This action has been automatically identified ..."
}
],
"*": "See http://localhost:3080/w/api.php for API usage. ..."
}
For comparison, a 'readonly' error:
{
"errors": [
{
"code": "readonly",
"data": {
"readonlyreason": "foo bar"
},
"module": "main",
"*": "The wiki is currently in read-only mode."
}
],
"*": "See http://localhost:3080/w/api.php for API usage. ..."
}
Bug: T229539
Depends-On: I106dbd3cbdbf7082b1d1f1c1106ece6b19c22a86
Depends-On: I3b9c4fefc0869ef7999c21cef754434febd852ec
Change-Id: I5424de387cbbcc9c85026b8cfeaf01635eee34a0
2019-08-01 01:48:08 +00:00
|
|
|
$summary, $slot = SlotRecord::MAIN
|
2019-06-25 10:43:54 +00:00
|
|
|
) {
|
|
|
|
self::$lastEditPage = null;
|
|
|
|
|
|
|
|
$title = $context->getTitle();
|
|
|
|
if ( $title === null ) {
|
|
|
|
// T144265: This *should* never happen.
|
|
|
|
$logger = LoggerFactory::getInstance( 'AbuseFilter' );
|
|
|
|
$logger->warning( __METHOD__ . ' received a null title.' );
|
|
|
|
return Status::newGood();
|
|
|
|
}
|
|
|
|
|
|
|
|
$user = $context->getUser();
|
|
|
|
if ( $title->canExist() && $title->exists() ) {
|
|
|
|
// Make sure we load the latest text saved in database (bug 31656)
|
|
|
|
$page = $context->getWikiPage();
|
|
|
|
} else {
|
|
|
|
$page = null;
|
|
|
|
}
|
2012-11-16 17:06:34 +00:00
|
|
|
|
2019-06-25 10:43:54 +00:00
|
|
|
$vars = self::getEditVars( $title, $user, $content, $text, $summary, $slot, $page );
|
|
|
|
if ( $vars === null ) {
|
|
|
|
// We don't have to filter the edit
|
|
|
|
return Status::newGood();
|
|
|
|
}
|
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, 'default' );
|
|
|
|
$filterResult = $runner->run();
|
|
|
|
if ( !$filterResult->isOK() ) {
|
|
|
|
return $filterResult;
|
2008-06-27 09:26:54 +00:00
|
|
|
}
|
2012-02-10 23:41:05 +00:00
|
|
|
|
2018-12-09 16:58:57 +00:00
|
|
|
self::$lastEditPage = $page;
|
2012-02-10 23:41:05 +00:00
|
|
|
|
2016-06-20 21:58:17 +00:00
|
|
|
return Status::newGood();
|
2012-02-10 23:41:05 +00:00
|
|
|
}
|
|
|
|
|
2016-06-13 11:53:50 +00:00
|
|
|
/**
|
|
|
|
* @param User $user
|
|
|
|
* @param Title $title
|
|
|
|
* @param WikiPage|null $page
|
|
|
|
* @param string $summary
|
2016-09-13 08:40:21 +00:00
|
|
|
* @param Content $newcontent
|
2016-06-13 11:53:50 +00:00
|
|
|
* @param string $text
|
2019-08-04 13:06:22 +00:00
|
|
|
* @param string $oldtext
|
2018-04-29 17:52:45 +00:00
|
|
|
* @param Content|null $oldcontent
|
2016-06-13 11:53:50 +00:00
|
|
|
* @return AbuseFilterVariableHolder
|
|
|
|
* @throws MWException
|
|
|
|
*/
|
|
|
|
private static function newVariableHolderForEdit(
|
2016-09-13 08:40:21 +00:00
|
|
|
User $user, Title $title, $page, $summary, Content $newcontent,
|
2019-08-04 13:06:22 +00:00
|
|
|
$text, $oldtext, Content $oldcontent = null
|
2016-06-13 11:53:50 +00:00
|
|
|
) {
|
|
|
|
$vars = new AbuseFilterVariableHolder();
|
|
|
|
$vars->addHolders(
|
|
|
|
AbuseFilter::generateUserVars( $user ),
|
2019-01-02 10:30:38 +00:00
|
|
|
AbuseFilter::generateTitleVars( $title, 'page' )
|
2016-06-13 11:53:50 +00:00
|
|
|
);
|
|
|
|
$vars->setVar( 'action', 'edit' );
|
|
|
|
$vars->setVar( 'summary', $summary );
|
2016-09-13 08:40:21 +00:00
|
|
|
if ( $oldcontent instanceof Content ) {
|
|
|
|
$oldmodel = $oldcontent->getModel();
|
|
|
|
} else {
|
|
|
|
$oldmodel = '';
|
|
|
|
$oldtext = '';
|
|
|
|
}
|
|
|
|
$vars->setVar( 'old_content_model', $oldmodel );
|
|
|
|
$vars->setVar( 'new_content_model', $newcontent->getModel() );
|
2016-06-13 11:53:50 +00:00
|
|
|
$vars->setVar( 'old_wikitext', $oldtext );
|
|
|
|
$vars->setVar( 'new_wikitext', $text );
|
|
|
|
$vars->addHolders( AbuseFilter::getEditVars( $title, $page ) );
|
|
|
|
|
|
|
|
return $vars;
|
|
|
|
}
|
|
|
|
|
2016-04-07 05:06:02 +00:00
|
|
|
/**
|
|
|
|
* @param Status $status Error message details
|
Actually return errors for action=edit API
Setting 'apiHookResult' results in a "successful" response; if we want
to report an error, we need to use ApiMessage. We already were doing
this for action=upload. Now our action=edit API responses will be
consistent with MediaWiki and other extensions, and will be able to
take advantage of errorformat=html.
Since this breaks compatibility anyway, also remove some redundant
backwards-compatibility values from the output.
To avoid user interface regressions in VisualEditor, the changes
I3b9c4fef (in VE) and I106dbd3c (in MediaWiki) should be merged first.
Before:
{
"edit": {
"code": "abusefilter-disallowed",
"message": {
"key": "abusefilter-disallowed",
"params": [ ... ]
},
"abusefilter": { ... },
"info": "Hit AbuseFilter: Test filter disallow",
"warning": "This action has been automatically identified ...",
"result": "Failure"
}
}
After:
{
"errors": [
{
"code": "abusefilter-disallowed",
"data": {
"abusefilter": { ... },
},
"module": "edit",
"*": "This action has been automatically identified ..."
}
],
"*": "See http://localhost:3080/w/api.php for API usage. ..."
}
For comparison, a 'readonly' error:
{
"errors": [
{
"code": "readonly",
"data": {
"readonlyreason": "foo bar"
},
"module": "main",
"*": "The wiki is currently in read-only mode."
}
],
"*": "See http://localhost:3080/w/api.php for API usage. ..."
}
Bug: T229539
Depends-On: I106dbd3cbdbf7082b1d1f1c1106ece6b19c22a86
Depends-On: I3b9c4fefc0869ef7999c21cef754434febd852ec
Change-Id: I5424de387cbbcc9c85026b8cfeaf01635eee34a0
2019-08-01 01:48:08 +00:00
|
|
|
* @return Status Status containing the same error messages with extra data for the API
|
2016-04-07 05:06:02 +00:00
|
|
|
*/
|
Actually return errors for action=edit API
Setting 'apiHookResult' results in a "successful" response; if we want
to report an error, we need to use ApiMessage. We already were doing
this for action=upload. Now our action=edit API responses will be
consistent with MediaWiki and other extensions, and will be able to
take advantage of errorformat=html.
Since this breaks compatibility anyway, also remove some redundant
backwards-compatibility values from the output.
To avoid user interface regressions in VisualEditor, the changes
I3b9c4fef (in VE) and I106dbd3c (in MediaWiki) should be merged first.
Before:
{
"edit": {
"code": "abusefilter-disallowed",
"message": {
"key": "abusefilter-disallowed",
"params": [ ... ]
},
"abusefilter": { ... },
"info": "Hit AbuseFilter: Test filter disallow",
"warning": "This action has been automatically identified ...",
"result": "Failure"
}
}
After:
{
"errors": [
{
"code": "abusefilter-disallowed",
"data": {
"abusefilter": { ... },
},
"module": "edit",
"*": "This action has been automatically identified ..."
}
],
"*": "See http://localhost:3080/w/api.php for API usage. ..."
}
For comparison, a 'readonly' error:
{
"errors": [
{
"code": "readonly",
"data": {
"readonlyreason": "foo bar"
},
"module": "main",
"*": "The wiki is currently in read-only mode."
}
],
"*": "See http://localhost:3080/w/api.php for API usage. ..."
}
Bug: T229539
Depends-On: I106dbd3cbdbf7082b1d1f1c1106ece6b19c22a86
Depends-On: I3b9c4fefc0869ef7999c21cef754434febd852ec
Change-Id: I5424de387cbbcc9c85026b8cfeaf01635eee34a0
2019-08-01 01:48:08 +00:00
|
|
|
private static function getApiStatus( Status $status ) {
|
|
|
|
$allActionsTaken = $status->getValue();
|
|
|
|
$statusForApi = Status::newGood();
|
|
|
|
|
|
|
|
foreach ( $status->getErrors() as $error ) {
|
|
|
|
list( $filterDescription, $filter ) = $error['params'];
|
|
|
|
$actionsTaken = $allActionsTaken[ $filter ];
|
|
|
|
|
|
|
|
$code = ( $actionsTaken === [ 'warn' ] ) ? 'abusefilter-warning' : 'abusefilter-disallowed';
|
|
|
|
$data = [
|
|
|
|
'abusefilter' => [
|
|
|
|
'id' => $filter,
|
|
|
|
'description' => $filterDescription,
|
|
|
|
'actions' => $actionsTaken,
|
|
|
|
],
|
|
|
|
];
|
|
|
|
|
|
|
|
$message = ApiMessage::create( $error, $code, $data );
|
|
|
|
$statusForApi->fatal( $message );
|
2016-04-07 05:06:02 +00:00
|
|
|
}
|
|
|
|
|
Actually return errors for action=edit API
Setting 'apiHookResult' results in a "successful" response; if we want
to report an error, we need to use ApiMessage. We already were doing
this for action=upload. Now our action=edit API responses will be
consistent with MediaWiki and other extensions, and will be able to
take advantage of errorformat=html.
Since this breaks compatibility anyway, also remove some redundant
backwards-compatibility values from the output.
To avoid user interface regressions in VisualEditor, the changes
I3b9c4fef (in VE) and I106dbd3c (in MediaWiki) should be merged first.
Before:
{
"edit": {
"code": "abusefilter-disallowed",
"message": {
"key": "abusefilter-disallowed",
"params": [ ... ]
},
"abusefilter": { ... },
"info": "Hit AbuseFilter: Test filter disallow",
"warning": "This action has been automatically identified ...",
"result": "Failure"
}
}
After:
{
"errors": [
{
"code": "abusefilter-disallowed",
"data": {
"abusefilter": { ... },
},
"module": "edit",
"*": "This action has been automatically identified ..."
}
],
"*": "See http://localhost:3080/w/api.php for API usage. ..."
}
For comparison, a 'readonly' error:
{
"errors": [
{
"code": "readonly",
"data": {
"readonlyreason": "foo bar"
},
"module": "main",
"*": "The wiki is currently in read-only mode."
}
],
"*": "See http://localhost:3080/w/api.php for API usage. ..."
}
Bug: T229539
Depends-On: I106dbd3cbdbf7082b1d1f1c1106ece6b19c22a86
Depends-On: I3b9c4fefc0869ef7999c21cef754434febd852ec
Change-Id: I5424de387cbbcc9c85026b8cfeaf01635eee34a0
2019-08-01 01:48:08 +00:00
|
|
|
return $statusForApi;
|
2016-04-07 05:06:02 +00:00
|
|
|
}
|
|
|
|
|
2016-06-13 11:53:50 +00:00
|
|
|
/**
|
2018-06-07 11:29:22 +00:00
|
|
|
* @param WikiPage $wikiPage
|
|
|
|
* @param User $user
|
2016-10-09 17:20:42 +00:00
|
|
|
* @param string $content Content
|
2016-06-13 11:53:50 +00:00
|
|
|
* @param string $summary
|
|
|
|
* @param bool $minoredit
|
|
|
|
* @param bool $watchthis
|
|
|
|
* @param string $sectionanchor
|
2018-06-07 11:29:22 +00:00
|
|
|
* @param int $flags
|
2016-06-13 11:53:50 +00:00
|
|
|
* @param Revision $revision
|
2018-06-07 11:29:22 +00:00
|
|
|
* @param Status $status
|
2017-08-04 23:14:10 +00:00
|
|
|
* @param int $baseRevId
|
2016-06-13 11:53:50 +00:00
|
|
|
*/
|
2016-10-09 17:20:42 +00:00
|
|
|
public static function onPageContentSaveComplete(
|
2018-10-17 05:15:21 +00:00
|
|
|
WikiPage $wikiPage, User $user, $content, $summary, $minoredit, $watchthis, $sectionanchor,
|
|
|
|
$flags, Revision $revision, Status $status, $baseRevId
|
2012-02-10 23:41:05 +00:00
|
|
|
) {
|
2019-01-05 18:28:03 +00:00
|
|
|
$curTitle = $wikiPage->getTitle()->getPrefixedText();
|
2019-09-01 09:23:31 +00:00
|
|
|
if ( !isset( AbuseFilter::$logIds[ $curTitle ] ) ||
|
2018-12-09 16:58:57 +00:00
|
|
|
$wikiPage !== self::$lastEditPage
|
2012-02-10 23:41:05 +00:00
|
|
|
) {
|
2019-01-05 18:28:03 +00:00
|
|
|
// This isn't the edit AbuseFilter::$logIds was set for
|
|
|
|
AbuseFilter::$logIds = [];
|
2018-06-07 11:24:53 +00:00
|
|
|
return;
|
2012-02-10 23:41:05 +00:00
|
|
|
}
|
|
|
|
|
2018-12-09 16:58:57 +00:00
|
|
|
self::$lastEditPage = null;
|
2012-02-10 23:41:05 +00:00
|
|
|
|
2019-01-05 18:28:03 +00:00
|
|
|
$logs = AbuseFilter::$logIds[ $curTitle ];
|
|
|
|
if ( $logs[ 'local' ] ) {
|
2012-02-10 23:41:05 +00:00
|
|
|
// Now actually do our storage
|
2019-01-05 18:28:03 +00:00
|
|
|
$dbw = wfGetDB( DB_MASTER );
|
2012-02-10 23:41:05 +00:00
|
|
|
|
2019-01-05 18:28:03 +00:00
|
|
|
$dbw->update( 'abuse_filter_log',
|
|
|
|
[ 'afl_rev_id' => $revision->getId() ],
|
|
|
|
[ 'afl_id' => $logs['local'] ],
|
|
|
|
__METHOD__
|
|
|
|
);
|
|
|
|
}
|
2012-02-10 23:41:05 +00:00
|
|
|
|
2019-01-05 18:28:03 +00:00
|
|
|
if ( $logs[ 'global' ] ) {
|
2019-06-27 22:55:20 +00:00
|
|
|
$fdb = AbuseFilter::getCentralDB( DB_MASTER );
|
2019-01-05 18:28:03 +00:00
|
|
|
$fdb->update( 'abuse_filter_log',
|
|
|
|
[ 'afl_rev_id' => $revision->getId() ],
|
2019-06-27 22:55:20 +00:00
|
|
|
[ 'afl_id' => $logs['global'], 'afl_wiki' => wfWikiID() ],
|
2019-01-05 18:28:03 +00:00
|
|
|
__METHOD__
|
|
|
|
);
|
2012-02-10 23:41:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-03-11 20:32:31 +00:00
|
|
|
/**
|
2017-08-04 23:14:10 +00:00
|
|
|
* @param User $user
|
2017-10-06 18:52:31 +00:00
|
|
|
* @param array &$promote
|
2012-03-11 20:32:31 +00:00
|
|
|
*/
|
2018-10-17 05:15:21 +00:00
|
|
|
public static function onGetAutoPromoteGroups( User $user, &$promote ) {
|
2015-08-06 00:17:38 +00:00
|
|
|
if ( $promote ) {
|
2019-07-06 23:35:03 +00:00
|
|
|
$cache = ObjectCache::getInstance( 'hash' );
|
2019-08-10 13:17:20 +00:00
|
|
|
$key = AbuseFilter::autoPromoteBlockKey( $cache, $user );
|
2019-07-06 23:35:03 +00:00
|
|
|
$blocked = (bool)$cache->getWithSetCallback(
|
2019-08-10 13:17:20 +00:00
|
|
|
$key,
|
2019-07-06 23:35:03 +00:00
|
|
|
$cache::TTL_PROC_LONG,
|
|
|
|
function () use ( $user ) {
|
|
|
|
return AbuseFilter::getAutoPromoteBlockStatus( $user );
|
2016-04-30 21:30:46 +00:00
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
if ( $blocked ) {
|
2017-06-15 14:23:34 +00:00
|
|
|
$promote = [];
|
2015-08-06 00:17:38 +00:00
|
|
|
}
|
2008-06-27 06:18:51 +00:00
|
|
|
}
|
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2017-01-02 11:41:29 +00:00
|
|
|
/**
|
2019-06-25 10:43:54 +00:00
|
|
|
* Get variables used to filter a move.
|
|
|
|
* @todo Consider splitting into a separate class
|
|
|
|
* @internal Until we'll find the right place for it
|
|
|
|
*
|
|
|
|
* @param User $user
|
2017-01-02 11:41:29 +00:00
|
|
|
* @param Title $oldTitle
|
|
|
|
* @param Title $newTitle
|
|
|
|
* @param string $reason
|
2019-06-25 10:43:54 +00:00
|
|
|
* @return AbuseFilterVariableHolder
|
2017-01-02 11:41:29 +00:00
|
|
|
*/
|
2019-06-25 10:43:54 +00:00
|
|
|
public static function getMoveVars(
|
|
|
|
User $user,
|
2018-11-09 15:05:11 +00:00
|
|
|
Title $oldTitle,
|
|
|
|
Title $newTitle,
|
2019-06-25 10:43:54 +00:00
|
|
|
$reason
|
|
|
|
) : AbuseFilterVariableHolder {
|
2014-10-27 17:55:33 +00:00
|
|
|
$vars = new AbuseFilterVariableHolder;
|
|
|
|
$vars->addHolders(
|
|
|
|
AbuseFilter::generateUserVars( $user ),
|
|
|
|
AbuseFilter::generateTitleVars( $oldTitle, 'MOVED_FROM' ),
|
|
|
|
AbuseFilter::generateTitleVars( $newTitle, 'MOVED_TO' )
|
|
|
|
);
|
2019-01-02 10:30:38 +00:00
|
|
|
$vars->setVar( 'summary', $reason );
|
|
|
|
$vars->setVar( 'action', 'move' );
|
2019-06-25 10:43:54 +00:00
|
|
|
return $vars;
|
|
|
|
}
|
2014-10-27 17:55:33 +00:00
|
|
|
|
2019-06-25 10:43:54 +00:00
|
|
|
/**
|
|
|
|
* @param Title $oldTitle
|
|
|
|
* @param Title $newTitle
|
|
|
|
* @param User $user
|
|
|
|
* @param string $reason
|
|
|
|
* @param Status &$status
|
|
|
|
*/
|
|
|
|
public static function onTitleMove(
|
|
|
|
Title $oldTitle,
|
|
|
|
Title $newTitle,
|
|
|
|
User $user,
|
|
|
|
$reason,
|
|
|
|
Status &$status
|
|
|
|
) {
|
|
|
|
$vars = self::getMoveVars( $user, $oldTitle, $newTitle, $reason );
|
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, $oldTitle, $vars, 'default' );
|
|
|
|
$result = $runner->run();
|
2014-10-27 17:55:33 +00:00
|
|
|
$status->merge( $result );
|
|
|
|
}
|
|
|
|
|
2012-03-11 20:32:31 +00:00
|
|
|
/**
|
2019-06-25 10:43:54 +00:00
|
|
|
* Get variables for filtering a deletion.
|
|
|
|
* @todo Consider splitting into a separate class
|
|
|
|
* @internal Until we'll find the right place for it
|
|
|
|
*
|
2018-06-07 11:29:22 +00:00
|
|
|
* @param User $user
|
2019-06-25 10:43:54 +00:00
|
|
|
* @param WikiPage $article
|
2018-06-07 11:29:22 +00:00
|
|
|
* @param string $reason
|
2019-06-25 10:43:54 +00:00
|
|
|
* @return AbuseFilterVariableHolder
|
2012-03-11 20:32:31 +00:00
|
|
|
*/
|
2019-06-25 10:43:54 +00:00
|
|
|
public static function getDeleteVars(
|
|
|
|
User $user,
|
|
|
|
WikiPage $article,
|
|
|
|
$reason
|
|
|
|
) : AbuseFilterVariableHolder {
|
2009-02-26 12:15:14 +00:00
|
|
|
$vars = new AbuseFilterVariableHolder;
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2013-03-06 06:21:55 +00:00
|
|
|
$vars->addHolders(
|
2016-07-23 09:30:17 +00:00
|
|
|
AbuseFilter::generateUserVars( $user ),
|
2019-01-02 10:30:38 +00:00
|
|
|
AbuseFilter::generateTitleVars( $article->getTitle(), 'page' )
|
2013-03-06 06:21:55 +00:00
|
|
|
);
|
|
|
|
|
2019-01-02 10:30:38 +00:00
|
|
|
$vars->setVar( 'summary', $reason );
|
|
|
|
$vars->setVar( 'action', 'delete' );
|
2019-06-25 10:43:54 +00:00
|
|
|
return $vars;
|
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2019-06-25 10:43:54 +00:00
|
|
|
/**
|
|
|
|
* @param WikiPage $article
|
|
|
|
* @param User $user
|
|
|
|
* @param string $reason
|
|
|
|
* @param string &$error
|
|
|
|
* @param Status $status
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public static function onArticleDelete( WikiPage $article, User $user, $reason, &$error,
|
|
|
|
Status $status ) {
|
|
|
|
$vars = self::getDeleteVars( $user, $article, $reason );
|
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, $article->getTitle(), $vars, 'default' );
|
|
|
|
$filterResult = $runner->run();
|
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
|
|
|
$status->merge( $filterResult );
|
|
|
|
$error = $filterResult->isOK() ? '' : $filterResult->getHTML();
|
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
|
|
|
return $filterResult->isOK();
|
2008-06-27 06:18:51 +00:00
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2012-03-11 20:32:31 +00:00
|
|
|
/**
|
2017-08-04 23:14:10 +00:00
|
|
|
* @param RecentChange $recentChange
|
2012-03-11 20:32:31 +00:00
|
|
|
*/
|
2018-10-17 05:15:21 +00:00
|
|
|
public static function onRecentChangeSave( RecentChange $recentChange ) {
|
2009-10-07 13:57:06 +00:00
|
|
|
$title = Title::makeTitle(
|
2012-03-11 20:32:31 +00:00
|
|
|
$recentChange->getAttribute( 'rc_namespace' ),
|
|
|
|
$recentChange->getAttribute( 'rc_title' )
|
2009-10-07 13:57:06 +00:00
|
|
|
);
|
2019-01-01 15:35:01 +00:00
|
|
|
|
|
|
|
$logType = $recentChange->getAttribute( 'rc_log_type' ) ?: 'edit';
|
|
|
|
if ( $logType === 'newusers' ) {
|
|
|
|
$action = $recentChange->getAttribute( 'rc_log_action' ) === 'autocreate' ?
|
|
|
|
'autocreateaccount' :
|
|
|
|
'createaccount';
|
|
|
|
} else {
|
|
|
|
$action = $logType;
|
|
|
|
}
|
|
|
|
$actionID = AbuseFilter::getTaggingActionId(
|
|
|
|
$action,
|
|
|
|
$title,
|
|
|
|
$recentChange->getAttribute( 'rc_user_text' )
|
|
|
|
);
|
2009-01-23 19:23:19 +00:00
|
|
|
|
2016-09-25 19:51:56 +00:00
|
|
|
if ( isset( AbuseFilter::$tagsToSet[$actionID] ) ) {
|
|
|
|
$recentChange->addTags( AbuseFilter::$tagsToSet[$actionID] );
|
2018-12-29 10:06:04 +00:00
|
|
|
unset( AbuseFilter::$tagsToSet[$actionID] );
|
2009-01-28 19:08:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-17 13:00:14 +00:00
|
|
|
/**
|
|
|
|
* Purge all cache related to tags, both within AbuseFilter and in core
|
|
|
|
*/
|
|
|
|
public static function purgeTagCache() {
|
|
|
|
ChangeTags::purgeTagCacheAll();
|
|
|
|
|
|
|
|
$services = MediaWikiServices::getInstance();
|
|
|
|
$cache = $services->getMainWANObjectCache();
|
|
|
|
|
|
|
|
$cache->delete(
|
|
|
|
$cache->makeKey( self::FETCH_ALL_TAGS_KEY, 0 )
|
|
|
|
);
|
|
|
|
|
|
|
|
$cache->delete(
|
|
|
|
$cache->makeKey( self::FETCH_ALL_TAGS_KEY, 1 )
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2012-03-11 20:32:31 +00:00
|
|
|
/**
|
2015-01-29 13:07:04 +00:00
|
|
|
* @param array $tags
|
|
|
|
* @param bool $enabled
|
2012-03-11 20:32:31 +00:00
|
|
|
*/
|
2015-01-29 13:07:04 +00:00
|
|
|
private static function fetchAllTags( array &$tags, $enabled ) {
|
2015-03-17 13:00:14 +00:00
|
|
|
$services = MediaWikiServices::getInstance();
|
|
|
|
$cache = $services->getMainWANObjectCache();
|
2018-09-30 12:08:06 +00:00
|
|
|
$fname = __METHOD__;
|
2015-09-25 14:24:51 +00:00
|
|
|
|
2015-03-17 13:00:14 +00:00
|
|
|
$tags = $cache->getWithSetCallback(
|
|
|
|
// Key to store the cached value under
|
2017-07-08 18:49:13 +00:00
|
|
|
$cache->makeKey( self::FETCH_ALL_TAGS_KEY, (int)$enabled ),
|
2015-03-17 13:00:14 +00:00
|
|
|
// Time-to-live (in seconds)
|
|
|
|
$cache::TTL_MINUTE,
|
|
|
|
// Function that derives the new key value
|
2018-09-30 12:08:06 +00:00
|
|
|
function ( $oldValue, &$ttl, array &$setOpts ) use ( $enabled, $tags, $fname ) {
|
2015-03-17 13:00:14 +00:00
|
|
|
global $wgAbuseFilterCentralDB, $wgAbuseFilterIsCentral;
|
2009-01-28 19:08:18 +00:00
|
|
|
|
2015-03-17 13:00:14 +00:00
|
|
|
$dbr = wfGetDB( DB_REPLICA );
|
|
|
|
// Account for any snapshot/replica DB lag
|
|
|
|
$setOpts += Database::getCacheSetOptions( $dbr );
|
2015-09-25 14:24:51 +00:00
|
|
|
|
2018-04-04 21:14:25 +00:00
|
|
|
// This is a pretty awful hack.
|
2015-03-17 13:00:14 +00:00
|
|
|
|
|
|
|
$where = [ 'afa_consequence' => 'tag', 'af_deleted' => false ];
|
|
|
|
if ( $enabled ) {
|
|
|
|
$where['af_enabled'] = true;
|
|
|
|
}
|
|
|
|
$res = $dbr->select(
|
|
|
|
[ 'abuse_filter_action', 'abuse_filter' ],
|
|
|
|
'afa_parameters',
|
|
|
|
$where,
|
2018-09-30 12:08:06 +00:00
|
|
|
$fname,
|
2015-03-17 13:00:14 +00:00
|
|
|
[],
|
|
|
|
[ 'abuse_filter' => [ 'INNER JOIN', 'afa_filter=af_id' ] ]
|
2015-09-25 14:24:51 +00:00
|
|
|
);
|
2015-03-17 13:00:14 +00:00
|
|
|
|
|
|
|
foreach ( $res as $row ) {
|
|
|
|
$tags = array_filter(
|
|
|
|
array_merge( explode( "\n", $row->afa_parameters ), $tags )
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( $wgAbuseFilterCentralDB && !$wgAbuseFilterIsCentral ) {
|
2019-06-27 22:55:20 +00:00
|
|
|
$dbr = AbuseFilter::getCentralDB( DB_REPLICA );
|
2015-03-17 13:00:14 +00:00
|
|
|
$res = $dbr->select(
|
|
|
|
[ 'abuse_filter_action', 'abuse_filter' ],
|
|
|
|
'afa_parameters',
|
2019-06-27 22:55:20 +00:00
|
|
|
[ 'af_global' => 1 ] + $where,
|
2018-09-30 12:08:06 +00:00
|
|
|
$fname,
|
2015-03-17 13:00:14 +00:00
|
|
|
[],
|
|
|
|
[ 'abuse_filter' => [ 'INNER JOIN', 'afa_filter=af_id' ] ]
|
|
|
|
);
|
|
|
|
|
|
|
|
foreach ( $res as $row ) {
|
|
|
|
$tags = array_filter(
|
|
|
|
array_merge( explode( "\n", $row->afa_parameters ), $tags )
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $tags;
|
2015-09-25 14:24:51 +00:00
|
|
|
}
|
2015-03-17 13:00:14 +00:00
|
|
|
);
|
2015-09-25 14:24:51 +00:00
|
|
|
|
2017-02-26 09:55:31 +00:00
|
|
|
$tags[] = 'abusefilter-condition-limit';
|
2009-01-28 19:08:18 +00:00
|
|
|
}
|
2009-01-29 00:37:53 +00:00
|
|
|
|
2015-01-29 13:07:04 +00:00
|
|
|
/**
|
2017-10-06 18:52:31 +00:00
|
|
|
* @param string[] &$tags
|
2015-01-29 13:07:04 +00:00
|
|
|
*/
|
|
|
|
public static function onListDefinedTags( array &$tags ) {
|
2018-06-07 11:24:53 +00:00
|
|
|
self::fetchAllTags( $tags, false );
|
2015-01-29 13:07:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-10-06 18:52:31 +00:00
|
|
|
* @param string[] &$tags
|
2015-01-29 13:07:04 +00:00
|
|
|
*/
|
|
|
|
public static function onChangeTagsListActive( array &$tags ) {
|
2018-06-07 11:24:53 +00:00
|
|
|
self::fetchAllTags( $tags, true );
|
2015-01-29 13:07:04 +00:00
|
|
|
}
|
|
|
|
|
2011-02-10 17:32:57 +00:00
|
|
|
/**
|
2018-03-09 15:16:12 +00:00
|
|
|
* @param DatabaseUpdater $updater
|
2012-03-11 20:32:31 +00:00
|
|
|
* @throws MWException
|
2011-02-10 17:32:57 +00:00
|
|
|
*/
|
2018-03-09 15:16:12 +00:00
|
|
|
public static function onLoadExtensionSchemaUpdates( DatabaseUpdater $updater ) {
|
2017-08-07 23:11:38 +00:00
|
|
|
$dir = dirname( __DIR__ );
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2018-08-26 08:34:42 +00:00
|
|
|
if ( $updater->getDB()->getType() === 'mysql' || $updater->getDB()->getType() === 'sqlite' ) {
|
|
|
|
if ( $updater->getDB()->getType() === 'mysql' ) {
|
2017-06-15 14:23:34 +00:00
|
|
|
$updater->addExtensionUpdate( [ 'addTable', 'abuse_filter',
|
|
|
|
"$dir/abusefilter.tables.sql", true ] );
|
|
|
|
$updater->addExtensionUpdate( [ 'addTable', 'abuse_filter_history',
|
|
|
|
"$dir/db_patches/patch-abuse_filter_history.sql", true ] );
|
2011-11-08 03:07:01 +00:00
|
|
|
} else {
|
2017-06-15 14:23:34 +00:00
|
|
|
$updater->addExtensionUpdate( [ 'addTable', 'abuse_filter',
|
|
|
|
"$dir/abusefilter.tables.sqlite.sql", true ] );
|
|
|
|
$updater->addExtensionUpdate( [ 'addTable', 'abuse_filter_history',
|
|
|
|
"$dir/db_patches/patch-abuse_filter_history.sqlite.sql", true ] );
|
2010-10-03 15:51:04 +00:00
|
|
|
}
|
2017-06-15 14:23:34 +00:00
|
|
|
$updater->addExtensionUpdate( [
|
2015-09-28 18:03:35 +00:00
|
|
|
'addField', 'abuse_filter_history', 'afh_changed_fields',
|
|
|
|
"$dir/db_patches/patch-afh_changed_fields.sql", true
|
2017-06-15 14:23:34 +00:00
|
|
|
] );
|
|
|
|
$updater->addExtensionUpdate( [ 'addField', 'abuse_filter', 'af_deleted',
|
|
|
|
"$dir/db_patches/patch-af_deleted.sql", true ] );
|
|
|
|
$updater->addExtensionUpdate( [ 'addField', 'abuse_filter', 'af_actions',
|
|
|
|
"$dir/db_patches/patch-af_actions.sql", true ] );
|
|
|
|
$updater->addExtensionUpdate( [ 'addField', 'abuse_filter', 'af_global',
|
|
|
|
"$dir/db_patches/patch-global_filters.sql", true ] );
|
|
|
|
$updater->addExtensionUpdate( [ 'addField', 'abuse_filter_log', 'afl_rev_id',
|
|
|
|
"$dir/db_patches/patch-afl_action_id.sql", true ] );
|
2018-08-26 08:34:42 +00:00
|
|
|
if ( $updater->getDB()->getType() === 'mysql' ) {
|
2017-06-15 14:23:34 +00:00
|
|
|
$updater->addExtensionUpdate( [ 'addIndex', 'abuse_filter_log',
|
|
|
|
'filter_timestamp', "$dir/db_patches/patch-fix-indexes.sql", true ] );
|
2011-12-27 16:35:30 +00:00
|
|
|
} else {
|
2017-06-15 14:23:34 +00:00
|
|
|
$updater->addExtensionUpdate( [
|
2015-09-28 18:03:35 +00:00
|
|
|
'addIndex', 'abuse_filter_log', 'afl_filter_timestamp',
|
|
|
|
"$dir/db_patches/patch-fix-indexes.sqlite.sql", true
|
2017-06-15 14:23:34 +00:00
|
|
|
] );
|
2011-12-27 16:35:30 +00:00
|
|
|
}
|
2012-05-06 06:44:45 +00:00
|
|
|
|
2017-06-15 14:23:34 +00:00
|
|
|
$updater->addExtensionUpdate( [ 'addField', 'abuse_filter',
|
|
|
|
'af_group', "$dir/db_patches/patch-af_group.sql", true ] );
|
2012-08-03 21:55:35 +00:00
|
|
|
|
2018-08-26 08:34:42 +00:00
|
|
|
if ( $updater->getDB()->getType() === 'mysql' ) {
|
2017-06-15 14:23:34 +00:00
|
|
|
$updater->addExtensionUpdate( [
|
2015-09-28 18:03:35 +00:00
|
|
|
'addIndex', 'abuse_filter_log', 'wiki_timestamp',
|
|
|
|
"$dir/db_patches/patch-global_logging_wiki-index.sql", true
|
2017-06-15 14:23:34 +00:00
|
|
|
] );
|
2018-10-14 11:30:14 +00:00
|
|
|
$updater->addExtensionUpdate( [
|
|
|
|
'modifyField', 'abuse_filter_log', 'afl_namespace',
|
|
|
|
"$dir/db_patches/patch-afl-namespace_int.sql", true
|
|
|
|
] );
|
2012-08-03 21:55:35 +00:00
|
|
|
} else {
|
2017-06-15 14:23:34 +00:00
|
|
|
$updater->addExtensionUpdate( [
|
2015-09-28 18:03:35 +00:00
|
|
|
'addIndex', 'abuse_filter_log', 'afl_wiki_timestamp',
|
|
|
|
"$dir/db_patches/patch-global_logging_wiki-index.sqlite.sql", true
|
2017-06-15 14:23:34 +00:00
|
|
|
] );
|
|
|
|
$updater->addExtensionUpdate( [
|
2015-09-28 18:03:35 +00:00
|
|
|
'modifyField', 'abuse_filter_log', 'afl_namespace',
|
2018-10-14 11:30:14 +00:00
|
|
|
"$dir/db_patches/patch-afl-namespace_int.sqlite.sql", true
|
2017-06-15 14:23:34 +00:00
|
|
|
] );
|
2014-01-30 20:27:52 +00:00
|
|
|
}
|
2019-07-09 11:21:33 +00:00
|
|
|
if ( $updater->getDB()->getType() === 'mysql' ) {
|
|
|
|
$updater->addExtensionUpdate( [ 'dropField', 'abuse_filter_log',
|
|
|
|
'afl_log_id', "$dir/db_patches/patch-drop_afl_log_id.sql", true ] );
|
|
|
|
} else {
|
|
|
|
$updater->addExtensionUpdate( [ 'dropField', 'abuse_filter_log',
|
|
|
|
'afl_log_id', "$dir/db_patches/patch-drop_afl_log_id.sqlite.sql", true ] );
|
|
|
|
}
|
2018-08-26 08:34:42 +00:00
|
|
|
} elseif ( $updater->getDB()->getType() === 'postgres' ) {
|
2017-06-15 14:23:34 +00:00
|
|
|
$updater->addExtensionUpdate( [
|
|
|
|
'addTable', 'abuse_filter', "$dir/abusefilter.tables.pg.sql", true ] );
|
|
|
|
$updater->addExtensionUpdate( [
|
2015-09-28 18:03:35 +00:00
|
|
|
'addTable', 'abuse_filter_history',
|
|
|
|
"$dir/db_patches/patch-abuse_filter_history.pg.sql", true
|
2017-06-15 14:23:34 +00:00
|
|
|
] );
|
|
|
|
$updater->addExtensionUpdate( [
|
|
|
|
'addPgField', 'abuse_filter', 'af_actions', "TEXT NOT NULL DEFAULT ''" ] );
|
|
|
|
$updater->addExtensionUpdate( [
|
|
|
|
'addPgField', 'abuse_filter', 'af_deleted', 'SMALLINT NOT NULL DEFAULT 0' ] );
|
|
|
|
$updater->addExtensionUpdate( [
|
|
|
|
'addPgField', 'abuse_filter', 'af_global', 'SMALLINT NOT NULL DEFAULT 0' ] );
|
2018-08-23 13:08:47 +00:00
|
|
|
$updater->addExtensionUpdate( [
|
|
|
|
'addPgField', 'abuse_filter', 'af_group', "TEXT NOT NULL DEFAULT 'default'" ] );
|
|
|
|
$updater->addExtensionUpdate( [
|
|
|
|
'addPgExtIndex', 'abuse_filter', 'abuse_filter_group_enabled_id',
|
|
|
|
"(af_group, af_enabled, af_id)"
|
|
|
|
] );
|
|
|
|
$updater->addExtensionUpdate( [
|
|
|
|
'addPgField', 'abuse_filter_history', 'afh_group', "TEXT" ] );
|
2017-06-15 14:23:34 +00:00
|
|
|
$updater->addExtensionUpdate( [
|
|
|
|
'addPgField', 'abuse_filter_log', 'afl_wiki', 'TEXT' ] );
|
|
|
|
$updater->addExtensionUpdate( [
|
|
|
|
'addPgField', 'abuse_filter_log', 'afl_deleted', 'SMALLINT' ] );
|
|
|
|
$updater->addExtensionUpdate( [
|
2018-08-23 13:08:47 +00:00
|
|
|
'setDefault', 'abuse_filter_log', 'afl_deleted', '0' ] );
|
2017-06-15 14:23:34 +00:00
|
|
|
$updater->addExtensionUpdate( [
|
2018-08-23 13:08:47 +00:00
|
|
|
'changeNullableField', 'abuse_filter_log', 'afl_deleted', 'NOT NULL', true ] );
|
2017-06-15 14:23:34 +00:00
|
|
|
$updater->addExtensionUpdate( [
|
2018-08-23 13:08:47 +00:00
|
|
|
'addPgField', 'abuse_filter_log', 'afl_patrolled_by', 'INTEGER' ] );
|
|
|
|
$updater->addExtensionUpdate( [
|
|
|
|
'addPgField', 'abuse_filter_log', 'afl_rev_id', 'INTEGER' ] );
|
|
|
|
$updater->addExtensionUpdate( [
|
|
|
|
'changeField', 'abuse_filter_log', 'afl_filter', 'TEXT', '' ] );
|
2017-06-15 14:23:34 +00:00
|
|
|
$updater->addExtensionUpdate( [
|
2018-01-19 20:13:28 +00:00
|
|
|
'changeField', 'abuse_filter_log', 'afl_namespace', "INTEGER", '' ] );
|
2015-02-20 18:25:55 +00:00
|
|
|
$updater->addExtensionUpdate( [
|
2018-08-23 13:08:47 +00:00
|
|
|
'dropPgIndex', 'abuse_filter_log', 'abuse_filter_log_filter' ] );
|
2015-02-20 18:25:55 +00:00
|
|
|
$updater->addExtensionUpdate( [
|
2018-08-23 13:08:47 +00:00
|
|
|
'dropPgIndex', 'abuse_filter_log', 'abuse_filter_log_ip' ] );
|
|
|
|
$updater->addExtensionUpdate( [
|
|
|
|
'dropPgIndex', 'abuse_filter_log', 'abuse_filter_log_title' ] );
|
|
|
|
$updater->addExtensionUpdate( [
|
|
|
|
'dropPgIndex', 'abuse_filter_log', 'abuse_filter_log_user' ] );
|
|
|
|
$updater->addExtensionUpdate( [
|
|
|
|
'dropPgIndex', 'abuse_filter_log', 'abuse_filter_log_user_text' ] );
|
|
|
|
$updater->addExtensionUpdate( [
|
|
|
|
'dropPgIndex', 'abuse_filter_log', 'abuse_filter_log_wiki' ] );
|
|
|
|
$updater->addExtensionUpdate( [
|
|
|
|
'addPgExtIndex', 'abuse_filter_log', 'abuse_filter_log_filter_timestamp',
|
|
|
|
'(afl_filter,afl_timestamp)'
|
2015-02-20 18:25:55 +00:00
|
|
|
] );
|
|
|
|
$updater->addExtensionUpdate( [
|
2018-08-23 13:08:47 +00:00
|
|
|
'addPgExtIndex', 'abuse_filter_log', 'abuse_filter_log_user_timestamp',
|
|
|
|
'(afl_user,afl_user_text,afl_timestamp)'
|
|
|
|
] );
|
|
|
|
$updater->addExtensionUpdate( [
|
|
|
|
'addPgExtIndex', 'abuse_filter_log', 'abuse_filter_log_page_timestamp',
|
|
|
|
'(afl_namespace,afl_title,afl_timestamp)'
|
|
|
|
] );
|
|
|
|
$updater->addExtensionUpdate( [
|
|
|
|
'addPgExtIndex', 'abuse_filter_log', 'abuse_filter_log_ip_timestamp',
|
|
|
|
'(afl_ip, afl_timestamp)'
|
|
|
|
] );
|
|
|
|
$updater->addExtensionUpdate( [
|
|
|
|
'addPgExtIndex', 'abuse_filter_log', 'abuse_filter_log_rev_id',
|
|
|
|
'(afl_rev_id)'
|
|
|
|
] );
|
|
|
|
$updater->addExtensionUpdate( [
|
|
|
|
'addPgExtIndex', 'abuse_filter_log', 'abuse_filter_log_wiki_timestamp',
|
|
|
|
'(afl_wiki,afl_timestamp)'
|
|
|
|
] );
|
2019-03-28 13:55:37 +00:00
|
|
|
$updater->addExtensionUpdate( [
|
|
|
|
'dropPgField', 'abuse_filter_log', 'afl_log_id' ] );
|
2011-12-27 16:35:30 +00:00
|
|
|
}
|
|
|
|
|
2017-06-15 14:23:34 +00:00
|
|
|
$updater->addExtensionUpdate( [ [ __CLASS__, 'createAbuseFilterUser' ] ] );
|
2018-09-08 17:15:49 +00:00
|
|
|
$updater->addPostDatabaseUpdateMaintenance( 'NormalizeThrottleParameters' );
|
2012-03-19 09:50:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Updater callback to create the AbuseFilter user after the user tables have been updated.
|
2017-01-02 11:41:29 +00:00
|
|
|
* @param DatabaseUpdater $updater
|
2012-03-19 09:50:48 +00:00
|
|
|
*/
|
2018-10-17 05:15:21 +00:00
|
|
|
public static function createAbuseFilterUser( DatabaseUpdater $updater ) {
|
2015-09-17 15:31:51 +00:00
|
|
|
$username = wfMessage( 'abusefilter-blocker' )->inContentLanguage()->text();
|
|
|
|
$user = User::newFromName( $username );
|
2011-12-27 16:35:30 +00:00
|
|
|
|
2012-01-18 02:37:14 +00:00
|
|
|
if ( $user && !$updater->updateRowExists( 'create abusefilter-blocker-user' ) ) {
|
2017-08-08 12:03:56 +00:00
|
|
|
$user = User::newSystemUser( $username, [ 'steal' => true ] );
|
2017-10-21 06:09:36 +00:00
|
|
|
$updater->insertUpdateRow( 'create abusefilter-blocker-user' );
|
2018-04-04 21:14:25 +00:00
|
|
|
// Promote user so it doesn't look too crazy.
|
2017-10-21 06:09:36 +00:00
|
|
|
$user->addGroup( 'sysop' );
|
2009-03-23 14:18:35 +00:00
|
|
|
}
|
2009-01-29 00:37:53 +00:00
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2012-03-11 20:32:31 +00:00
|
|
|
/**
|
2017-10-06 18:52:31 +00:00
|
|
|
* @param int $id
|
2017-08-04 23:14:10 +00:00
|
|
|
* @param Title $nt
|
2017-10-06 18:52:31 +00:00
|
|
|
* @param array &$tools
|
2017-08-08 12:03:56 +00:00
|
|
|
* @param SpecialPage $sp for context
|
2012-03-11 20:32:31 +00:00
|
|
|
*/
|
2018-10-17 05:15:21 +00:00
|
|
|
public static function onContributionsToolLinks( $id, Title $nt, array &$tools, SpecialPage $sp ) {
|
2018-02-05 09:19:06 +00:00
|
|
|
$username = $nt->getText();
|
|
|
|
if ( $sp->getUser()->isAllowed( 'abusefilter-log' ) && !IP::isValidRange( $username ) ) {
|
2017-08-08 12:03:56 +00:00
|
|
|
$linkRenderer = $sp->getLinkRenderer();
|
|
|
|
$tools['abuselog'] = $linkRenderer->makeLink(
|
2015-09-28 18:03:35 +00:00
|
|
|
SpecialPage::getTitleFor( 'AbuseLog' ),
|
2017-08-08 12:03:56 +00:00
|
|
|
$sp->msg( 'abusefilter-log-linkoncontribs' )->text(),
|
|
|
|
[ 'title' => $sp->msg( 'abusefilter-log-linkoncontribs-text',
|
2018-02-05 09:19:06 +00:00
|
|
|
$username )->text() ],
|
|
|
|
[ 'wpSearchUser' => $username ]
|
2014-03-25 15:57:01 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param IContextSource $context
|
|
|
|
* @param LinkRenderer $linkRenderer
|
|
|
|
* @param string[] &$links
|
|
|
|
*/
|
|
|
|
public static function onHistoryPageToolLinks(
|
|
|
|
IContextSource $context,
|
|
|
|
LinkRenderer $linkRenderer,
|
|
|
|
array &$links
|
|
|
|
) {
|
|
|
|
$user = $context->getUser();
|
|
|
|
if ( $user->isAllowed( 'abusefilter-log' ) ) {
|
|
|
|
$links[] = $linkRenderer->makeLink(
|
|
|
|
SpecialPage::getTitleFor( 'AbuseLog' ),
|
|
|
|
$context->msg( 'abusefilter-log-linkonhistory' )->text(),
|
|
|
|
[ 'title' => $context->msg( 'abusefilter-log-linkonhistory-text' )->text() ],
|
|
|
|
[ 'wpSearchTitle' => $context->getTitle()->getPrefixedText() ]
|
2015-09-28 18:03:35 +00:00
|
|
|
);
|
2009-04-24 01:53:12 +00:00
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
}
|
|
|
|
|
2019-08-22 23:19:42 +00:00
|
|
|
/**
|
|
|
|
* @param IContextSource $context
|
|
|
|
* @param LinkRenderer $linkRenderer
|
|
|
|
* @param string[] &$links
|
|
|
|
*/
|
|
|
|
public static function onUndeletePageToolLinks(
|
|
|
|
IContextSource $context,
|
|
|
|
LinkRenderer $linkRenderer,
|
|
|
|
array &$links
|
|
|
|
) {
|
|
|
|
$user = $context->getUser();
|
|
|
|
if ( $user->isAllowed( 'abusefilter-log' ) ) {
|
|
|
|
$links[] = $linkRenderer->makeLink(
|
|
|
|
SpecialPage::getTitleFor( 'AbuseLog' ),
|
|
|
|
$context->msg( 'abusefilter-log-linkonundelete' )->text(),
|
|
|
|
[ 'title' => $context->msg( 'abusefilter-log-linkonundelete-text' )->text() ],
|
|
|
|
[ 'wpSearchTitle' => $context->getTitle()->getPrefixedText() ]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-03-11 20:32:31 +00:00
|
|
|
/**
|
2016-08-03 14:30:18 +00:00
|
|
|
* Filter an upload.
|
|
|
|
*
|
2016-06-17 15:23:42 +00:00
|
|
|
* @param UploadBase $upload
|
|
|
|
* @param User $user
|
2019-05-15 18:38:16 +00:00
|
|
|
* @param array|null $props
|
2016-06-17 15:23:42 +00:00
|
|
|
* @param string $comment
|
|
|
|
* @param string $pageText
|
2016-06-20 21:58:17 +00:00
|
|
|
* @param array|ApiMessage &$error
|
2016-06-17 15:23:42 +00:00
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public static function onUploadVerifyUpload( UploadBase $upload, User $user,
|
2019-05-15 18:38:16 +00:00
|
|
|
$props, $comment, $pageText, &$error
|
2016-06-17 15:23:42 +00:00
|
|
|
) {
|
|
|
|
return self::filterUpload( 'upload', $upload, $user, $props, $comment, $pageText, $error );
|
|
|
|
}
|
|
|
|
|
2016-08-03 14:30:18 +00:00
|
|
|
/**
|
|
|
|
* Filter an upload to stash. If a filter doesn't need to check the page contents or
|
|
|
|
* upload comment, it can use `action='stashupload'` to provide better experience to e.g.
|
|
|
|
* UploadWizard (rejecting files immediately, rather than after the user adds the details).
|
|
|
|
*
|
|
|
|
* @param UploadBase $upload
|
|
|
|
* @param User $user
|
|
|
|
* @param array $props
|
|
|
|
* @param array|ApiMessage &$error
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public static function onUploadStashFile( UploadBase $upload, User $user,
|
|
|
|
array $props, &$error
|
|
|
|
) {
|
|
|
|
return self::filterUpload( 'stashupload', $upload, $user, $props, null, null, $error );
|
|
|
|
}
|
|
|
|
|
2016-06-17 15:23:42 +00:00
|
|
|
/**
|
2019-06-25 10:43:54 +00:00
|
|
|
* Get variables for filtering an upload.
|
|
|
|
* @todo Consider splitting into a separate class
|
|
|
|
* @internal Until we'll find the right place for it
|
2016-06-17 15:23:42 +00:00
|
|
|
*
|
2019-06-25 10:43:54 +00:00
|
|
|
* @param string $action
|
|
|
|
* @param User $user
|
|
|
|
* @param Title $title
|
2016-06-17 15:23:42 +00:00
|
|
|
* @param UploadBase $upload
|
2019-06-25 10:43:54 +00:00
|
|
|
* @param string $summary
|
|
|
|
* @param string $text
|
|
|
|
* @param array $props
|
|
|
|
* @return AbuseFilterVariableHolder|null
|
2016-06-17 15:23:42 +00:00
|
|
|
*/
|
2019-06-25 10:43:54 +00:00
|
|
|
public static function getUploadVars(
|
|
|
|
$action,
|
|
|
|
User $user,
|
|
|
|
Title $title,
|
|
|
|
UploadBase $upload,
|
|
|
|
$summary,
|
|
|
|
$text,
|
|
|
|
$props
|
2016-06-17 15:23:42 +00:00
|
|
|
) {
|
2019-05-15 18:38:16 +00:00
|
|
|
$mimeAnalyzer = MediaWikiServices::getInstance()->getMimeAnalyzer();
|
|
|
|
if ( !$props ) {
|
|
|
|
$props = ( new MWFileProps( $mimeAnalyzer ) )->getPropsFromPath(
|
|
|
|
$upload->getTempPath(),
|
|
|
|
true
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2016-06-17 15:23:42 +00:00
|
|
|
$vars = new AbuseFilterVariableHolder;
|
2013-03-06 06:21:55 +00:00
|
|
|
$vars->addHolders(
|
2016-06-17 15:23:42 +00:00
|
|
|
AbuseFilter::generateUserVars( $user ),
|
2019-01-02 10:30:38 +00:00
|
|
|
AbuseFilter::generateTitleVars( $title, 'page' )
|
2009-10-07 13:57:06 +00:00
|
|
|
);
|
2019-01-02 10:30:38 +00:00
|
|
|
$vars->setVar( 'action', $action );
|
2013-02-28 01:25:59 +00:00
|
|
|
|
2015-11-23 09:55:06 +00:00
|
|
|
// We use the hexadecimal version of the file sha1.
|
|
|
|
// Use UploadBase::getTempFileSha1Base36 so that we don't have to calculate the sha1 sum again
|
2015-09-28 18:03:35 +00:00
|
|
|
$sha1 = Wikimedia\base_convert( $upload->getTempFileSha1Base36(), 36, 16, 40 );
|
2013-02-28 01:25:59 +00:00
|
|
|
|
2018-07-11 23:54:48 +00:00
|
|
|
// This is the same as AbuseFilter::getUploadVarsFromRCRow, but from a different source
|
2013-02-28 01:25:59 +00:00
|
|
|
$vars->setVar( 'file_sha1', $sha1 );
|
2015-01-08 04:56:35 +00:00
|
|
|
$vars->setVar( 'file_size', $upload->getFileSize() );
|
2009-10-07 13:57:06 +00:00
|
|
|
|
Add more file_* variables for file metadata
* file_mime
The MIME type of the file, e.g. 'image/png'.
* file_mediatype
The media type of the file, one of 'UNKNOWN', 'BITMAP', 'DRAWING',
'AUDIO', 'VIDEO', 'MULTIMEDIA', 'OFFICE', 'TEXT', 'EXECUTABLE', 'ARCHIVE'.
* file_width
Width of the image in pixels, or 0 if it's inapplicable (e.g. for
audio files).
* file_height
Height of the image in pixels, or 0 if it's inapplicable (e.g. for
audio files).
* file_bits_per_channel
Bits per color channel of the image, or 0 if it's inapplicable (e.g.
for audio files). The most common value is 8.
Bug: T131643
Change-Id: Id355515a18d3674393332c0f4094e34f9f522623
2016-04-04 19:12:08 +00:00
|
|
|
$vars->setVar( 'file_mime', $props['mime'] );
|
2019-05-15 18:38:16 +00:00
|
|
|
$vars->setVar( 'file_mediatype', $mimeAnalyzer->getMediaType( null, $props['mime'] ) );
|
Add more file_* variables for file metadata
* file_mime
The MIME type of the file, e.g. 'image/png'.
* file_mediatype
The media type of the file, one of 'UNKNOWN', 'BITMAP', 'DRAWING',
'AUDIO', 'VIDEO', 'MULTIMEDIA', 'OFFICE', 'TEXT', 'EXECUTABLE', 'ARCHIVE'.
* file_width
Width of the image in pixels, or 0 if it's inapplicable (e.g. for
audio files).
* file_height
Height of the image in pixels, or 0 if it's inapplicable (e.g. for
audio files).
* file_bits_per_channel
Bits per color channel of the image, or 0 if it's inapplicable (e.g.
for audio files). The most common value is 8.
Bug: T131643
Change-Id: Id355515a18d3674393332c0f4094e34f9f522623
2016-04-04 19:12:08 +00:00
|
|
|
$vars->setVar( 'file_width', $props['width'] );
|
|
|
|
$vars->setVar( 'file_height', $props['height'] );
|
|
|
|
$vars->setVar( 'file_bits_per_channel', $props['bits'] );
|
|
|
|
|
2016-06-17 15:23:42 +00:00
|
|
|
// We only have the upload comment and page text when using the UploadVerifyUpload hook
|
|
|
|
if ( $summary !== null && $text !== null ) {
|
|
|
|
// This block is adapted from self::filterEdit()
|
|
|
|
if ( $title->exists() ) {
|
|
|
|
$page = WikiPage::factory( $title );
|
|
|
|
$revision = $page->getRevision();
|
|
|
|
if ( !$revision ) {
|
2019-06-25 10:43:54 +00:00
|
|
|
return null;
|
2016-06-17 15:23:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
$oldcontent = $revision->getContent( Revision::RAW );
|
|
|
|
$oldtext = AbuseFilter::contentToString( $oldcontent );
|
|
|
|
|
|
|
|
// Cache article object so we can share a parse operation
|
|
|
|
$articleCacheKey = $title->getNamespace() . ':' . $title->getText();
|
|
|
|
AFComputedVariable::$articleCache[$articleCacheKey] = $page;
|
|
|
|
|
|
|
|
// Page text is ignored for uploads when the page already exists
|
|
|
|
$text = $oldtext;
|
|
|
|
} else {
|
|
|
|
$page = null;
|
|
|
|
$oldtext = '';
|
|
|
|
}
|
|
|
|
|
|
|
|
// Load vars for filters to check
|
|
|
|
$vars->setVar( 'summary', $summary );
|
|
|
|
$vars->setVar( 'old_wikitext', $oldtext );
|
|
|
|
$vars->setVar( 'new_wikitext', $text );
|
|
|
|
// TODO: set old_content and new_content vars, use them
|
|
|
|
$vars->addHolders( AbuseFilter::getEditVars( $title, $page ) );
|
|
|
|
}
|
2019-06-25 10:43:54 +00:00
|
|
|
return $vars;
|
|
|
|
}
|
2016-06-17 15:23:42 +00:00
|
|
|
|
2019-06-25 10:43:54 +00:00
|
|
|
/**
|
|
|
|
* Implementation for UploadStashFile and UploadVerifyUpload hooks.
|
|
|
|
*
|
|
|
|
* @param string $action 'upload' or 'stashupload'
|
|
|
|
* @param UploadBase $upload
|
|
|
|
* @param User $user User performing the action
|
|
|
|
* @param array|null $props File properties, as returned by MWFileProps::getPropsFromPath().
|
|
|
|
* @param string|null $summary Upload log comment (also used as edit summary)
|
|
|
|
* @param string|null $text File description page text (only used for new uploads)
|
|
|
|
* @param array|ApiMessage &$error
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public static function filterUpload( $action, UploadBase $upload, User $user,
|
|
|
|
$props, $summary, $text, &$error
|
|
|
|
) {
|
|
|
|
$title = $upload->getTitle();
|
|
|
|
if ( $title === null ) {
|
|
|
|
// T144265: This could happen for 'stashupload' if the specified title is invalid.
|
|
|
|
// Let UploadBase warn the user about that, and we'll filter later.
|
|
|
|
$logger = LoggerFactory::getInstance( 'AbuseFilter' );
|
|
|
|
$logger->warning( __METHOD__ . " received a null title. Action: $action." );
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
$vars = self::getUploadVars( $action, $user, $title, $upload, $summary, $text, $props );
|
|
|
|
if ( $vars === null ) {
|
|
|
|
return true;
|
|
|
|
}
|
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, 'default' );
|
|
|
|
$filterResult = $runner->run();
|
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
|
|
|
if ( !$filterResult->isOK() ) {
|
Actually return errors for action=edit API
Setting 'apiHookResult' results in a "successful" response; if we want
to report an error, we need to use ApiMessage. We already were doing
this for action=upload. Now our action=edit API responses will be
consistent with MediaWiki and other extensions, and will be able to
take advantage of errorformat=html.
Since this breaks compatibility anyway, also remove some redundant
backwards-compatibility values from the output.
To avoid user interface regressions in VisualEditor, the changes
I3b9c4fef (in VE) and I106dbd3c (in MediaWiki) should be merged first.
Before:
{
"edit": {
"code": "abusefilter-disallowed",
"message": {
"key": "abusefilter-disallowed",
"params": [ ... ]
},
"abusefilter": { ... },
"info": "Hit AbuseFilter: Test filter disallow",
"warning": "This action has been automatically identified ...",
"result": "Failure"
}
}
After:
{
"errors": [
{
"code": "abusefilter-disallowed",
"data": {
"abusefilter": { ... },
},
"module": "edit",
"*": "This action has been automatically identified ..."
}
],
"*": "See http://localhost:3080/w/api.php for API usage. ..."
}
For comparison, a 'readonly' error:
{
"errors": [
{
"code": "readonly",
"data": {
"readonlyreason": "foo bar"
},
"module": "main",
"*": "The wiki is currently in read-only mode."
}
],
"*": "See http://localhost:3080/w/api.php for API usage. ..."
}
Bug: T229539
Depends-On: I106dbd3cbdbf7082b1d1f1c1106ece6b19c22a86
Depends-On: I3b9c4fefc0869ef7999c21cef754434febd852ec
Change-Id: I5424de387cbbcc9c85026b8cfeaf01635eee34a0
2019-08-01 01:48:08 +00:00
|
|
|
// Produce a useful error message for API edits
|
|
|
|
$filterResultApi = self::getApiStatus( $filterResult );
|
|
|
|
// @todo Return all errors instead of only the first one
|
|
|
|
$error = $filterResultApi->getErrors()[0]['message'];
|
2013-02-28 01:25:59 +00:00
|
|
|
}
|
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
|
|
|
return $filterResult->isOK();
|
2009-10-07 13:57:06 +00:00
|
|
|
}
|
2011-08-26 20:12:34 +00:00
|
|
|
|
2014-07-10 03:57:02 +00:00
|
|
|
/**
|
|
|
|
* Tables that Extension:UserMerge needs to update
|
|
|
|
*
|
2017-10-06 18:52:31 +00:00
|
|
|
* @param array &$updateFields
|
2014-07-10 03:57:02 +00:00
|
|
|
*/
|
|
|
|
public static function onUserMergeAccountFields( array &$updateFields ) {
|
2017-06-15 14:23:34 +00:00
|
|
|
$updateFields[] = [ 'abuse_filter', 'af_user', 'af_user_text' ];
|
|
|
|
$updateFields[] = [ 'abuse_filter_log', 'afl_user', 'afl_user_text' ];
|
|
|
|
$updateFields[] = [ 'abuse_filter_history', 'afh_user', 'afh_user_text' ];
|
2014-07-10 03:57:02 +00:00
|
|
|
}
|
|
|
|
|
2016-01-27 01:37:58 +00:00
|
|
|
/**
|
|
|
|
* Warms the cache for getLastPageAuthors() - T116557
|
|
|
|
*
|
|
|
|
* @param WikiPage $page
|
|
|
|
* @param Content $content
|
|
|
|
* @param ParserOutput $output
|
2016-06-13 11:53:50 +00:00
|
|
|
* @param string $summary
|
2018-05-25 23:31:49 +00:00
|
|
|
* @param User|null $user
|
2016-01-27 01:37:58 +00:00
|
|
|
*/
|
|
|
|
public static function onParserOutputStashForEdit(
|
2016-06-13 11:53:50 +00:00
|
|
|
WikiPage $page, Content $content, ParserOutput $output, $summary = '', $user = null
|
2016-01-27 01:37:58 +00:00
|
|
|
) {
|
2019-01-10 23:21:28 +00:00
|
|
|
$oldRevision = $page->getRevision();
|
|
|
|
if ( !$oldRevision ) {
|
2016-06-13 11:53:50 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-01-10 23:21:28 +00:00
|
|
|
$oldContent = $oldRevision->getContent( Revision::RAW );
|
2016-06-13 11:53:50 +00:00
|
|
|
$user = $user ?: RequestContext::getMain()->getUser();
|
2019-01-10 23:21:28 +00:00
|
|
|
$oldAfText = AbuseFilter::revisionToString( $oldRevision, $user );
|
|
|
|
|
|
|
|
// XXX: This makes the assumption that this method is only ever called for the main slot.
|
|
|
|
// Which right now holds true, but any more fancy MCR stuff will likely break here...
|
|
|
|
$slot = SlotRecord::MAIN;
|
|
|
|
|
|
|
|
// XXX: Recreate what the new revision will probably be so we can get the full AF
|
|
|
|
// text for all slots
|
|
|
|
$oldRevRecord = $oldRevision->getRevisionRecord();
|
|
|
|
$newRevision = MutableRevisionRecord::newFromParentRevision( $oldRevRecord );
|
|
|
|
$newRevision->setContent( $slot, $content );
|
|
|
|
$text = AbuseFilter::revisionToString( $newRevision, $user );
|
2016-06-13 11:53:50 +00:00
|
|
|
|
2016-06-27 18:30:29 +00:00
|
|
|
// Cache any resulting filter matches.
|
|
|
|
// Do this outside the synchronous stash lock to avoid any chance of slowdown.
|
|
|
|
DeferredUpdates::addCallableUpdate(
|
2019-01-10 23:21:28 +00:00
|
|
|
function () use ( $user, $page, $summary, $content, $text, $oldContent, $oldAfText ) {
|
2016-06-27 18:30:29 +00:00
|
|
|
$vars = self::newVariableHolderForEdit(
|
2019-08-04 13:06:22 +00:00
|
|
|
$user, $page->getTitle(), $page, $summary, $content, $text, $oldAfText, $oldContent
|
2016-06-27 18:30:29 +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, $page->getTitle(), $vars, 'default' );
|
|
|
|
$runner->runForStash();
|
2016-06-27 18:30:29 +00:00
|
|
|
},
|
|
|
|
DeferredUpdates::PRESEND
|
2016-06-13 11:53:50 +00:00
|
|
|
);
|
2016-01-27 01:37:58 +00:00
|
|
|
}
|
2018-08-25 12:44:01 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Setup tables to emulate global filters, used in AbuseFilterConsequencesTest.
|
|
|
|
*
|
|
|
|
* @param IMaintainableDatabase $db
|
|
|
|
* @param string $prefix The prefix used in unit tests
|
|
|
|
* @suppress PhanUndeclaredClassConstant AbuseFilterConsequencesTest is in AutoloadClasses
|
|
|
|
* @suppress PhanUndeclaredClassStaticProperty AbuseFilterConsequencesTest is in AutoloadClasses
|
|
|
|
*/
|
|
|
|
public static function onUnitTestsAfterDatabaseSetup( IMaintainableDatabase $db, $prefix ) {
|
|
|
|
$externalPrefix = AbuseFilterConsequencesTest::DB_EXTERNAL_PREFIX;
|
|
|
|
if ( $db->tableExists( $externalPrefix . AbuseFilterConsequencesTest::$externalTables[0] ) ) {
|
|
|
|
// Check a random table to avoid unnecessary table creations. See T155147.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ( AbuseFilterConsequencesTest::$externalTables as $table ) {
|
|
|
|
// Don't create them as temporary, as we'll access the DB via another connection
|
|
|
|
$db->duplicateTableStructure(
|
|
|
|
"$prefix$table",
|
|
|
|
"$prefix$externalPrefix$table",
|
|
|
|
false,
|
|
|
|
__METHOD__
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Drop tables used for global filters in AbuseFilterConsequencesTest.
|
|
|
|
* Note: this has the same problem as T201290.
|
|
|
|
*
|
|
|
|
* @suppress PhanUndeclaredClassConstant AbuseFilterConsequencesTest is in AutoloadClasses
|
|
|
|
* @suppress PhanUndeclaredClassStaticProperty AbuseFilterConsequencesTest is in AutoloadClasses
|
|
|
|
*/
|
|
|
|
public static function onUnitTestsBeforeDatabaseTeardown() {
|
|
|
|
$db = wfGetDB( DB_MASTER );
|
|
|
|
foreach ( AbuseFilterConsequencesTest::$externalTables as $table ) {
|
|
|
|
$db->dropTable( AbuseFilterConsequencesTest::DB_EXTERNAL_PREFIX . $table );
|
|
|
|
}
|
|
|
|
}
|
2008-06-27 06:18:51 +00:00
|
|
|
}
|