mediawiki-extensions-AbuseF.../AbuseFilterVariableHolder.php

381 lines
11 KiB
PHP
Raw Normal View History

2009-02-27 00:03:30 +00:00
<?php
class AbuseFilterVariableHolder {
var $mVars = array();
static $varBlacklist = array( 'context' );
2009-02-27 00:03:30 +00:00
function setVar( $variable, $datum ) {
$variable = strtolower( $variable );
2009-05-22 06:42:10 +00:00
if ( !( $datum instanceof AFPData || $datum instanceof AFComputedVariable ) ) {
2009-02-27 00:03:30 +00:00
$datum = AFPData::newFromPHPVar( $datum );
}
$this->mVars[$variable] = $datum;
}
function setLazyLoadVar( $variable, $method, $parameters ) {
$placeholder = new AFComputedVariable( $method, $parameters );
$this->setVar( $variable, $placeholder );
}
function getVar( $variable ) {
$variable = strtolower( $variable );
2009-05-22 06:42:10 +00:00
if ( isset( $this->mVars[$variable] ) ) {
if ( $this->mVars[$variable] instanceof AFComputedVariable ) {
2009-02-27 00:03:30 +00:00
$value = $this->mVars[$variable]->compute( $this );
$this->setVar( $variable, $value );
return $value;
2009-05-22 06:42:10 +00:00
} elseif ( $this->mVars[$variable] instanceof AFPData ) {
2009-02-27 00:03:30 +00:00
return $this->mVars[$variable];
2009-05-22 06:42:10 +00:00
}
2009-02-27 00:03:30 +00:00
} else {
return new AFPData();
}
}
static function merge( /* ... */ ) {
$newHolder = new AbuseFilterVariableHolder;
foreach( func_get_args() as $addHolder ) {
$newHolder->addHolder( $addHolder );
}
return $newHolder;
}
function addHolder( $addHolder ) {
2009-05-22 06:42:10 +00:00
if ( !is_object( $addHolder ) ) {
2009-03-06 23:11:07 +00:00
throw new MWException( "Invalid argument to AbuseFilterVariableHolder::addHolder" );
2009-05-22 06:42:10 +00:00
}
2009-02-27 00:03:30 +00:00
$this->mVars = array_merge( $this->mVars, $addHolder->mVars );
}
function __wakeup() {
// Reset the context.
$this->setVar( 'context', 'stored' );
}
2009-02-27 00:03:30 +00:00
function exportAllVars() {
$allVarNames = array_keys( $this->mVars );
$exported = array();
foreach( $allVarNames as $varName ) {
2009-05-22 06:42:10 +00:00
if ( !in_array( $varName, self::$varBlacklist ) ) {
$exported[$varName] = $this->getVar( $varName )->toString();
2009-05-22 06:42:10 +00:00
}
2009-02-27 00:03:30 +00:00
}
2009-02-27 00:03:30 +00:00
return $exported;
}
function varIsSet( $var ) {
return array_key_exists( $var, $this->mVars );
}
/** Compute all vars which need DB access. Useful for vars which are going to be saved
* cross-wiki or used for offline analysis */
function computeDBVars() {
static $dbTypes = array( 'links-from-wikitext-or-database', 'load-recent-authors',
'get-page-restrictions', 'simple-user-accessor',
'user-age', 'user-groups', 'revision-text-by-id',
'revision-text-by-timestamp' );
foreach( $this->mVars as $name => $value ) {
2009-05-22 06:42:10 +00:00
if ( $value instanceof AFComputedVariable &&
in_array( $value->mMethod, $dbTypes ) ) {
$value = $value->compute( $this );
$this->setVar( $name, $value );
}
}
}
2009-02-27 00:03:30 +00:00
}
class AFComputedVariable {
var $mMethod, $mParameters;
static $userCache = array();
static $articleCache = array();
function __construct( $method, $parameters ) {
$this->mMethod = $method;
$this->mParameters = $parameters;
}
/** It's like Article::prepareTextForEdit, but not for editing (old wikitext usually) */
function parseNonEditWikitext( $wikitext, $article ) {
static $cache = array();
$cacheKey = md5($wikitext).':'.$article->mTitle->getPrefixedText();
2009-05-22 06:42:10 +00:00
if ( isset( $cache[$cacheKey] ) ) {
2009-02-27 00:03:30 +00:00
return $cache[$cacheKey];
2009-05-22 06:42:10 +00:00
}
2009-02-27 00:03:30 +00:00
global $wgParser;
$edit = (object)array();
$options = new ParserOptions;
$options->setTidy( true );
$edit->output = $wgParser->parse( $wikitext, $article->mTitle, $options );
$cache[$cacheKey] = $edit;
return $edit;
}
static function userObjectFromName( $username ) {
2009-05-22 06:42:10 +00:00
if ( isset( self::$userCache[$username] ) ) {
2009-02-27 00:03:30 +00:00
return self::$userCache[$username];
2009-05-22 06:42:10 +00:00
}
2009-02-27 00:03:30 +00:00
wfDebug( "Couldn't find user $username in cache\n" );
2009-05-22 06:42:10 +00:00
if ( IP::isIPAddress( $username ) ) {
$u = new User;
$u->setName( $username );
2009-05-22 06:42:10 +00:00
self::$userCache[$username] = $u;
return $u;
}
$user = User::newFromName( $username );
$user->load();
self::$userCache[$username] = $user;
return $user;
2009-02-27 00:03:30 +00:00
}
static function articleFromTitle( $namespace, $title ) {
2009-05-22 06:42:10 +00:00
if ( isset( self::$articleCache["$namespace:$title"] ) ) {
2009-02-27 00:03:30 +00:00
return self::$articleCache["$namespace:$title"];
2009-05-22 06:42:10 +00:00
}
wfDebug( "Creating article object for $namespace:$title in cache\n" );
2009-02-27 00:03:30 +00:00
$t = Title::makeTitle( $namespace, $title );
self::$articleCache["$namespace:$title"] = new Article( $t );
return self::$articleCache["$namespace:$title"];
}
static function getLinksFromDB( $article ) {
// Stolen from ConfirmEdit
$id = $article->getId();
2009-05-22 06:42:10 +00:00
if ( !$id ) {
return array();
}
$dbr = wfGetDB( DB_SLAVE );
$res = $dbr->select( 'externallinks', array( 'el_to' ),
array( 'el_from' => $id ), __METHOD__ );
$links = array();
while ( $row = $dbr->fetchObject( $res ) ) {
$links[] = $row->el_to;
}
return $links;
}
2009-02-27 00:03:30 +00:00
function compute( $vars ) {
$parameters = $this->mParameters;
$result = null;
switch( $this->mMethod ) {
case 'diff':
$text1Var = $parameters['oldtext-var'];
$text2Var = $parameters['newtext-var'];
$text1 = $vars->getVar( $text1Var )->toString();
$text2 = $vars->getVar( $text2Var )->toString();
$result = wfDiff( $text1, $text2 );
$result = trim( str_replace( '\No newline at end of file', '', $result ) );
break;
case 'diff-split':
$diff = $vars->getVar( $parameters['diff-var'] )->toString();
$line_prefix = $parameters['line-prefix'];
$diff_lines = explode( "\n", $diff );
$interest_lines = array();
foreach( $diff_lines as $line ) {
if (strpos( $line, $line_prefix )===0) {
$interest_lines[] = substr( $line, strlen($line_prefix) );
}
}
$result = $interest_lines;
2009-02-27 00:03:30 +00:00
break;
case 'links-from-wikitext':
$article = self::articleFromTitle( $parameters['namespace'],
$parameters['title'] );
2009-02-27 00:03:30 +00:00
$textVar = $parameters['text-var'];
$new_text = $vars->getVar( $textVar )->toString();
$editInfo = $article->prepareTextForEdit( $new_text );
$links = array_keys( $editInfo->output->getExternalLinks() );
$result = $links;
2009-02-27 00:03:30 +00:00
break;
2009-03-19 03:10:18 +00:00
case 'links-from-wikitext-nonedit':
case 'links-from-wikitext-or-database':
$article = self::articleFromTitle( $parameters['namespace'],
$parameters['title'] );
if ($vars->getVar( 'context' )->toString() == 'filter') {
$links = $this->getLinksFromDB( $article );
wfDebug( "AbuseFilter: loading old links from DB\n" );
} else {
wfDebug( "AbuseFilter: loading old links from Parser\n" );
$textVar = $parameters['text-var'];
$wikitext = $vars->getVar( $textVar )->toString();
$editInfo = $this->parseNonEditWikitext( $wikitext, $article );
$links = array_keys( $editInfo->output->getExternalLinks() );
}
2009-02-27 00:03:30 +00:00
$result = $links;
2009-02-27 00:03:30 +00:00
break;
case 'link-diff-added':
$oldLinkVar = $parameters['oldlink-var'];
$newLinkVar = $parameters['newlink-var'];
$oldLinks = $vars->getVar( $oldLinkVar )->toString();
$newLinks = $vars->getVar( $newLinkVar )->toString();
$oldLinks = explode( "\n", $oldLinks );
$newLinks = explode( "\n", $newLinks );
$added = array_diff( $newLinks, $oldLinks );
$result = $added;
2009-02-27 00:03:30 +00:00
break;
case 'link-diff-removed':
$oldLinkVar = $parameters['oldlink-var'];
$newLinkVar = $parameters['newlink-var'];
$oldLinks = $vars->getVar( $oldLinkVar )->toString();
$newLinks = $vars->getVar( $newLinkVar )->toString();
$oldLinks = explode( "\n", $oldLinks );
$newLinks = explode( "\n", $newLinks );
$removed = array_diff( $oldLinks, $newLinks );
$result = $removed;
2009-02-27 00:03:30 +00:00
break;
case 'parse-wikitext':
$article = self::articleFromTitle( $parameters['namespace'], $parameters['title'] );
$textVar = $parameters['wikitext-var'];
$new_text = $vars->getVar( $textVar )->toString();
$editInfo = $article->prepareTextForEdit( $new_text );
$newHTML = $editInfo->output->getText();
// Kill the PP limit comments. Ideally we'd just remove these by not setting the
// parser option, but then we can't share a parse operation with the edit, which is bad.
$result = preg_replace( '/<!--\s*NewPP limit report[^>]*-->\s*$/si', '', $newHTML );
break;
case 'parse-wikitext-nonedit':
$article = self::articleFromTitle( $parameters['namespace'], $parameters['title'] );
$textVar = $parameters['wikitext-var'];
$text = $vars->getVar( $textVar )->toString();
2009-05-22 06:42:10 +00:00
$editInfo = $this->parseNonEditWikitext( $text, $article );
2009-02-27 00:03:30 +00:00
$result = $editInfo->output->getText();
break;
case 'strip-html':
$htmlVar = $parameters['html-var'];
$html = $vars->getVar( $htmlVar )->toString();
$result = preg_replace( '/<[^>]+>/', '', $html );
break;
case 'load-recent-authors':
$cutOff = $parameters['cutoff'];
$title = Title::makeTitle( $parameters['namespace'], $parameters['title'] );
if (!$title->exists()) {
$result = '';
break;
}
2009-02-27 00:03:30 +00:00
$dbr = wfGetDB( DB_SLAVE );
$res = $dbr->select( 'revision', 'distinct rev_user_text',
array(
'rev_page' => $title->getArticleId(),
'rev_timestamp<'.$dbr->addQuotes( $dbr->timestamp( $cutOff ) ) ),
__METHOD__,
array( 'ORDER BY' => 'rev_timestamp DESC', 'LIMIT' => 10 ) );
$users = array();
while ($user = $dbr->fetchRow($res)) {
$users[] = $user[0];
}
$result = $users;
2009-02-27 00:03:30 +00:00
break;
case 'get-page-restrictions':
$action = $parameters['action'];
$title = Title::makeTitle( $parameters['namespace'], $parameters['title'] );
$rights = $title->getRestrictions( $action );
$rights = count($rights) ? $rights : array();
$result = $rights;
2009-02-27 00:03:30 +00:00
break;
case 'simple-user-accessor':
$user = $parameters['user'];
$method = $parameters['method'];
2009-02-27 13:16:34 +00:00
if (!$user) {
throw new MWException( "No user parameter given." );
}
2009-02-27 00:03:30 +00:00
$obj = self::userObjectFromName( $user );
2009-02-27 13:16:34 +00:00
if (!$obj) {
throw new MWException( "Invalid username $user" );
}
2009-02-27 00:03:30 +00:00
$result = call_user_func( array($obj, $method) );
break;
case 'user-age':
$user = $parameters['user'];
$asOf = $parameters['asof'];
$obj = self::userObjectFromName( $user );
if ($obj->getId() == 0) {
$result = 0;
break;
}
2009-02-27 00:03:30 +00:00
$registration = $obj->getRegistration();
$result =
wfTimestamp( TS_UNIX, $asOf) -
wfTimestampOrNull( TS_UNIX, $registration );
2009-02-27 00:03:30 +00:00
break;
case 'user-groups':
$user = $parameters['user'];
$obj = self::userObjectFromName( $user );
$result = $obj->getEffectiveGroups();
2009-02-27 00:03:30 +00:00
break;
case 'length':
$s = $vars->getVar( $parameters['length-var'] )->toString();
$result = strlen( $s );
break;
case 'subtract':
$v1 = $vars->getVar( $parameters['val1-var'] )->toFloat();
$v2 = $vars->getVar( $parameters['val2-var'] )->toFloat();
$result = $v1 - $v2;
break;
case 'revision-text-by-id':
$rev = Revision::newFromId( $parameters['revid'] );
$result = $rev->getText();
break;
case 'revision-text-by-timestamp':
$timestamp = $parameters['timestamp'];
$title = Title::makeTitle( $parameters['namespace'], $parameters['title'] );
$dbr = wfGetDB( DB_SLAVE );
$rev = Revision::loadFromTimestamp( $dbr, $title, $timestamp );
if ($rev)
$result = $rev->getText();
else
$result = '';
break;
default:
if ( wfRunHooks( 'AbuseFilter-computeVariable',
array( $this->mMethod, $vars ) ) ) {
throw new AFPException( "Unknown variable compute type ".$this->mMethod );
}
2009-02-27 00:03:30 +00:00
}
return AFPData::newFromPHPVar( $result );
}
}