mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/AbuseFilter.git
synced 2024-09-23 10:18:22 +00:00
(bug 42064) AbuseFilter + EditFilterMergedContent
This makes AbuseFilter use EditFilterMergedContent if support for the ContentHandler infrastructure is present. This means living without some nice bits of context, because EditFilterMergedContent doesn't provide an EditPage object. This requires core change I99a19c93 to work correctly. Change-Id: Ibb9d4c9a36b8a199213958b920902e8006c71fe8
This commit is contained in:
parent
b8b799630b
commit
f3788c4f0c
|
@ -1867,12 +1867,17 @@ class AbuseFilter {
|
|||
|
||||
/**
|
||||
* @param $title Title
|
||||
* @param $article Array|Article
|
||||
* @param $article Page|null
|
||||
* @return AbuseFilterVariableHolder
|
||||
*/
|
||||
public static function getEditVars( $title, $article = null ) {
|
||||
public static function getEditVars( $title, Page $page = null ) {
|
||||
$vars = new AbuseFilterVariableHolder;
|
||||
|
||||
// NOTE: $page may end up remaining null, e.g. if $title points to a special page.
|
||||
if ( !$page && $title->canExist() && $title->exists() ) {
|
||||
$page = WikiPage::factory( $title );
|
||||
}
|
||||
|
||||
$vars->setLazyLoadVar( 'edit_diff', 'diff',
|
||||
array( 'oldtext-var' => 'old_wikitext', 'newtext-var' => 'new_wikitext' ) );
|
||||
$vars->setLazyLoadVar( 'new_size', 'length', array( 'length-var' => 'new_wikitext' ) );
|
||||
|
@ -1892,7 +1897,7 @@ class AbuseFilter {
|
|||
'namespace' => $title->getNamespace(),
|
||||
'title' => $title->getText(),
|
||||
'text-var' => 'new_wikitext',
|
||||
'article' => $article
|
||||
'article' => $page
|
||||
) );
|
||||
$vars->setLazyLoadVar( 'old_links', 'links-from-wikitext-or-database',
|
||||
array(
|
||||
|
@ -1910,7 +1915,7 @@ class AbuseFilter {
|
|||
'namespace' => $title->getNamespace(),
|
||||
'title' => $title->getText(),
|
||||
'wikitext-var' => 'new_wikitext',
|
||||
'article' => $article
|
||||
'article' => $page
|
||||
) );
|
||||
$vars->setLazyLoadVar( 'new_text', 'strip-html',
|
||||
array( 'html-var' => 'new_html' ) );
|
||||
|
@ -2100,13 +2105,16 @@ class AbuseFilter {
|
|||
* @return string|null the content of the revision as some kind of string,
|
||||
* or an empty string if it can not be found
|
||||
*/
|
||||
static function revisionToString( $revision, $audience = Revision::FOR_PUBLIC ) {
|
||||
static function revisionToString( $revision, $audience = Revision::FOR_THIS_USER ) {
|
||||
if ( !$revision instanceof Revision ) {
|
||||
return '';
|
||||
}
|
||||
if ( defined( 'MW_SUPPORTS_CONTENTHANDLER' ) ) {
|
||||
$content = $revision->getContent( $audience );
|
||||
$result = $content instanceof TextContent ? $content->getNativeData() : $content->getTextForSearchIndex();
|
||||
if ( !$content instanceof Content ) {
|
||||
return '';
|
||||
}
|
||||
$result = self::contentToString( $content );
|
||||
} else {
|
||||
// For MediaWiki without contenthandler support (< 1.21)
|
||||
$result = $revision->getText();
|
||||
|
@ -2114,4 +2122,37 @@ class AbuseFilter {
|
|||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param Content $content
|
||||
*
|
||||
* @return string a suitable string representation of the content.
|
||||
*/
|
||||
static function contentToString( Content $content ) {
|
||||
$text = null;
|
||||
|
||||
if ( wfRunHooks( 'AbuseFilter-contentToString', array( $content, &$text ) ) ) {
|
||||
$text = $content instanceof TextContent
|
||||
? $content->getNativeData()
|
||||
: $content->getTextForSearchIndex();
|
||||
}
|
||||
|
||||
if ( is_string( $text ) ) {
|
||||
// bug 20310
|
||||
// XXX: Is this really needed? Should we rather apply PST?
|
||||
$text = str_replace( "\r\n", "\n", $text );
|
||||
} else {
|
||||
$text = '';
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ class AbuseFilterHooks {
|
|||
// Hooray!
|
||||
|
||||
/**
|
||||
* Entry points for MediaWiki hook 'EditFilterMerged'
|
||||
* Entry points for MediaWiki hook 'EditFilterMerged' (MW 1.20 and earlier)
|
||||
*
|
||||
* @param $editor EditPage instance (object)
|
||||
* @param $text string Content of the edit box
|
||||
|
@ -17,63 +17,135 @@ class AbuseFilterHooks {
|
|||
* @return bool
|
||||
*/
|
||||
public static function onEditFilterMerged( $editor, $text, &$error, $summary ) {
|
||||
global $wgUser;
|
||||
|
||||
$context = $editor->mArticle->getContext();
|
||||
|
||||
$status = Status::newGood();
|
||||
$minoredit = $editor->minoredit;
|
||||
|
||||
// poor man's PST, see bug 20310
|
||||
$text = str_replace( "\r\n", "\n", $text );
|
||||
|
||||
$continue = self::filterEdit( $context, null, $text, $status, $summary, $wgUser, $minoredit );
|
||||
|
||||
if ( !$status->isOK() ) {
|
||||
$error = $status->getWikiText();
|
||||
}
|
||||
|
||||
return $continue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry points for MediaWiki hook 'EditFilterMergedContent' (MW 1.21 and later)
|
||||
*
|
||||
* @param IContextSource $context the context of the edit
|
||||
* @param Content $content the new Content generated by the edit
|
||||
* @param Status $status Error message to return
|
||||
* @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.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function onEditFilterMergedContent( IContextSource $context, Content $content,
|
||||
Status $status, $summary, User $user, $minoredit ) {
|
||||
|
||||
$text = AbuseFilter::contentToString( $content, Revision::RAW );
|
||||
|
||||
$continue = self::filterEdit( $context, $content, $text, $status, $summary, $user, $minoredit );
|
||||
return $continue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Common implementation for EditFilterMerged and EditFilterMergedContent hooks.
|
||||
*
|
||||
* @param IContextSource $context the context of the edit
|
||||
* @param Content|null $content the new Content generated by the edit
|
||||
* @param string $text new page content (subject of filtering)
|
||||
* @param Status $status Error message to return
|
||||
* @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.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function filterEdit( IContextSource $context, $content, $text,
|
||||
Status $status, $summary, User $user, $minoredit ) {
|
||||
// Load vars
|
||||
$vars = new AbuseFilterVariableHolder;
|
||||
|
||||
# Replace line endings so the filter won't get confused as $text
|
||||
# was not processed by Parser::preSaveTransform (bug 20310)
|
||||
$text = str_replace( "\r\n", "\n", $text );
|
||||
|
||||
self::$successful_action_vars = false;
|
||||
self::$last_edit_page = false;
|
||||
|
||||
// Check for null edits.
|
||||
$oldtext = '';
|
||||
$oldcontent = null;
|
||||
|
||||
$article = $editor->getArticle();
|
||||
if ( $article->exists() ) {
|
||||
$title = $context->getTitle();
|
||||
if ( $title->canExist() && $title->exists() ) {
|
||||
// Make sure we load the latest text saved in database (bug 31656)
|
||||
$revision = $article->getRevision();
|
||||
$page = $context->getWikiPage();
|
||||
$revision = $page->getRevision();
|
||||
if ( !$revision ) {
|
||||
return true;
|
||||
}
|
||||
$oldtext = AbuseFilter::revisionToString( $revision, Revision::RAW );
|
||||
|
||||
if ( defined( 'MW_SUPPORTS_CONTENTHANDLER' ) ) {
|
||||
$oldcontent = $revision->getContent( Revision::RAW );
|
||||
$oldtext = AbuseFilter::contentToString( $oldcontent );
|
||||
} else {
|
||||
$oldtext = AbuseFilter::revisionToString( $revision, Revision::RAW );
|
||||
}
|
||||
|
||||
// Cache article object so we can share a parse operation
|
||||
$articleCacheKey = $title->getNamespace() . ':' . $title->getText();
|
||||
AFComputedVariable::$articleCache[$articleCacheKey] = $page;
|
||||
} else {
|
||||
$page = null;
|
||||
}
|
||||
|
||||
// Cache article object so we can share a parse operation
|
||||
$title = $editor->mTitle;
|
||||
$articleCacheKey = $title->getNamespace() . ':' . $title->getText();
|
||||
AFComputedVariable::$articleCache[$articleCacheKey] = $article;
|
||||
|
||||
if ( strcmp( $oldtext, $text ) == 0 ) {
|
||||
// Don't trigger for null edits.
|
||||
// Don't trigger for null edits.
|
||||
if ( $content && $oldcontent && $oldcontent->equals( $content ) ) {
|
||||
// Compare Content objects if available
|
||||
return true;
|
||||
} else if ( strcmp( $oldtext, $text ) == 0 ) {
|
||||
// Otherwise, compare strings
|
||||
return true;
|
||||
}
|
||||
|
||||
global $wgUser;
|
||||
$vars->addHolder( AbuseFilter::generateUserVars( $wgUser ) );
|
||||
$vars->addHolder( AbuseFilter::generateUserVars( $user ) );
|
||||
$vars->addHolder( AbuseFilter::generateTitleVars( $title , 'article' ) );
|
||||
|
||||
$vars->setVar( 'action', 'edit' );
|
||||
$vars->setVar( 'summary', $summary );
|
||||
$vars->setVar( 'minor_edit', $editor->minoredit );
|
||||
$vars->setVar( 'minor_edit', $minoredit );
|
||||
|
||||
$vars->setVar( 'old_wikitext', $oldtext );
|
||||
$vars->setVar( 'new_wikitext', $text );
|
||||
|
||||
$vars->addHolder( AbuseFilter::getEditVars( $title, $article ) );
|
||||
// TODO: set old_content and new_content vars, use them
|
||||
|
||||
$vars->addHolder( AbuseFilter::getEditVars( $title, $page ) );
|
||||
|
||||
$filter_result = AbuseFilter::filterAction( $vars, $title );
|
||||
|
||||
if ( $filter_result !== true ) {
|
||||
global $wgOut;
|
||||
$wgOut->addHTML( $filter_result );
|
||||
$editor->showEditForm();
|
||||
return false;
|
||||
// NOTE: $filter_result is already the wikitext we want to show.
|
||||
// abusefilter-message is just a dummy.
|
||||
|
||||
// This hack works in 1.21:
|
||||
// $msg = wfMessage( 'abusefilter-message' )->rawParams( $filter_result );
|
||||
// $status->fatal( $msg );
|
||||
|
||||
// This works in 1.20 and older, but causes double escaping and thereby broken formatting.
|
||||
// We'll live with this for now, will be fixed in a follow-up change.
|
||||
$status->fatal( 'abusefilter-message', $filter_result );
|
||||
return true; // re-show edit form
|
||||
}
|
||||
|
||||
self::$successful_action_vars = $vars;
|
||||
self::$last_edit_page = $editor->mArticle;
|
||||
self::$last_edit_page = $page;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -176,6 +176,7 @@ Filter description: $7 ($8)',
|
|||
|
||||
// The edit screen
|
||||
'abusefilter-edit' => 'Editing abuse filter',
|
||||
'abusefilter-message' => '$1', # do not translate or duplicate this message to other languages
|
||||
'abusefilter-edit-subtitle' => 'Editing filter $1',
|
||||
'abusefilter-edit-subtitle-new' => 'Creating filter',
|
||||
'abusefilter-edit-oldwarning' => "<strong>You are editing an old version of this filter.
|
||||
|
@ -693,6 +694,7 @@ $messages['qqq'] = array(
|
|||
* $5 is the number of matched actions
|
||||
* $6 is a percentage: $5 / $1 * 100',
|
||||
'abusefilter-edit' => 'Page title when editing an abuse filter.',
|
||||
'abusefilter-message' => 'Dummy message wrapping wikitext made up of already localized messages.',
|
||||
'abusefilter-edit-subtitle' => 'Page subtitle when editing an abuse filter.',
|
||||
'abusefilter-edit-subtitle-new' => 'Page subtitle when creating an abuse filter',
|
||||
'abusefilter-edit-oldwarning' => 'Warning displayed when editing an older version of a filter.',
|
||||
|
|
|
@ -68,7 +68,13 @@ $wgAPIModules['abusefilterunblockautopromote'] = 'ApiAbuseFilterUnblockAutopromo
|
|||
$wgAutoloadClasses['ApiAbuseFilterCheckMatch'] = "$dir/api/ApiAbuseFilterCheckMatch.php";
|
||||
$wgAPIModules['abusefiltercheckmatch'] = 'ApiAbuseFilterCheckMatch';
|
||||
|
||||
$wgHooks['EditFilterMerged'][] = 'AbuseFilterHooks::onEditFilterMerged';
|
||||
|
||||
if ( defined( 'MW_SUPPORTS_CONTENTHANDLER' ) ) {
|
||||
$wgHooks['EditFilterMergedContent'][] = 'AbuseFilterHooks::onEditFilterMergedContent';
|
||||
} else {
|
||||
$wgHooks['EditFilterMerged'][] = 'AbuseFilterHooks::onEditFilterMerged';
|
||||
}
|
||||
|
||||
$wgHooks['GetAutoPromoteGroups'][] = 'AbuseFilterHooks::onGetAutoPromoteGroups';
|
||||
$wgHooks['AbortMove'][] = 'AbuseFilterHooks::onAbortMove';
|
||||
$wgHooks['AbortNewAccount'][] = 'AbuseFilterHooks::onAbortNewAccount';
|
||||
|
|
|
@ -347,6 +347,7 @@ class AFComputedVariable {
|
|||
}
|
||||
// Otherwise fall back to database
|
||||
case 'parse-wikitext-nonedit':
|
||||
// TODO: use Content object instead, if available! In any case, use WikiPage, not Article.
|
||||
$article = self::articleFromTitle( $parameters['namespace'], $parameters['title'] );
|
||||
$textVar = $parameters['wikitext-var'];
|
||||
|
||||
|
|
|
@ -17,6 +17,12 @@ $vars: AbuseFilterVariableHolder
|
|||
$parameters: Parameters with data to compute the value
|
||||
&$result: Result of the computation
|
||||
|
||||
'AbuseFilter-contentToString': Called when converting a Content object to a string to which
|
||||
filters can be applied. If the hook function returns true, Content::getTextForSearchIndex()
|
||||
will be used for non-text content.
|
||||
$content: The Content object
|
||||
&$text: Set this to the desired text.
|
||||
|
||||
'AbuseFilter-filterAction': Allows overwriting of abusefilter variables in AbuseFilter::filterAction just
|
||||
before they're checked against filters.
|
||||
$vars: AbuseFilterVariableHolder with variables
|
||||
|
|
Loading…
Reference in a new issue