Add $wgContribScoreIgnoreMinorEdits, bug fixes
A comprehensive list of changes made in this commit is provided in the README for this fork.
This commit is contained in:
26
README
26
README
@@ -1,2 +1,28 @@
|
|||||||
The Contribution Scores extension polls the wiki database to locate contributors with the highest contribution volume.
|
The Contribution Scores extension polls the wiki database to locate contributors with the highest contribution volume.
|
||||||
The extension is intended to add a fun metric for contributors to see how much they are helping out.
|
The extension is intended to add a fun metric for contributors to see how much they are helping out.
|
||||||
|
|
||||||
|
--- TEAR HERE ---
|
||||||
|
|
||||||
|
This is a modified version of Extension:Contribution Scores, the original source
|
||||||
|
code for which can be found here: https://www.mediawiki.org/wiki/Extension:Contribution_Scores
|
||||||
|
The rest of this document discussed changes made to this copy of the extension.
|
||||||
|
|
||||||
|
This fork developed by Eunakria the config option $wgContribScoreIgnoreMinorEdits,
|
||||||
|
which is mutually exclusive with $wgContribScoreUseRoughEditCount.
|
||||||
|
|
||||||
|
In addition to providing the aforementioned bug fixes, this fork also fixes two
|
||||||
|
other unwanted behaviors exhibited by the original extension:
|
||||||
|
|
||||||
|
1. When $wgContribScoreUseRoughEditCount was enabled, the extension would query
|
||||||
|
the user_editcount field of the user table but not actually join on the user
|
||||||
|
table, in the parser function {{#cscore}}. This was presumably never noticed
|
||||||
|
since this parser function is not frequently used.
|
||||||
|
|
||||||
|
2. On some databases, there was a potential for integer underflow when
|
||||||
|
subtracting unsigned values in both the parser function and contribution
|
||||||
|
score data. This could occur if $wgContribScoreUseRoughEditCount was enabled
|
||||||
|
and edits went uncounted in user_editcount, and was resolved by clamping the
|
||||||
|
right-hand side preimage of subtraction.
|
||||||
|
|
||||||
|
There are probably more bugs I don't know about, both in the original upstream
|
||||||
|
code *and* my code! In other words, caveat emptor.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "ContributionScores",
|
"name": "ContributionScores",
|
||||||
"author": "Tim Laqua",
|
"author": "Tim Laqua (modified by Eunakria)",
|
||||||
"url": "https://www.mediawiki.org/wiki/Extension:Contribution_Scores",
|
"url": "https://git.eunakria.com/Eunakria/mediawiki-extensions-ContributionScores",
|
||||||
"descriptionmsg": "contributionscores-desc",
|
"descriptionmsg": "contributionscores-desc",
|
||||||
"version": "1.26.1",
|
"version": "1.26.1",
|
||||||
"type": "specialpage",
|
"type": "specialpage",
|
||||||
@@ -53,7 +53,11 @@
|
|||||||
},
|
},
|
||||||
"ContribScoreUseRoughEditCount": {
|
"ContribScoreUseRoughEditCount": {
|
||||||
"value": false,
|
"value": false,
|
||||||
"description": "Set to true to use the rough number of edits in user table, for performance issue."
|
"description": "Set to true to use the rough number of edits in user table, for performance issue. Mutually exclusive with $ContribScoreIgnoreMinorEdits; this option will be disabled if the other is enabled."
|
||||||
|
},
|
||||||
|
"ContribScoreIgnoreMinorEdits": {
|
||||||
|
"value": false,
|
||||||
|
"description": "Set to true to ignore minor edits. Mutually exclusive with $wgContribScoreUseRoughEditCount; if this is enabled, the other option will be disabled."
|
||||||
},
|
},
|
||||||
"ContribScoreCacheTTL": {
|
"ContribScoreCacheTTL": {
|
||||||
"value": 30,
|
"value": 30,
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ use MediaWiki\User\ActorMigration;
|
|||||||
*
|
*
|
||||||
* @ingroup Extensions
|
* @ingroup Extensions
|
||||||
* @author Tim Laqua <t.laqua@gmail.com>
|
* @author Tim Laqua <t.laqua@gmail.com>
|
||||||
|
* @author Eunakria <eunakria@gmail.com>
|
||||||
*/
|
*/
|
||||||
class ContributionScores extends IncludableSpecialPage {
|
class ContributionScores extends IncludableSpecialPage {
|
||||||
const CONTRIBUTIONSCORES_MAXINCLUDELIMIT = 50;
|
const CONTRIBUTIONSCORES_MAXINCLUDELIMIT = 50;
|
||||||
@@ -28,8 +29,19 @@ class ContributionScores extends IncludableSpecialPage {
|
|||||||
$parser->setFunctionHook( 'cscore', [ self::class, 'efContributionScoresRender' ] );
|
$parser->setFunctionHook( 'cscore', [ self::class, 'efContributionScoresRender' ] );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See extension.json; this function returns whether or not to use the
|
||||||
|
* "rough edit count" (user_editcount, stored in the user table). Since
|
||||||
|
* ignoring minor edits is mutually exclusive with this and takes priority,
|
||||||
|
* we check here.
|
||||||
|
*/
|
||||||
|
private static function shouldUseRoughEditCount(): bool {
|
||||||
|
global $wgContribScoreIgnoreMinorEdits, $wgContribScoreUseRoughEditCount;
|
||||||
|
return $wgContribScoreUseRoughEditCount && !$wgContribScoreIgnoreMinorEdits;
|
||||||
|
}
|
||||||
|
|
||||||
public static function efContributionScoresRender( $parser, $usertext, $metric = 'score' ) {
|
public static function efContributionScoresRender( $parser, $usertext, $metric = 'score' ) {
|
||||||
global $wgContribScoreDisableCache, $wgContribScoreUseRoughEditCount;
|
global $wgContribScoreDisableCache, $wgContribScoreIgnoreMinorEdits;
|
||||||
|
|
||||||
if ( $wgContribScoreDisableCache ) {
|
if ( $wgContribScoreDisableCache ) {
|
||||||
$parser->getOutput()->updateCacheExpiry( 0 );
|
$parser->getOutput()->updateCacheExpiry( 0 );
|
||||||
@@ -41,37 +53,81 @@ class ContributionScores extends IncludableSpecialPage {
|
|||||||
|
|
||||||
if ( $user instanceof User && $user->isRegistered() ) {
|
if ( $user instanceof User && $user->isRegistered() ) {
|
||||||
global $wgLang;
|
global $wgLang;
|
||||||
$revVar = $wgContribScoreUseRoughEditCount ? 'user_editcount' : 'COUNT(rev_id)';
|
$revVar = self::shouldUseRoughEditCount() ? 'user_editcount' : 'COUNT(rev_id)';
|
||||||
|
|
||||||
$revWhere = ActorMigration::newMigration()->getWhere( $dbr, 'rev_user', $user );
|
$revWhere = ActorMigration::newMigration()->getWhere( $dbr, 'rev_user', $user );
|
||||||
|
$revConds = (array)$revWhere['conds'];
|
||||||
|
if ( $wgContribScoreIgnoreMinorEdits ) {
|
||||||
|
$revConds['rev_minor_edit'] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
# HACK: The original ContributionScores doesn't actually join on
|
||||||
|
# user despite querying on user... again not our fault, but
|
||||||
|
# definitely our problem. What a mess...
|
||||||
|
$revTables = array_merge([ 'revision' ], $revWhere['tables']);
|
||||||
|
$revJoins = $revWhere['joins'];
|
||||||
|
if ( self::shouldUseRoughEditCount() && !in_array( 'user', $revTables ) ) {
|
||||||
|
# Not familiar enough with MediaWiki to know if this is the
|
||||||
|
# right way to do things, but it seems like it. The
|
||||||
|
# ActorMigration class is deprecated and I think I'm supposed
|
||||||
|
# to just join on the bare names?
|
||||||
|
$revTables[] = 'actor';
|
||||||
|
$revTables[] = 'user';
|
||||||
|
$revJoins['actor'] = [ 'JOIN', 'rev_actor = actor_id' ];
|
||||||
|
$revJoins['user'] = [ 'JOIN', 'actor_user = user_id' ];
|
||||||
|
}
|
||||||
|
|
||||||
|
# HACK: This is not our fault (this bug exists in the original
|
||||||
|
# ContributionScores as well!) but in cases where the rough edit
|
||||||
|
# count underestimates the number of revisions, on MariaDB the
|
||||||
|
# unsigned subtraction can underflow, which will throw an error.
|
||||||
|
# So we have to clamp the subtraction rhs.
|
||||||
|
#
|
||||||
|
# Confused by buildLeast's API? Yeah, me too. It eventually
|
||||||
|
# dispatches to buildSuperlative, which looks like this:
|
||||||
|
# https://doc.wikimedia.org/mediawiki-core/master/php/SQLPlatform_8php_source.html#l00127
|
||||||
|
# The way to smuggle in an existing expression is by passing it as
|
||||||
|
# an array element with a non-integer key to the $fields argument,
|
||||||
|
# which is what we do here.
|
||||||
|
#
|
||||||
|
# Of course the use of buildLeast is necessitated by the fact that
|
||||||
|
# MariaDB and Postgres call it "LEAST", but SQLite calls it "MIN"
|
||||||
|
# (a more intuitive name, but the same as the aggregate function).
|
||||||
|
#
|
||||||
|
$clampedRhsSql = $dbr->buildLeast (
|
||||||
|
/* $fields: */ [ 'lhs' => $revVar, 'rhs' => 'COUNT(DISTINCT rev_page)' ],
|
||||||
|
/* $values: */ []
|
||||||
|
);
|
||||||
|
$wikiRankSql = "COUNT(DISTINCT rev_page) + SQRT($revVar-$clampedRhsSql) * 2";
|
||||||
|
|
||||||
if ( $metric == 'score' ) {
|
if ( $metric == 'score' ) {
|
||||||
$row = $dbr->selectRow(
|
$row = $dbr->selectRow(
|
||||||
[ 'revision' ] + $revWhere['tables'],
|
$revTables,
|
||||||
[ 'wiki_rank' => "COUNT(DISTINCT rev_page)+SQRT($revVar-COUNT(DISTINCT rev_page))*2" ],
|
[ 'wiki_rank' => $wikiRankSql ],
|
||||||
$revWhere['conds'],
|
$revConds,
|
||||||
__METHOD__,
|
__METHOD__,
|
||||||
[],
|
[],
|
||||||
$revWhere['joins']
|
$revJoins
|
||||||
);
|
);
|
||||||
$output = $wgLang->formatNum( round( $row->wiki_rank, 0 ) );
|
$output = $wgLang->formatNum( round( $row->wiki_rank, 0 ) );
|
||||||
} elseif ( $metric == 'changes' ) {
|
} elseif ( $metric == 'changes' ) {
|
||||||
$row = $dbr->selectRow(
|
$row = $dbr->selectRow(
|
||||||
[ 'revision' ] + $revWhere['tables'],
|
$revTables,
|
||||||
[ 'rev_count' => $revVar ],
|
[ 'rev_count' => $revVar ],
|
||||||
$revWhere['conds'],
|
$revConds,
|
||||||
__METHOD__,
|
__METHOD__,
|
||||||
[],
|
[],
|
||||||
$revWhere['joins']
|
$revJoins
|
||||||
);
|
);
|
||||||
$output = $wgLang->formatNum( $row->rev_count );
|
$output = $wgLang->formatNum( $row->rev_count );
|
||||||
} elseif ( $metric == 'pages' ) {
|
} elseif ( $metric == 'pages' ) {
|
||||||
$row = $dbr->selectRow(
|
$row = $dbr->selectRow(
|
||||||
[ 'revision' ] + $revWhere['tables'],
|
$revTables,
|
||||||
[ 'page_count' => 'COUNT(DISTINCT rev_page)' ],
|
[ 'page_count' => 'COUNT(DISTINCT rev_page)' ],
|
||||||
$revWhere['conds'],
|
$revConds,
|
||||||
__METHOD__,
|
__METHOD__,
|
||||||
[],
|
[],
|
||||||
$revWhere['joins']
|
$revJoins
|
||||||
);
|
);
|
||||||
$output = $wgLang->formatNum( $row->page_count );
|
$output = $wgLang->formatNum( $row->page_count );
|
||||||
} else {
|
} else {
|
||||||
@@ -92,7 +148,7 @@ class ContributionScores extends IncludableSpecialPage {
|
|||||||
*/
|
*/
|
||||||
public static function getContributionScoreData( $days, $limit ) {
|
public static function getContributionScoreData( $days, $limit ) {
|
||||||
global $wgContribScoreIgnoreBots, $wgContribScoreIgnoreBlockedUsers, $wgContribScoreIgnoreUsernames,
|
global $wgContribScoreIgnoreBots, $wgContribScoreIgnoreBlockedUsers, $wgContribScoreIgnoreUsernames,
|
||||||
$wgContribScoreUseRoughEditCount;
|
$wgContribScoreIgnoreMinorEdits;
|
||||||
|
|
||||||
$loadBalancer = MediaWikiServices::getInstance()->getDBLoadBalancer();
|
$loadBalancer = MediaWikiServices::getInstance()->getDBLoadBalancer();
|
||||||
$dbr = $loadBalancer->getConnection( DB_REPLICA );
|
$dbr = $loadBalancer->getConnection( DB_REPLICA );
|
||||||
@@ -105,6 +161,10 @@ class ContributionScores extends IncludableSpecialPage {
|
|||||||
|
|
||||||
$sqlWhere = [];
|
$sqlWhere = [];
|
||||||
|
|
||||||
|
if ( $wgContribScoreIgnoreMinorEdits ) {
|
||||||
|
$sqlWhere[] = 'rev_minor_edit = 0';
|
||||||
|
}
|
||||||
|
|
||||||
if ( $days > 0 ) {
|
if ( $days > 0 ) {
|
||||||
$date = time() - ( 60 * 60 * 24 * $days );
|
$date = time() - ( 60 * 60 * 24 * $days );
|
||||||
$sqlWhere[] = 'rev_timestamp > ' . $dbr->addQuotes( $dbr->timestamp( $date ) );
|
$sqlWhere[] = 'rev_timestamp > ' . $dbr->addQuotes( $dbr->timestamp( $date ) );
|
||||||
@@ -114,7 +174,7 @@ class ContributionScores extends IncludableSpecialPage {
|
|||||||
'rev_user' => $revUser,
|
'rev_user' => $revUser,
|
||||||
'page_count' => 'COUNT(DISTINCT rev_page)'
|
'page_count' => 'COUNT(DISTINCT rev_page)'
|
||||||
];
|
];
|
||||||
if ( $wgContribScoreUseRoughEditCount ) {
|
if ( self::shouldUseRoughEditCount() ) {
|
||||||
$revQuery['tables'][] = 'user';
|
$revQuery['tables'][] = 'user';
|
||||||
$revQuery['joins']['user'] = [ 'LEFT JOIN', [ "$revUser != 0", "user_id = $revUser" ] ];
|
$revQuery['joins']['user'] = [ 'LEFT JOIN', [ "$revUser != 0", "user_id = $revUser" ] ];
|
||||||
$sqlVars['rev_count'] = 'user_editcount';
|
$sqlVars['rev_count'] = 'user_editcount';
|
||||||
@@ -186,6 +246,14 @@ class ContributionScores extends IncludableSpecialPage {
|
|||||||
$revQuery['joins']
|
$revQuery['joins']
|
||||||
);
|
);
|
||||||
|
|
||||||
|
# HACK: See previous comment on the use of buildLeast. Not our fault,
|
||||||
|
# but still our problem...
|
||||||
|
$clampedRhsSql = $dbr->buildLeast(
|
||||||
|
/* $fields: */ [ 'rev_count', 'page_count' ],
|
||||||
|
/* $values: */ []
|
||||||
|
);
|
||||||
|
$wikiRankSql = "page_count+SQRT(rev_count-$clampedRhsSql)*2";
|
||||||
|
|
||||||
$sqlMostPagesOrRevs = $dbr->unionQueries( [ $sqlMostPages, $sqlMostRevs ], false );
|
$sqlMostPagesOrRevs = $dbr->unionQueries( [ $sqlMostPages, $sqlMostRevs ], false );
|
||||||
$res = $dbr->select(
|
$res = $dbr->select(
|
||||||
[
|
[
|
||||||
@@ -198,7 +266,7 @@ class ContributionScores extends IncludableSpecialPage {
|
|||||||
'user_real_name',
|
'user_real_name',
|
||||||
'page_count',
|
'page_count',
|
||||||
'rev_count',
|
'rev_count',
|
||||||
'wiki_rank' => 'page_count+SQRT(rev_count-page_count)*2',
|
'wiki_rank' => $wikiRankSql,
|
||||||
],
|
],
|
||||||
[],
|
[],
|
||||||
__METHOD__,
|
__METHOD__,
|
||||||
|
|||||||
Reference in New Issue
Block a user