Compare commits

..

16 Commits

Author SHA1 Message Date
a7f94ae854 Add $wgContribScoreIgnoreMinorEdits, bug fixes
A comprehensive list of changes made in this commit is provided in the
README for this fork.
2026-07-10 13:05:29 -04:00
libraryupgrader
ebe2898f76 build: Updating dependencies
composer:
* mediawiki/mediawiki-codesniffer: 50.0.0 → 51.0.0

npm:
* eslint-config-wikimedia: 0.32.3 → 0.32.4

Change-Id: Ie41a129b2c8033a053e88a3d44160dfa6a3c0fee
2026-05-06 04:16:17 +00:00
libraryupgrader
439756d5ff build: Updating npm dependencies
* grunt: 1.6.1 → 1.6.2
* grunt-legacy-log: 3.0.0 → 3.0.1
  * https://github.com/advisories/GHSA-f23m-r3pf-42rh
  * https://github.com/advisories/GHSA-r5fr-rjxr-66jc
* grunt-legacy-log-utils: 2.1.0 → 2.1.3
  * https://github.com/advisories/GHSA-f23m-r3pf-42rh
  * https://github.com/advisories/GHSA-r5fr-rjxr-66jc
* grunt-legacy-util: 2.0.1 → 2.0.2
  * https://github.com/advisories/GHSA-f23m-r3pf-42rh
  * https://github.com/advisories/GHSA-r5fr-rjxr-66jc
* lodash: 4.17.23 → 4.18.1
  * https://github.com/advisories/GHSA-f23m-r3pf-42rh
  * https://github.com/advisories/GHSA-r5fr-rjxr-66jc

Change-Id: I5fcbe92126437f8492b8d7a5cd5bdecb8168d641
2026-04-15 03:39:01 +00:00
libraryupgrader
6cf741b335 build: Updating brace-expansion to 1.1.13, 2.0.3
* https://github.com/advisories/GHSA-f886-m6hf-6m8v

Change-Id: I9adf13fe16cf6199b55bb91f342993dae93b76f7
2026-03-29 05:34:16 +00:00
libraryupgrader
edf4799dd8 build: Updating npm dependencies
* picomatch: 2.3.1, 4.0.3 → 2.3.2, 4.0.4
  * https://github.com/advisories/GHSA-3v7f-55p6-f55p
  * https://github.com/advisories/GHSA-c2c7-rcm5-vvqj
* yaml: 2.8.1 → 2.8.3
  * https://github.com/advisories/GHSA-48c2-rrv3-qjmp

Change-Id: Idaff99f4a0518b82bc5ac7de30c982d9ce5a9faf
2026-03-27 02:29:13 +00:00
libraryupgrader
8eeae0d6a7 build: Updating flatted to 3.4.2
* https://github.com/advisories/GHSA-rf6f-7fwh-wjgh

Change-Id: I39cf6400432507586cbd5e87584a0acdeeceecdc
2026-03-21 23:58:26 +00:00
libraryupgrader
8594ca249d build: Updating flatted to 3.4.1
* https://github.com/advisories/GHSA-25h7-pfq9-p65f

Change-Id: If168dc7471f09bdecfbfb21bb87d53c3390601fe
2026-03-14 20:18:19 +00:00
libraryupgrader
52c7d14fc7 build: Updating mediawiki/mediawiki-codesniffer to 50.0.0
Change-Id: I2a42c3c49a343974e00db65201efc686e100ddf6
2026-02-28 20:32:21 +00:00
libraryupgrader
c1145996fb build: Updating dependencies
composer:
* mediawiki/minus-x: 1.1.3 → 2.0.1

npm:
* ajv: 6.12.6 → 6.14.0
  * https://github.com/advisories/GHSA-2g4f-4pwh-qvx6

Change-Id: I4a83b4f858336cd17714f107f0727044c72febb5
2026-02-21 16:49:37 +00:00
libraryupgrader
706ba25406 build: Updating lodash to 4.17.23
* https://github.com/advisories/GHSA-xxjr-mmjv-4gpg

Change-Id: I3f8ffb7538662307450f3dd2268fb65f90c312bc
2026-01-23 04:03:15 +00:00
libraryupgrader
7934f5da8f build: Updating mediawiki/mediawiki-codesniffer to 49.0.0
Change-Id: I105d0409458d998d5cc34b3bd7fde6b9751136dd
2026-01-16 12:15:40 +00:00
Umherirrender
0c8eb5d7c0 Replace use of Xml methods for building Html markup
Bug: T341775
Change-Id: I506f544fae3ead363e3bcd86d33b9cc30c909763
2025-12-28 14:10:23 +01:00
libraryupgrader
76214cfab1 build: Updating eslint-config-wikimedia to 0.32.3
Change-Id: I6e54cd0d4fa5c691928f912078ec76492b7002cb
2025-12-04 10:08:15 +00:00
Edward Chernenko
6f95b63a0b Update for MediaWiki 1.43
1) replaced use of "ipblocks" table (removed from MediaWiki core).
2) removed unnecessary GROUP BY (user_name is already unique),
which was causing errors in MySQL strict mode.

Bug: T379715
Bug: T309096
Change-Id: Ib3e9dd01a52466aa19b20e1f7fbfe9bb0f5dd02f
2025-12-01 14:53:49 +00:00
libraryupgrader
c1d081fbb6 build: Updating eslint-config-wikimedia to 0.32.2
Change-Id: I3313befe18ebf0501c874c391304b20ed741358e
2025-11-27 17:44:02 +00:00
libraryupgrader
4859ac5e44 build: Updating eslint-config-wikimedia to 0.32.1
Change-Id: I83399db580c88a4399a1e9eba3a85f5f7b14ad11
2025-11-15 06:42:05 +00:00
6 changed files with 1104 additions and 1210 deletions

26
README
View File

@@ -1,2 +1,28 @@
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.
--- 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.

View File

@@ -1,7 +1,7 @@
{
"require-dev": {
"mediawiki/mediawiki-codesniffer": "48.0.0",
"mediawiki/minus-x": "1.1.3",
"mediawiki/mediawiki-codesniffer": "51.0.0",
"mediawiki/minus-x": "2.0.1",
"php-parallel-lint/php-console-highlighter": "1.0.0",
"php-parallel-lint/php-parallel-lint": "1.4.0"
},

View File

@@ -1,7 +1,7 @@
{
"name": "ContributionScores",
"author": "Tim Laqua",
"url": "https://www.mediawiki.org/wiki/Extension:Contribution_Scores",
"author": "Tim Laqua (modified by Eunakria)",
"url": "https://git.eunakria.com/Eunakria/mediawiki-extensions-ContributionScores",
"descriptionmsg": "contributionscores-desc",
"version": "1.26.1",
"type": "specialpage",
@@ -53,7 +53,11 @@
},
"ContribScoreUseRoughEditCount": {
"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": {
"value": 30,

2154
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@
"test": "grunt test"
},
"devDependencies": {
"eslint-config-wikimedia": "0.31.0",
"eslint-config-wikimedia": "0.32.4",
"grunt": "1.6.2",
"grunt-banana-checker": "0.13.0",
"grunt-eslint": "24.3.0"

View File

@@ -16,6 +16,7 @@ use MediaWiki\User\ActorMigration;
*
* @ingroup Extensions
* @author Tim Laqua <t.laqua@gmail.com>
* @author Eunakria <eunakria@gmail.com>
*/
class ContributionScores extends IncludableSpecialPage {
const CONTRIBUTIONSCORES_MAXINCLUDELIMIT = 50;
@@ -28,8 +29,19 @@ class ContributionScores extends IncludableSpecialPage {
$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' ) {
global $wgContribScoreDisableCache, $wgContribScoreUseRoughEditCount;
global $wgContribScoreDisableCache, $wgContribScoreIgnoreMinorEdits;
if ( $wgContribScoreDisableCache ) {
$parser->getOutput()->updateCacheExpiry( 0 );
@@ -41,37 +53,81 @@ class ContributionScores extends IncludableSpecialPage {
if ( $user instanceof User && $user->isRegistered() ) {
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 );
$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' ) {
$row = $dbr->selectRow(
[ 'revision' ] + $revWhere['tables'],
[ 'wiki_rank' => "COUNT(DISTINCT rev_page)+SQRT($revVar-COUNT(DISTINCT rev_page))*2" ],
$revWhere['conds'],
$revTables,
[ 'wiki_rank' => $wikiRankSql ],
$revConds,
__METHOD__,
[],
$revWhere['joins']
$revJoins
);
$output = $wgLang->formatNum( round( $row->wiki_rank, 0 ) );
} elseif ( $metric == 'changes' ) {
$row = $dbr->selectRow(
[ 'revision' ] + $revWhere['tables'],
$revTables,
[ 'rev_count' => $revVar ],
$revWhere['conds'],
$revConds,
__METHOD__,
[],
$revWhere['joins']
$revJoins
);
$output = $wgLang->formatNum( $row->rev_count );
} elseif ( $metric == 'pages' ) {
$row = $dbr->selectRow(
[ 'revision' ] + $revWhere['tables'],
$revTables,
[ 'page_count' => 'COUNT(DISTINCT rev_page)' ],
$revWhere['conds'],
$revConds,
__METHOD__,
[],
$revWhere['joins']
$revJoins
);
$output = $wgLang->formatNum( $row->page_count );
} else {
@@ -92,7 +148,7 @@ class ContributionScores extends IncludableSpecialPage {
*/
public static function getContributionScoreData( $days, $limit ) {
global $wgContribScoreIgnoreBots, $wgContribScoreIgnoreBlockedUsers, $wgContribScoreIgnoreUsernames,
$wgContribScoreUseRoughEditCount;
$wgContribScoreIgnoreMinorEdits;
$loadBalancer = MediaWikiServices::getInstance()->getDBLoadBalancer();
$dbr = $loadBalancer->getConnection( DB_REPLICA );
@@ -105,6 +161,10 @@ class ContributionScores extends IncludableSpecialPage {
$sqlWhere = [];
if ( $wgContribScoreIgnoreMinorEdits ) {
$sqlWhere[] = 'rev_minor_edit = 0';
}
if ( $days > 0 ) {
$date = time() - ( 60 * 60 * 24 * $days );
$sqlWhere[] = 'rev_timestamp > ' . $dbr->addQuotes( $dbr->timestamp( $date ) );
@@ -114,7 +174,7 @@ class ContributionScores extends IncludableSpecialPage {
'rev_user' => $revUser,
'page_count' => 'COUNT(DISTINCT rev_page)'
];
if ( $wgContribScoreUseRoughEditCount ) {
if ( self::shouldUseRoughEditCount() ) {
$revQuery['tables'][] = 'user';
$revQuery['joins']['user'] = [ 'LEFT JOIN', [ "$revUser != 0", "user_id = $revUser" ] ];
$sqlVars['rev_count'] = 'user_editcount';
@@ -124,7 +184,21 @@ class ContributionScores extends IncludableSpecialPage {
if ( $wgContribScoreIgnoreBlockedUsers ) {
$sqlWhere[] = "{$revUser} NOT IN " .
$dbr->buildSelectSubquery( 'ipblocks', 'ipb_user', 'ipb_user <> 0', __METHOD__ );
$dbr->buildSelectSubquery(
[
'block',
'block_target'
],
'bt_user',
'bt_user <> 0',
__METHOD__,
[],
[
'block_target' => [ 'JOIN', [
'bl_target=bt_id'
] ]
]
);
}
if ( $wgContribScoreIgnoreBots ) {
@@ -172,6 +246,14 @@ class ContributionScores extends IncludableSpecialPage {
$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 );
$res = $dbr->select(
[
@@ -184,7 +266,7 @@ class ContributionScores extends IncludableSpecialPage {
'user_real_name',
'page_count',
'rev_count',
'wiki_rank' => 'page_count+SQRT(rev_count-page_count)*2',
'wiki_rank' => $wikiRankSql,
],
[],
__METHOD__,
@@ -365,7 +447,7 @@ class ContributionScores extends IncludableSpecialPage {
$reportTitle = $this->msg( 'contributionscores-allrevisions' )->text();
}
$reportTitle .= ' ' . $this->msg( 'contributionscores-top' )->numParams( $limit )->text();
$title = Xml::element( 'h4',
$title = Html::element( 'h4',
[ 'class' => 'contributionscores-title' ],
$reportTitle
) . "\n";
@@ -402,7 +484,7 @@ class ContributionScores extends IncludableSpecialPage {
$reportTitle = $this->msg( 'contributionscores-allrevisions' )->text();
}
$reportTitle .= ' ' . $this->msg( 'contributionscores-top' )->numParams( $revs )->text();
$title = Xml::element( 'h2',
$title = Html::element( 'h2',
[ 'class' => 'contributionscores-title' ],
$reportTitle
) . "\n";