(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:
daniel 2013-01-04 16:37:56 +01:00 committed by Marius Hoch
parent b8b799630b
commit f3788c4f0c
6 changed files with 160 additions and 32 deletions

View file

@ -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;
}
}

View file

@ -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;
}

View file

@ -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.',

View file

@ -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';

View file

@ -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'];

View file

@ -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