diff --git a/AbuseFilter.hooks.php b/AbuseFilter.hooks.php index 96b421cd4..255a1f6a8 100644 --- a/AbuseFilter.hooks.php +++ b/AbuseFilter.hooks.php @@ -7,6 +7,57 @@ class AbuseFilterHooks { // So far, all of the error message out-params for these hooks accept HTML. // Hooray! + /** + * Entry point for the APIEditBeforeSave hook. + * This is needed to give a useful error for API edits (Bug 32216) + * + * @see https://www.mediawiki.org/wiki/Manual:Hooks/APIEditBeforeSave + * + * @param EditPage $editPage + * @param string $text New text of the article (has yet to be saved) + * @param array &$resultArr Data in this array will be added to the API result + * + * @return bool + */ + public static function onAPIEditBeforeSave( $editPage, $text, &$result ) { + global $wgUser; + + $context = $editPage->mArticle->getContext(); + + $status = Status::newGood(); + $minoredit = $editPage->minoredit; + $summary = $editPage->summary; + + // 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() ) { + $msg = $status->getErrorsArray(); + + // Use the error message key name as error code, the first parameter is the filter description. + if ( $msg[0] instanceof Message ) { + // For forward compatibility: In case we switch over towards using Message objects someday. + // (see the todo for AbuseFilter::buildStatus) + $code = $msg[0]->getKey(); + $filterDescription = $msg[0]->getParams(); + $filterDescription = $filterDescription[0]; + } else { + $code = $msg[0][0]; + $filterDescription = $msg[0][1]; + } + + $result = array( + 'code' => $code, + 'info' => 'Hit AbuseFilter: ' . $filterDescription, + 'warning' => $status->getHTML() + ); + } + + return $status->isOK(); + } + /** * Entry points for MediaWiki hook 'EditFilterMerged' (MW 1.20 and earlier) * @@ -58,7 +109,8 @@ class AbuseFilterHooks { } /** - * Common implementation for EditFilterMerged and EditFilterMergedContent hooks. + * Common implementation for the APIEditBeforeSave, EditFilterMerged + * and EditFilterMergedContent hooks. * * @param IContextSource $context the context of the edit * @param Content|null $content the new Content generated by the edit @@ -73,7 +125,15 @@ class AbuseFilterHooks { public static function filterEdit( IContextSource $context, $content, $text, Status $status, $summary, User $user, $minoredit ) { // Load vars - $vars = new AbuseFilterVariableHolder; + $vars = new AbuseFilterVariableHolder(); + + $title = $context->getTitle(); + + // Some edits are running through multiple hooks, but we only want to filter them once + if ( isset( $title->editAlreadyFiltered ) ) { + return true; + } + $title->editAlreadyFiltered = true; self::$successful_action_vars = false; self::$last_edit_page = false; @@ -82,7 +142,6 @@ class AbuseFilterHooks { $oldtext = ''; $oldcontent = null; - $title = $context->getTitle(); if ( ( $title instanceof Title ) && $title->canExist() && $title->exists() ) { // Make sure we load the latest text saved in database (bug 31656) $page = $context->getWikiPage(); diff --git a/AbuseFilter.php b/AbuseFilter.php index 67ceddc33..ba38823df 100644 --- a/AbuseFilter.php +++ b/AbuseFilter.php @@ -87,6 +87,7 @@ $wgHooks['ContributionsToolLinks'][] = 'AbuseFilterHooks::onContributionsToolLin $wgHooks['UploadVerifyFile'][] = 'AbuseFilterHooks::onUploadVerifyFile'; $wgHooks['MakeGlobalVariablesScript'][] = 'AbuseFilterHooks::onMakeGlobalVariablesScript'; $wgHooks['ArticleSaveComplete'][] = 'AbuseFilterHooks::onArticleSaveComplete'; +$wgHooks['APIEditBeforeSave'][] = 'AbuseFilterHooks::onAPIEditBeforeSave'; $wgAvailableRights[] = 'abusefilter-modify'; $wgAvailableRights[] = 'abusefilter-log-detail';