2009-02-27 00:03:30 +00:00
|
|
|
<?php
|
|
|
|
|
2018-02-15 21:55:51 +00:00
|
|
|
use Wikimedia\Rdbms\Database;
|
|
|
|
use MediaWiki\MediaWikiServices;
|
|
|
|
|
2009-02-27 00:03:30 +00:00
|
|
|
class AFComputedVariable {
|
2013-10-15 12:35:03 +00:00
|
|
|
public $mMethod, $mParameters;
|
2017-06-15 14:23:34 +00:00
|
|
|
public static $userCache = [];
|
|
|
|
public static $articleCache = [];
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2012-03-11 20:40:04 +00:00
|
|
|
/**
|
2017-10-06 18:52:31 +00:00
|
|
|
* @param string $method
|
|
|
|
* @param array $parameters
|
2012-03-11 20:40:04 +00:00
|
|
|
*/
|
2009-02-27 00:03:30 +00:00
|
|
|
function __construct( $method, $parameters ) {
|
|
|
|
$this->mMethod = $method;
|
|
|
|
$this->mParameters = $parameters;
|
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2011-08-24 22:11:52 +00:00
|
|
|
/**
|
2016-09-28 20:07:52 +00:00
|
|
|
* It's like Article::prepareContentForEdit, but not for editing (old wikitext usually)
|
2011-08-24 22:11:52 +00:00
|
|
|
*
|
|
|
|
*
|
2013-10-15 13:22:05 +00:00
|
|
|
* @param string $wikitext
|
|
|
|
* @param WikiPage $article
|
2011-08-24 22:11:52 +00:00
|
|
|
*
|
|
|
|
* @return object
|
|
|
|
*/
|
2009-02-27 00:03:30 +00:00
|
|
|
function parseNonEditWikitext( $wikitext, $article ) {
|
2017-06-15 14:23:34 +00:00
|
|
|
static $cache = [];
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2012-03-11 20:40:04 +00:00
|
|
|
$cacheKey = md5( $wikitext ) . ':' . $article->getTitle()->getPrefixedText();
|
2009-10-07 13:57:06 +00:00
|
|
|
|
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-10-07 13:57:06 +00:00
|
|
|
|
2009-02-27 00:03:30 +00:00
|
|
|
global $wgParser;
|
2017-06-15 14:23:34 +00:00
|
|
|
$edit = (object)[];
|
2009-02-27 00:03:30 +00:00
|
|
|
$options = new ParserOptions;
|
|
|
|
$options->setTidy( true );
|
2011-08-24 22:11:52 +00:00
|
|
|
$edit->output = $wgParser->parse( $wikitext, $article->getTitle(), $options );
|
2009-02-27 00:03:30 +00:00
|
|
|
$cache[$cacheKey] = $edit;
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2009-02-27 00:03:30 +00:00
|
|
|
return $edit;
|
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2012-03-11 20:40:04 +00:00
|
|
|
/**
|
2013-01-07 00:02:41 +00:00
|
|
|
* For backwards compatibility: Get the user object belonging to a certain name
|
|
|
|
* in case a user name is given as argument. Nowadays user objects are passed
|
|
|
|
* directly but many old log entries rely on this.
|
|
|
|
*
|
2017-10-06 18:52:31 +00:00
|
|
|
* @param string|User $user
|
2012-03-11 20:40:04 +00:00
|
|
|
* @return User
|
|
|
|
*/
|
2013-01-07 00:02:41 +00:00
|
|
|
static function getUserObject( $user ) {
|
|
|
|
if ( $user instanceof User ) {
|
|
|
|
$username = $user->getName();
|
|
|
|
} else {
|
|
|
|
$username = $user;
|
|
|
|
if ( isset( self::$userCache[$username] ) ) {
|
|
|
|
return self::$userCache[$username];
|
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2013-01-07 00:02:41 +00:00
|
|
|
wfDebug( "Couldn't find user $username in cache\n" );
|
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2009-05-26 13:08:15 +00:00
|
|
|
if ( count( self::$userCache ) > 1000 ) {
|
2017-06-15 14:23:34 +00:00
|
|
|
self::$userCache = [];
|
2009-05-26 13:08:15 +00:00
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2013-01-07 00:02:41 +00:00
|
|
|
if ( $user instanceof User ) {
|
|
|
|
$userCache[$username] = $user;
|
|
|
|
return $user;
|
|
|
|
}
|
|
|
|
|
2009-05-22 06:42:10 +00:00
|
|
|
if ( IP::isIPAddress( $username ) ) {
|
2009-02-28 01:10:45 +00:00
|
|
|
$u = new User;
|
|
|
|
$u->setName( $username );
|
2009-05-22 06:42:10 +00:00
|
|
|
self::$userCache[$username] = $u;
|
|
|
|
return $u;
|
2009-02-28 01:10:45 +00:00
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2009-03-10 23:28:18 +00:00
|
|
|
$user = User::newFromName( $username );
|
|
|
|
$user->load();
|
|
|
|
self::$userCache[$username] = $user;
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2009-03-10 23:28:18 +00:00
|
|
|
return $user;
|
2009-02-27 00:03:30 +00:00
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2012-03-11 20:40:04 +00:00
|
|
|
/**
|
2017-10-06 18:52:31 +00:00
|
|
|
* @param int $namespace
|
|
|
|
* @param Title $title
|
2012-03-11 20:40:04 +00:00
|
|
|
* @return Article
|
|
|
|
*/
|
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
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2009-05-26 13:08:15 +00:00
|
|
|
if ( count( self::$articleCache ) > 1000 ) {
|
2017-06-15 14:23:34 +00:00
|
|
|
self::$articleCache = [];
|
2009-05-26 13:08:15 +00:00
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2009-02-28 01:10:45 +00:00
|
|
|
wfDebug( "Creating article object for $namespace:$title in cache\n" );
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2013-01-22 16:02:21 +00:00
|
|
|
// TODO: use WikiPage instead!
|
2009-02-27 00:03:30 +00:00
|
|
|
$t = Title::makeTitle( $namespace, $title );
|
|
|
|
self::$articleCache["$namespace:$title"] = new Article( $t );
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2009-02-27 00:03:30 +00:00
|
|
|
return self::$articleCache["$namespace:$title"];
|
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2012-03-11 20:40:04 +00:00
|
|
|
/**
|
2013-10-15 13:22:05 +00:00
|
|
|
* @param WikiPage $article
|
2012-03-11 20:40:04 +00:00
|
|
|
* @return array
|
|
|
|
*/
|
2009-03-19 02:05:58 +00:00
|
|
|
static function getLinksFromDB( $article ) {
|
|
|
|
// Stolen from ConfirmEdit
|
|
|
|
$id = $article->getId();
|
2009-05-22 06:42:10 +00:00
|
|
|
if ( !$id ) {
|
2017-06-15 14:23:34 +00:00
|
|
|
return [];
|
2009-05-22 06:42:10 +00:00
|
|
|
}
|
2010-02-13 14:10:36 +00:00
|
|
|
|
2017-08-30 02:51:39 +00:00
|
|
|
$dbr = wfGetDB( DB_REPLICA );
|
2010-08-19 21:12:09 +00:00
|
|
|
$res = $dbr->select(
|
|
|
|
'externallinks',
|
2017-06-15 14:23:34 +00:00
|
|
|
[ 'el_to' ],
|
|
|
|
[ 'el_from' => $id ],
|
2010-08-19 21:12:09 +00:00
|
|
|
__METHOD__
|
|
|
|
);
|
2017-06-15 14:23:34 +00:00
|
|
|
$links = [];
|
2015-09-28 18:03:35 +00:00
|
|
|
foreach ( $res as $row ) {
|
2009-03-19 02:05:58 +00:00
|
|
|
$links[] = $row->el_to;
|
|
|
|
}
|
|
|
|
return $links;
|
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2012-03-11 20:40:04 +00:00
|
|
|
/**
|
2017-10-06 18:52:31 +00:00
|
|
|
* @param AbuseFilterVariableHolder $vars
|
2012-03-11 20:40:04 +00:00
|
|
|
* @return AFPData|array|int|mixed|null|string
|
|
|
|
* @throws MWException
|
|
|
|
* @throws AFPException
|
|
|
|
*/
|
2009-02-27 00:03:30 +00:00
|
|
|
function compute( $vars ) {
|
|
|
|
$parameters = $this->mParameters;
|
|
|
|
$result = null;
|
2012-11-16 17:06:34 +00:00
|
|
|
|
2015-08-06 17:15:42 +00:00
|
|
|
if ( !Hooks::run( 'AbuseFilter-interceptVariable',
|
2017-06-15 14:23:34 +00:00
|
|
|
[ $this->mMethod, $vars, $parameters, &$result ] ) ) {
|
2012-11-16 17:06:34 +00:00
|
|
|
return $result instanceof AFPData
|
|
|
|
? $result : AFPData::newFromPHPVar( $result );
|
|
|
|
}
|
|
|
|
|
2015-09-28 18:03:35 +00:00
|
|
|
switch ( $this->mMethod ) {
|
2009-02-27 00:03:30 +00:00
|
|
|
case 'diff':
|
|
|
|
$text1Var = $parameters['oldtext-var'];
|
|
|
|
$text2Var = $parameters['newtext-var'];
|
2017-02-24 14:22:56 +00:00
|
|
|
$text1 = $vars->getVar( $text1Var )->toString();
|
|
|
|
$text2 = $vars->getVar( $text2Var )->toString();
|
|
|
|
$diffs = new Diff( explode( "\n", $text1 ), explode( "\n", $text2 ) );
|
|
|
|
$format = new UnifiedDiffFormatter();
|
|
|
|
$result = $format->format( $diffs );
|
2009-02-27 00:03:30 +00:00
|
|
|
break;
|
|
|
|
case 'diff-split':
|
|
|
|
$diff = $vars->getVar( $parameters['diff-var'] )->toString();
|
|
|
|
$line_prefix = $parameters['line-prefix'];
|
|
|
|
$diff_lines = explode( "\n", $diff );
|
2017-06-15 14:23:34 +00:00
|
|
|
$interest_lines = [];
|
2010-02-13 14:10:36 +00:00
|
|
|
foreach ( $diff_lines as $line ) {
|
2009-10-07 13:57:06 +00:00
|
|
|
if ( substr( $line, 0, 1 ) === $line_prefix ) {
|
|
|
|
$interest_lines[] = substr( $line, strlen( $line_prefix ) );
|
2009-02-27 00:03:30 +00:00
|
|
|
}
|
|
|
|
}
|
2009-04-05 19:07:47 +00:00
|
|
|
$result = $interest_lines;
|
2009-02-27 00:03:30 +00:00
|
|
|
break;
|
|
|
|
case 'links-from-wikitext':
|
2009-05-26 13:08:15 +00:00
|
|
|
// This should ONLY be used when sharing a parse operation with the edit.
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2013-01-22 16:02:21 +00:00
|
|
|
/* @var WikiPage $article */
|
2015-03-21 02:53:13 +00:00
|
|
|
if ( isset( $parameters['article'] ) ) {
|
|
|
|
$article = $parameters['article'];
|
|
|
|
} else {
|
|
|
|
$article = self::articleFromTitle(
|
|
|
|
$parameters['namespace'],
|
|
|
|
$parameters['title']
|
|
|
|
);
|
|
|
|
}
|
2015-11-23 10:02:53 +00:00
|
|
|
if ( $article->getContentModel() === CONTENT_MODEL_WIKITEXT ) {
|
2009-05-26 13:08:15 +00:00
|
|
|
$textVar = $parameters['text-var'];
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2013-01-22 16:02:21 +00:00
|
|
|
// XXX: Use prepareContentForEdit. But we need a Content object for that.
|
2009-05-26 13:08:15 +00:00
|
|
|
$new_text = $vars->getVar( $textVar )->toString();
|
2013-04-01 09:05:08 +00:00
|
|
|
$content = ContentHandler::makeContent( $new_text, $article->getTitle() );
|
|
|
|
$editInfo = $article->prepareContentForEdit( $content );
|
2009-05-26 13:08:15 +00:00
|
|
|
$links = array_keys( $editInfo->output->getExternalLinks() );
|
|
|
|
$result = $links;
|
2011-11-09 08:36:26 +00:00
|
|
|
break;
|
2009-05-26 13:08:15 +00:00
|
|
|
}
|
2011-11-09 08:36:26 +00:00
|
|
|
// Otherwise fall back to database
|
2009-03-19 03:10:18 +00:00
|
|
|
case 'links-from-wikitext-nonedit':
|
2009-03-19 02:05:58 +00:00
|
|
|
case 'links-from-wikitext-or-database':
|
2013-01-22 16:02:21 +00:00
|
|
|
// TODO: use Content object instead, if available! In any case, use WikiPage, not Article.
|
2010-08-19 21:12:09 +00:00
|
|
|
$article = self::articleFromTitle(
|
|
|
|
$parameters['namespace'],
|
|
|
|
$parameters['title']
|
|
|
|
);
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2010-02-13 14:10:36 +00:00
|
|
|
if ( $vars->getVar( 'context' )->toString() == 'filter' ) {
|
2009-03-19 02:05:58 +00:00
|
|
|
$links = $this->getLinksFromDB( $article );
|
|
|
|
wfDebug( "AbuseFilter: loading old links from DB\n" );
|
2015-11-23 10:02:53 +00:00
|
|
|
} elseif ( $article->getContentModel() === CONTENT_MODEL_WIKITEXT ) {
|
2009-03-19 02:05:58 +00:00
|
|
|
wfDebug( "AbuseFilter: loading old links from Parser\n" );
|
|
|
|
$textVar = $parameters['text-var'];
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2009-03-19 02:05:58 +00:00
|
|
|
$wikitext = $vars->getVar( $textVar )->toString();
|
|
|
|
$editInfo = $this->parseNonEditWikitext( $wikitext, $article );
|
|
|
|
$links = array_keys( $editInfo->output->getExternalLinks() );
|
2013-01-22 16:02:21 +00:00
|
|
|
} else {
|
|
|
|
// TODO: Get links from Content object. But we don't have the content object.
|
2017-07-08 18:49:13 +00:00
|
|
|
// And for non-text content, $wikitext is usually not going to be a valid
|
|
|
|
// serialization, but rather some dummy text for filtering.
|
2017-06-15 14:23:34 +00:00
|
|
|
$links = [];
|
2009-03-19 02:05:58 +00:00
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2009-04-05 19:07:47 +00:00
|
|
|
$result = $links;
|
2009-02-27 00:03:30 +00:00
|
|
|
break;
|
|
|
|
case 'link-diff-added':
|
|
|
|
case 'link-diff-removed':
|
|
|
|
$oldLinkVar = $parameters['oldlink-var'];
|
|
|
|
$newLinkVar = $parameters['newlink-var'];
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2009-02-27 00:03:30 +00:00
|
|
|
$oldLinks = $vars->getVar( $oldLinkVar )->toString();
|
|
|
|
$newLinks = $vars->getVar( $newLinkVar )->toString();
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2009-02-27 00:03:30 +00:00
|
|
|
$oldLinks = explode( "\n", $oldLinks );
|
|
|
|
$newLinks = explode( "\n", $newLinks );
|
2009-10-07 13:57:06 +00:00
|
|
|
|
|
|
|
if ( $this->mMethod == 'link-diff-added' ) {
|
2009-05-26 13:08:15 +00:00
|
|
|
$result = array_diff( $newLinks, $oldLinks );
|
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
if ( $this->mMethod == 'link-diff-removed' ) {
|
2009-05-26 13:08:15 +00:00
|
|
|
$result = array_diff( $oldLinks, $newLinks );
|
|
|
|
}
|
2009-02-27 00:03:30 +00:00
|
|
|
break;
|
|
|
|
case 'parse-wikitext':
|
2009-05-26 13:08:15 +00:00
|
|
|
// Should ONLY be used when sharing a parse operation with the edit.
|
2015-03-21 02:53:13 +00:00
|
|
|
if ( isset( $parameters['article'] ) ) {
|
|
|
|
$article = $parameters['article'];
|
|
|
|
} else {
|
|
|
|
$article = self::articleFromTitle(
|
|
|
|
$parameters['namespace'],
|
|
|
|
$parameters['title']
|
|
|
|
);
|
|
|
|
}
|
2015-11-23 10:02:53 +00:00
|
|
|
if ( $article->getContentModel() === CONTENT_MODEL_WIKITEXT ) {
|
2009-05-26 13:08:15 +00:00
|
|
|
$textVar = $parameters['wikitext-var'];
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2009-05-26 13:08:15 +00:00
|
|
|
$new_text = $vars->getVar( $textVar )->toString();
|
2016-09-28 20:07:52 +00:00
|
|
|
$content = ContentHandler::makeContent( $new_text, $article->getTitle() );
|
|
|
|
$editInfo = $article->prepareContentForEdit( $content );
|
2013-04-24 14:53:12 +00:00
|
|
|
if ( isset( $parameters['pst'] ) && $parameters['pst'] ) {
|
|
|
|
$result = $editInfo->pstContent->serialize( $editInfo->format );
|
|
|
|
} else {
|
|
|
|
$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 );
|
|
|
|
}
|
2011-11-09 08:36:26 +00:00
|
|
|
break;
|
2009-05-26 13:08:15 +00:00
|
|
|
}
|
2011-11-09 08:36:26 +00:00
|
|
|
// Otherwise fall back to database
|
2009-02-27 00:03:30 +00:00
|
|
|
case 'parse-wikitext-nonedit':
|
2013-01-04 15:37:56 +00:00
|
|
|
// TODO: use Content object instead, if available! In any case, use WikiPage, not Article.
|
2009-02-27 00:03:30 +00:00
|
|
|
$article = self::articleFromTitle( $parameters['namespace'], $parameters['title'] );
|
|
|
|
$textVar = $parameters['wikitext-var'];
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2015-11-23 10:02:53 +00:00
|
|
|
if ( $article->getContentModel() === CONTENT_MODEL_WIKITEXT ) {
|
2013-04-24 14:53:12 +00:00
|
|
|
if ( isset( $parameters['pst'] ) && $parameters['pst'] ) {
|
|
|
|
// $textVar is already PSTed when it's not loaded from an ongoing edit.
|
|
|
|
$result = $vars->getVar( $textVar )->toString();
|
|
|
|
} else {
|
|
|
|
$text = $vars->getVar( $textVar )->toString();
|
|
|
|
$editInfo = $this->parseNonEditWikitext( $text, $article );
|
|
|
|
$result = $editInfo->output->getText();
|
|
|
|
}
|
2013-01-22 16:02:21 +00:00
|
|
|
} else {
|
|
|
|
// TODO: Parser Output from Content object. But we don't have the content object.
|
2017-07-08 18:49:13 +00:00
|
|
|
// And for non-text content, $wikitext is usually not going to be a valid
|
|
|
|
// serialization, but rather some dummy text for filtering.
|
2013-01-22 16:02:21 +00:00
|
|
|
$result = '';
|
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2009-02-27 00:03:30 +00:00
|
|
|
break;
|
|
|
|
case 'strip-html':
|
|
|
|
$htmlVar = $parameters['html-var'];
|
|
|
|
$html = $vars->getVar( $htmlVar )->toString();
|
2009-05-26 13:08:15 +00:00
|
|
|
$result = StringUtils::delimiterReplace( '<', '>', '', $html );
|
2009-02-27 00:03:30 +00:00
|
|
|
break;
|
|
|
|
case 'load-recent-authors':
|
|
|
|
$title = Title::makeTitle( $parameters['namespace'], $parameters['title'] );
|
2009-10-07 13:57:06 +00:00
|
|
|
if ( !$title->exists() ) {
|
2009-03-16 08:21:24 +00:00
|
|
|
$result = '';
|
|
|
|
break;
|
|
|
|
}
|
2015-12-22 20:37:35 +00:00
|
|
|
|
2016-01-27 01:37:58 +00:00
|
|
|
$result = self::getLastPageAuthors( $title );
|
2014-07-09 16:58:07 +00:00
|
|
|
break;
|
|
|
|
case 'load-first-author':
|
|
|
|
$title = Title::makeTitle( $parameters['namespace'], $parameters['title'] );
|
|
|
|
|
|
|
|
$revision = $title->getFirstRevision();
|
|
|
|
if ( $revision ) {
|
|
|
|
$result = $revision->getUserText();
|
|
|
|
} else {
|
|
|
|
$result = '';
|
|
|
|
}
|
|
|
|
|
2009-02-27 00:03:30 +00:00
|
|
|
break;
|
|
|
|
case 'get-page-restrictions':
|
|
|
|
$action = $parameters['action'];
|
|
|
|
$title = Title::makeTitle( $parameters['namespace'], $parameters['title'] );
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2009-02-27 00:03:30 +00:00
|
|
|
$rights = $title->getRestrictions( $action );
|
2017-06-15 14:23:34 +00:00
|
|
|
$rights = count( $rights ) ? $rights : [];
|
2009-04-05 19:07:47 +00:00
|
|
|
$result = $rights;
|
2009-02-27 00:03:30 +00:00
|
|
|
break;
|
|
|
|
case 'simple-user-accessor':
|
|
|
|
$user = $parameters['user'];
|
|
|
|
$method = $parameters['method'];
|
2009-10-07 13:57:06 +00:00
|
|
|
|
|
|
|
if ( !$user ) {
|
|
|
|
throw new MWException( 'No user parameter given.' );
|
2009-02-27 13:16:34 +00:00
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2013-01-07 00:02:41 +00:00
|
|
|
$obj = self::getUserObject( $user );
|
2009-10-07 13:57:06 +00:00
|
|
|
|
|
|
|
if ( !$obj ) {
|
2009-02-27 13:16:34 +00:00
|
|
|
throw new MWException( "Invalid username $user" );
|
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2017-06-15 14:23:34 +00:00
|
|
|
$result = call_user_func( [ $obj, $method ] );
|
2009-02-27 00:03:30 +00:00
|
|
|
break;
|
|
|
|
case 'user-age':
|
|
|
|
$user = $parameters['user'];
|
|
|
|
$asOf = $parameters['asof'];
|
2013-01-07 00:02:41 +00:00
|
|
|
$obj = self::getUserObject( $user );
|
2009-10-07 13:57:06 +00:00
|
|
|
|
|
|
|
if ( $obj->getId() == 0 ) {
|
2009-03-18 00:26:28 +00:00
|
|
|
$result = 0;
|
|
|
|
break;
|
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2009-02-27 00:03:30 +00:00
|
|
|
$registration = $obj->getRegistration();
|
2013-01-07 17:36:31 +00:00
|
|
|
$result = wfTimestamp( TS_UNIX, $asOf ) - wfTimestampOrNull( TS_UNIX, $registration );
|
2009-02-27 00:03:30 +00:00
|
|
|
break;
|
|
|
|
case 'user-groups':
|
2013-01-07 00:02:41 +00:00
|
|
|
// Deprecated but needed by old log entries
|
2009-02-27 00:03:30 +00:00
|
|
|
$user = $parameters['user'];
|
2013-01-07 00:02:41 +00:00
|
|
|
$obj = self::getUserObject( $user );
|
2009-04-05 19:07:47 +00:00
|
|
|
$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'] );
|
2012-11-20 15:16:58 +00:00
|
|
|
$result = AbuseFilter::revisionToString( $rev );
|
2009-02-27 00:03:30 +00:00
|
|
|
break;
|
|
|
|
case 'revision-text-by-timestamp':
|
|
|
|
$timestamp = $parameters['timestamp'];
|
|
|
|
$title = Title::makeTitle( $parameters['namespace'], $parameters['title'] );
|
2017-08-30 02:51:39 +00:00
|
|
|
$dbr = wfGetDB( DB_REPLICA );
|
2009-02-27 00:03:30 +00:00
|
|
|
$rev = Revision::loadFromTimestamp( $dbr, $title, $timestamp );
|
2012-11-20 15:16:58 +00:00
|
|
|
$result = AbuseFilter::revisionToString( $rev );
|
2009-02-27 00:03:30 +00:00
|
|
|
break;
|
|
|
|
default:
|
2015-08-06 17:15:42 +00:00
|
|
|
if ( Hooks::run( 'AbuseFilter-computeVariable',
|
2017-06-15 14:23:34 +00:00
|
|
|
[ $this->mMethod, $vars, $parameters, &$result ] ) ) {
|
2009-10-07 13:57:06 +00:00
|
|
|
throw new AFPException( 'Unknown variable compute type ' . $this->mMethod );
|
2009-03-25 05:18:27 +00:00
|
|
|
}
|
2009-02-27 00:03:30 +00:00
|
|
|
}
|
2009-10-07 13:57:06 +00:00
|
|
|
|
2009-05-26 13:08:15 +00:00
|
|
|
return $result instanceof AFPData
|
|
|
|
? $result : AFPData::newFromPHPVar( $result );
|
2009-02-27 00:03:30 +00:00
|
|
|
}
|
2016-01-27 01:37:58 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @param Title $title
|
|
|
|
* @return string[] List of the last 10 (unique) authors from $title
|
|
|
|
*/
|
|
|
|
public static function getLastPageAuthors( Title $title ) {
|
|
|
|
if ( !$title->exists() ) {
|
2017-06-15 14:23:34 +00:00
|
|
|
return [];
|
2016-01-27 01:37:58 +00:00
|
|
|
}
|
|
|
|
|
2018-02-15 21:55:51 +00:00
|
|
|
$cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
|
2016-01-27 01:37:58 +00:00
|
|
|
|
|
|
|
return $cache->getWithSetCallback(
|
|
|
|
$cache->makeKey( 'last-10-authors', 'revision', $title->getLatestRevID() ),
|
|
|
|
$cache::TTL_MINUTE,
|
|
|
|
function ( $oldValue, &$ttl, array &$setOpts ) use ( $title ) {
|
2017-08-30 02:51:39 +00:00
|
|
|
$dbr = wfGetDB( DB_REPLICA );
|
2016-01-27 01:37:58 +00:00
|
|
|
$setOpts += Database::getCacheSetOptions( $dbr );
|
|
|
|
// Get the last 100 edit authors with a trivial query (avoid T116557)
|
2018-03-09 21:23:38 +00:00
|
|
|
$revQuery = Revision::getQueryInfo();
|
2016-01-27 01:37:58 +00:00
|
|
|
$revAuthors = $dbr->selectFieldValues(
|
2018-03-09 21:23:38 +00:00
|
|
|
$revQuery['tables'],
|
|
|
|
$revQuery['fields']['rev_user_text'],
|
2017-06-15 14:23:34 +00:00
|
|
|
[ 'rev_page' => $title->getArticleID() ],
|
2016-01-27 01:37:58 +00:00
|
|
|
__METHOD__,
|
|
|
|
// Some pages have < 10 authors but many revisions (e.g. bot pages)
|
2017-06-15 14:23:34 +00:00
|
|
|
[ 'ORDER BY' => 'rev_timestamp DESC',
|
2017-04-24 19:03:12 +00:00
|
|
|
'LIMIT' => 100,
|
|
|
|
// Force index per T116557
|
2018-03-09 21:23:38 +00:00
|
|
|
'USE INDEX' => [ 'revision' => 'page_timestamp' ],
|
|
|
|
],
|
|
|
|
$revQuery['joins']
|
2016-01-27 01:37:58 +00:00
|
|
|
);
|
|
|
|
// Get the last 10 distinct authors within this set of edits
|
2017-06-15 14:23:34 +00:00
|
|
|
$users = [];
|
2016-01-27 01:37:58 +00:00
|
|
|
foreach ( $revAuthors as $author ) {
|
|
|
|
$users[$author] = 1;
|
|
|
|
if ( count( $users ) >= 10 ) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return array_keys( $users );
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
2009-03-25 05:18:27 +00:00
|
|
|
}
|