Compare commits

..

6 Commits

Author SHA1 Message Date
libraryupgrader
a35de703c0 build: Updating minimatch to 3.0.8
* https://github.com/advisories/GHSA-f8q6-p94x-37v3

Change-Id: I002d3cdf55f8634ce02a13de7ea50cc08dfb52fd
2022-10-23 07:32:57 +00:00
libraryupgrader
763278c76e build: Updating grunt to 1.5.3
Change-Id: Ie72c8e36aa39dfe82e38d53e5793f37a736c397c
2022-05-22 14:21:22 +00:00
libraryupgrader
9cf2aa9019 build: Updating dependencies
composer:
* mediawiki/mediawiki-codesniffer: 31.0.0 → 38.0.0
  The following sniffs are failing and were disabled:
  * MediaWiki.NamingConventions.PrefixedGlobalFunctions.allowedPrefix
  * MediaWiki.Usage.ExtendClassUsage.FunctionConfigUsage

npm:
* ajv: 6.11.0 → 6.12.6
  * https://github.com/advisories/GHSA-v88g-cgmw-v5xw
* ansi-regex: 5.0.0 → 5.0.1
  * https://github.com/advisories/GHSA-93q8-gq69-wqmw
  * https://github.com/advisories/GHSA-93q8-gq69-wqmw
* async: 3.2.0 → 3.2.3
  * https://github.com/advisories/GHSA-fwr7-v2mv-hh25
* minimist: 1.2.5 → 1.2.6
  * https://github.com/advisories/GHSA-xvch-5gv4-984h

Additional changes:
* Set `name` in package.json.
* composer.json: Updated phpcs command in composer test (T280592).
* composer.json: Added phpcs command to scripts (T280592).

Change-Id: Id3fadc2ea75f6bee4a1dfdfdc2f4892ec8d35a0d
2022-04-15 01:40:36 +00:00
libraryupgrader
8b144bcb65 build: Updating path-parse to 1.0.7
* https://npmjs.com/advisories/1773 (CVE-2021-23343)

Change-Id: Ieab54661a5aff68441817389f5c2a87764dbbee3
2021-09-02 16:31:03 +00:00
libraryupgrader
cf8cc9b595 build: Updating glob-parent to 5.1.2
* https://npmjs.com/advisories/1751 (CVE-2020-28469)

Change-Id: I60f6634ffcf1d6356837ecf5282098b0e7639cf1
2021-07-22 15:28:35 +00:00
libraryupgrader
de75d9f690 build: Updating npm dependencies
* grunt: 1.1.0 → 1.4.0
* lodash: 4.17.19 → 4.17.21
  * https://npmjs.com/advisories/1673 (CVE-2021-23337)

Additional changes:
* Dropped .php5 and .inc files from .phpcs.xml (T200956).

Change-Id: Iff562433ff7a4268f3eb7eb7a0c1ec1d0c43e540
2021-05-09 10:18:04 +00:00
27 changed files with 2512 additions and 5168 deletions

View File

@@ -2,7 +2,9 @@
"root": true,
"extends": [
"wikimedia/client",
"wikimedia/jquery",
"wikimedia/mediawiki"
]
"wikimedia/jquery"
],
"globals": {
"mw": false
}
}

View File

@@ -1,9 +1,11 @@
<?xml version="1.0"?>
<ruleset>
<rule ref="./vendor/mediawiki/mediawiki-codesniffer/MediaWiki">
<exclude name="MediaWiki.Commenting.FunctionComment.MissingDocumentationPublic" />
<exclude name="MediaWiki.Files.ClassMatchesFilename.NotMatch" />
<exclude name="MediaWiki.NamingConventions.PrefixedGlobalFunctions.allowedPrefix" />
<exclude name="MediaWiki.Usage.ExtendClassUsage.FunctionConfigUsage" />
<exclude name="PSR12.Properties.ConstantVisibility.NotFound" />
<exclude name="MediaWiki.Commenting.FunctionComment.MissingDocumentationProtected" />
<exclude name="MediaWiki.Commenting.FunctionComment.MissingDocumentationPublic" />
<exclude name="Squiz.Scope.MethodScope.Missing" />
</rule>
<file>.</file>

View File

@@ -233,16 +233,6 @@ $specialPageAliases['sk'] = [
'ContributionScores' => [ 'SkórePríspevkov' ],
];
/** Serbian Cyrillic (српски (ћирилица)) */
$specialPageAliases['sr-ec'] = [
'ContributionScores' => [ 'ОценеДоприноса' ],
];
/** Serbian Latin (srpski (latinica)) */
$specialPageAliases['sr-el'] = [
'ContributionScores' => [ 'OceneDoprinosa' ],
];
/** Swedish (svenska) */
$specialPageAliases['sv'] = [
'ContributionScores' => [ 'Bidragspoäng' ],
@@ -268,17 +258,12 @@ $specialPageAliases['uk'] = [
'ContributionScores' => [ 'Очки_внеску', 'Очки_вклада' ],
];
/** Chinese (中文) */
$specialPageAliases['zh'] = [
'ContributionScores' => [ 'ContributionScores' ],
];
/** Simplified Chinese (中文(简体)) */
/** Simplified Chinese (中文(简体)‎) */
$specialPageAliases['zh-hans'] = [
'ContributionScores' => [ '贡献得分' ],
];
/** Traditional Chinese (中文(繁體)) */
/** Traditional Chinese (中文(繁體)) */
$specialPageAliases['zh-hant'] = [
'ContributionScores' => [ '貢獻分數' ],
];

109
ContributionScores.php Normal file
View File

@@ -0,0 +1,109 @@
<?php
/** \file
* \brief Contains setup code for the Contribution Scores Extension.
*/
# Not a valid entry point, skip unless MEDIAWIKI is defined
if ( !defined( 'MEDIAWIKI' ) ) {
echo 'Contribution Scores extension';
exit( 1 );
}
$wgExtensionCredits['specialpage'][] = [
'path' => __FILE__,
'name' => 'Contribution Scores',
'url' => 'https://www.mediawiki.org/wiki/Extension:Contribution_Scores',
'author' => 'Tim Laqua',
'descriptionmsg' => 'contributionscores-desc',
'version' => '1.25.0'
];
define( 'CONTRIBUTIONSCORES_MAXINCLUDELIMIT', 50 );
$wgContribScoreReports = null;
// These settings can be overridden in LocalSettings.php.
// Set to true to exclude bots from the reporting.
$wgContribScoreIgnoreBlockedUsers = false;
// Set to true to exclude blocked users from the reporting.
$wgContribScoreIgnoreBots = false;
// Set to true to use real user names when available. Only for MediaWiki 1.19 and later.
$wgContribScoresUseRealName = false;
// Set to true to disable cache for parser function and inclusion of table.
$wgContribScoreDisableCache = false;
$wgAutoloadClasses['ContributionScores'] = __DIR__ . '/ContributionScores_body.php';
$wgSpecialPages['ContributionScores'] = 'ContributionScores';
$wgMessagesDirs['ContributionScores'] = __DIR__ . '/i18n';
$wgExtensionMessagesFiles['ContributionScoresAlias'] = __DIR__ . '/ContributionScores.alias.php';
$wgExtensionMessagesFiles['ContributionScoresMagic'] =
__DIR__ . '/ContributionScores.i18n.magic.php';
$wgHooks['ParserFirstCallInit'][] = 'efContributionScores_Setup';
function efContributionScores_Setup( &$parser ) {
$parser->setFunctionHook( 'cscore', 'efContributionScores_Render' );
return true;
}
function efContributionScores_Render( &$parser, $usertext, $metric = 'score' ) {
global $wgContribScoreDisableCache;
if ( $wgContribScoreDisableCache ) {
$parser->getOutput()->updateCacheExpiry( 0 );
}
$user = User::newFromName( $usertext );
$dbr = wfGetDB( DB_REPLICA );
if ( $user instanceof User && $user->isLoggedIn() ) {
global $wgLang;
$revWhere = ActorMigration::newMigration()->getWhere( $dbr, 'rev_user', $user );
if ( $metric == 'score' ) {
$res = $dbr->select(
[ 'revision' ] + $revWhere['tables'],
'COUNT(DISTINCT rev_page)+SQRT(COUNT(rev_id)-COUNT(DISTINCT rev_page))*2 AS wiki_rank',
$revWhere['conds'],
__METHOD__,
[],
$revWhere['joins']
);
$row = $dbr->fetchObject( $res );
$output = $wgLang->formatNum( round( $row->wiki_rank, 0 ) );
} elseif ( $metric == 'changes' ) {
$res = $dbr->select(
[ 'revision' ] + $revWhere['tables'],
'COUNT(rev_id) AS rev_count',
$revWhere['conds'],
__METHOD__,
[],
$revWhere['joins']
);
$row = $dbr->fetchObject( $res );
$output = $wgLang->formatNum( $row->rev_count );
} elseif ( $metric == 'pages' ) {
$res = $dbr->select(
[ 'revision' ] + $revWhere['tables'],
'COUNT(DISTINCT rev_page) AS page_count',
$revWhere['conds'],
__METHOD__,
[],
$revWhere['joins']
);
$row = $dbr->fetchObject( $res );
$output = $wgLang->formatNum( $row->page_count );
} else {
$output = wfMessage( 'contributionscores-invalidmetric' )->text();
}
} else {
$output = wfMessage( 'contributionscores-invalidusername' )->text();
}
return $parser->insertStripItem( $output, $parser->mStripState );
}

322
ContributionScores_body.php Normal file
View File

@@ -0,0 +1,322 @@
<?php
/** \file
* \brief Contains code for the ContributionScores Class (extends SpecialPage).
*/
use MediaWiki\MediaWikiServices;
/// Special page class for the Contribution Scores extension
/**
* Special page that generates a list of wiki contributors based
* on edit diversity (unique pages edited) and edit volume (total
* number of edits.
*
* @ingroup Extensions
* @author Tim Laqua <t.laqua@gmail.com>
*/
class ContributionScores extends IncludableSpecialPage {
public function __construct() {
parent::__construct( 'ContributionScores' );
}
/// Generates a "Contribution Scores" table for a given LIMIT and date range
/**
* Function generates Contribution Scores tables in HTML format (not wikiText)
*
* @param int $days Days in the past to run report for
* @param int $limit Maximum number of users to return (default 50)
* @param string|null $title The title of the table
* @param array $options array of options (default none; nosort/notools)
* @return string Html Table representing the requested Contribution Scores.
*/
function genContributionScoreTable( $days, $limit, $title = null, $options = 'none' ) {
global $wgContribScoreIgnoreBots, $wgContribScoreIgnoreBlockedUsers, $wgContribScoresUseRealName;
$opts = explode( ',', strtolower( $options ) );
$dbr = wfGetDB( DB_REPLICA );
$store = MediaWikiServices::getInstance()
->getRevisionStoreFactory()
->getRevisionStore();
$revQuery = $store->getQueryInfo();
$revUser = $revQuery['fields']['rev_user'];
$sqlWhere = [];
if ( $days > 0 ) {
$date = time() - ( 60 * 60 * 24 * $days );
$dateString = $dbr->timestamp( $date );
$sqlWhere[] = "rev_timestamp > '$dateString'";
}
if ( $wgContribScoreIgnoreBlockedUsers ) {
$sqlWhere[] = "{$revUser} NOT IN " .
$dbr->buildSelectSubquery( 'ipblocks', 'ipb_user', 'ipb_user <> 0', __METHOD__ );
}
if ( $wgContribScoreIgnoreBots ) {
$sqlWhere[] = "{$revUser} NOT IN " .
$dbr->buildSelectSubquery( 'user_groups', 'ug_user', [ 'ug_group' => 'bot' ], __METHOD__ );
}
if ( $dbr->unionSupportsOrderAndLimit() ) {
$order = [
'GROUP BY' => $revUser,
'ORDER BY' => 'page_count DESC',
'LIMIT' => $limit
];
} else {
$order = [ 'GROUP BY' => $revUser ];
}
$sqlMostPages = $dbr->selectSQLText(
$revQuery['tables'],
[
'rev_user' => $revUser,
'page_count' => 'COUNT(DISTINCT rev_page)',
'rev_count' => 'COUNT(rev_id)',
],
$sqlWhere,
__METHOD__,
$order,
$revQuery['joins']
);
if ( $dbr->unionSupportsOrderAndLimit() ) {
$order = [
'GROUP BY' => 'rev_user',
'ORDER BY' => 'rev_count DESC',
'LIMIT' => $limit
];
} else {
$order = [ 'GROUP BY' => 'rev_user' ];
}
$sqlMostRevs = $dbr->selectSQLText(
$revQuery['tables'],
[
'rev_user' => $revUser,
'page_count' => 'COUNT(DISTINCT rev_page)',
'rev_count' => 'COUNT(rev_id)',
],
$sqlWhere,
__METHOD__,
$order,
$revQuery['joins']
);
$sqlMostPagesOrRevs = $dbr->unionQueries( [ $sqlMostPages, $sqlMostRevs ], false );
$res = $dbr->select(
[
'u' => 'user',
's' => new Wikimedia\Rdbms\Subquery( $sqlMostPagesOrRevs ),
],
[
'user_id',
'user_name',
'user_real_name',
'page_count',
'rev_count',
'wiki_rank' => 'page_count+SQRT(rev_count-page_count)*2',
],
[],
__METHOD__,
[
'ORDER BY' => 'wiki_rank DESC',
'GROUP BY' => 'user_name',
'LIMIT' => $limit,
],
[
's' => [
'JOIN',
'user_id=rev_user'
]
]
);
$sortable = in_array( 'nosort', $opts ) ? '' : ' sortable';
$output = "<table class=\"wikitable contributionscores plainlinks{$sortable}\" >\n" .
"<tr class='header'>\n" .
Html::element( 'th', [], $this->msg( 'contributionscores-rank' )->text() ) .
Html::element( 'th', [], $this->msg( 'contributionscores-score' )->text() ) .
Html::element( 'th', [], $this->msg( 'contributionscores-pages' )->text() ) .
Html::element( 'th', [], $this->msg( 'contributionscores-changes' )->text() ) .
Html::element( 'th', [], $this->msg( 'contributionscores-username' )->text() );
$altrow = '';
$user_rank = 1;
$lang = $this->getLanguage();
foreach ( $res as $row ) {
// Use real name if option used and real name present.
if ( $wgContribScoresUseRealName && $row->user_real_name !== '' ) {
$userLink = Linker::userLink(
$row->user_id,
$row->user_name,
$row->user_real_name
);
} else {
$userLink = Linker::userLink(
$row->user_id,
$row->user_name
);
}
$output .= Html::closeElement( 'tr' );
$output .= "<tr class='{$altrow}'>\n" .
"<td class='content' style='padding-right:10px;text-align:right;'>" .
$lang->formatNum( round( $user_rank, 0 ) ) .
"\n</td><td class='content' style='padding-right:10px;text-align:right;'>" .
$lang->formatNum( round( $row->wiki_rank, 0 ) ) .
"\n</td><td class='content' style='padding-right:10px;text-align:right;'>" .
$lang->formatNum( $row->page_count ) .
"\n</td><td class='content' style='padding-right:10px;text-align:right;'>" .
$lang->formatNum( $row->rev_count ) .
"\n</td><td class='content'>" .
$userLink;
# Option to not display user tools
if ( !in_array( 'notools', $opts ) ) {
$output .= Linker::userToolLinks( $row->user_id, $row->user_name );
}
$output .= Html::closeElement( 'td' ) . "\n";
if ( $altrow == '' && empty( $sortable ) ) {
$altrow = 'odd ';
} else {
$altrow = '';
}
$user_rank++;
}
$output .= Html::closeElement( 'tr' );
$output .= Html::closeElement( 'table' );
$dbr->freeResult( $res );
if ( !empty( $title ) ) {
$output = Html::rawElement( 'table',
[
'style' => 'border-spacing: 0; padding: 0',
'class' => 'contributionscores-wrapper',
'lang' => htmlspecialchars( $lang->getCode() ),
'dir' => $lang->getDir()
],
"\n" .
"<tr>\n" .
"<td style='padding: 0px;'>{$title}</td>\n" .
"</tr>\n" .
"<tr>\n" .
"<td style='padding: 0px;'>{$output}</td>\n" .
"</tr>\n"
);
}
return $output;
}
function execute( $par ) {
$this->setHeaders();
if ( $this->including() ) {
$this->showInclude( $par );
} else {
$this->showPage();
}
return true;
}
/**
* Called when being included on a normal wiki page.
* Cache is disabled so it can depend on the user language.
* @param string|null $par A subpage give to the special page
*/
function showInclude( $par ) {
$days = null;
$limit = null;
$options = 'none';
if ( !empty( $par ) ) {
$params = explode( '/', $par );
$limit = intval( $params[0] );
if ( isset( $params[1] ) ) {
$days = intval( $params[1] );
}
if ( isset( $params[2] ) ) {
$options = $params[2];
}
}
if ( empty( $limit ) || $limit < 1 || $limit > CONTRIBUTIONSCORES_MAXINCLUDELIMIT ) {
$limit = 10;
}
if ( $days === null || $days < 0 ) {
$days = 7;
}
if ( $days > 0 ) {
$reportTitle = $this->msg( 'contributionscores-days' )->numParams( $days )->text();
} else {
$reportTitle = $this->msg( 'contributionscores-allrevisions' )->text();
}
$reportTitle .= ' ' . $this->msg( 'contributionscores-top' )->numParams( $limit )->text();
$title = Xml::element( 'h4',
[ 'class' => 'contributionscores-title' ],
$reportTitle
) . "\n";
$this->getOutput()->addHTML( $this->genContributionScoreTable(
$days,
$limit,
$title,
$options
) );
}
/**
* Show the special page
*/
function showPage() {
global $wgContribScoreReports;
if ( !is_array( $wgContribScoreReports ) ) {
$wgContribScoreReports = [
[ 7, 50 ],
[ 30, 50 ],
[ 0, 50 ]
];
}
$out = $this->getOutput();
$out->addWikiMsg( 'contributionscores-info' );
foreach ( $wgContribScoreReports as $scoreReport ) {
list( $days, $revs ) = $scoreReport;
if ( $days > 0 ) {
$reportTitle = $this->msg( 'contributionscores-days' )->numParams( $days )->text();
} else {
$reportTitle = $this->msg( 'contributionscores-allrevisions' )->text();
}
$reportTitle .= ' ' . $this->msg( 'contributionscores-top' )->numParams( $revs )->text();
$title = Xml::element( 'h2',
[ 'class' => 'contributionscores-title' ],
$reportTitle
) . "\n";
$out->addHTML( $title );
$out->addHTML( $this->genContributionScoreTable( $days, $revs ) );
}
}
protected function getGroupName() {
return 'wiki';
}
}

View File

@@ -11,6 +11,7 @@ module.exports = function ( grunt ) {
},
eslint: {
options: {
extensions: [ '.js', '.json' ],
cache: true
},
all: [

26
README
View File

@@ -1,28 +1,2 @@
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,9 +1,9 @@
{
"require-dev": {
"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"
"mediawiki/mediawiki-codesniffer": "38.0.0",
"mediawiki/minus-x": "1.1.0",
"php-parallel-lint/php-console-highlighter": "0.5.0",
"php-parallel-lint/php-parallel-lint": "1.2.0"
},
"scripts": {
"fix": [
@@ -16,10 +16,5 @@
"minus-x check ."
],
"phpcs": "phpcs -sp --cache"
},
"config": {
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true
}
}
}

View File

@@ -1,68 +0,0 @@
{
"name": "ContributionScores",
"author": "Tim Laqua (modified by Eunakria)",
"url": "https://git.eunakria.com/Eunakria/mediawiki-extensions-ContributionScores",
"descriptionmsg": "contributionscores-desc",
"version": "1.26.1",
"type": "specialpage",
"requires": {
"MediaWiki": ">= 1.40.0"
},
"SpecialPages": {
"ContributionScores": "ContributionScores"
},
"AutoloadClasses": {
"ContributionScores": "src/ContributionScores.php"
},
"Hooks": {
"ParserFirstCallInit": "ContributionScores::onParserFirstCallInit"
},
"MessagesDirs": {
"ContributionScores": [
"i18n"
]
},
"ExtensionMessagesFiles": {
"ContribScoreAlias": "ContributionScores.alias.php",
"ContribScoreMagic": "ContributionScores.i18n.magic.php"
},
"config": {
"ContribScoreReports": {
"value": null,
"description": "Each array defines a report - 7,50 is \"past 7 days \" and \"LIMIT 50 \" - Can be omitted."
},
"ContribScoreIgnoreBlockedUsers": {
"value": false,
"description": "Set to true to exclude blocked users from the reporting."
},
"ContribScoreIgnoreBots": {
"value": false,
"description": "Set to true to exclude bots users from the reporting."
},
"ContribScoreIgnoreUsernames": {
"value": [],
"description": "Array of usernames to exclude from the reporting."
},
"ContribScoresUseRealName": {
"value": false,
"description": "Set to true to use real user names when available."
},
"ContribScoreDisableCache": {
"value": false,
"description": "Set to true to disable cache for parser function and inclusion of table."
},
"ContribScoreUseRoughEditCount": {
"value": false,
"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,
"description": "Cache the contribution scores data, in minutes."
}
},
"manifest_version": 2
}

View File

@@ -3,8 +3,7 @@
"authors": [
"CERminator",
"KWiki",
"Srdjan m",
"Srđan"
"Srdjan m"
]
},
"contributionscores": "Rezultat doprinosa",

View File

@@ -4,8 +4,7 @@
"Byrial",
"Christian List",
"Kaare",
"Peter Alberti",
"Saederup92"
"Peter Alberti"
]
},
"contributionscores": "Bidragspoint",
@@ -15,7 +14,6 @@
"contributionscores-days": "Sidste {{PLURAL:$1|dag|$1 dage}}",
"contributionscores-allrevisions": "Gennem tiden",
"contributionscores-score": "Point",
"contributionscores-rank": "Rang",
"contributionscores-pages": "Sider",
"contributionscores-changes": "Ændringer",
"contributionscores-username": "Brugernavn",

View File

@@ -11,7 +11,7 @@
"contributionscores-info": "Ebe pawıtışê hecmê tedqiqê berzi ra, puwan raveri pelanê bêemsalanê vurniyayeyan senceno.",
"contributionscores-top": "(Tewr Gırde $1)",
"contributionscores-days": "{{PLURAL:$1|Roca peyêne|$1 Rocê peyêni}}",
"contributionscores-allrevisions": "Her dem",
"contributionscores-allrevisions": "Çaxan de hemi",
"contributionscores-score": "Puwan",
"contributionscores-rank": "Rêze",
"contributionscores-pages": "Peli",

View File

@@ -15,4 +15,4 @@
"contributionscores-username": "Username",
"contributionscores-invalidusername": "Invalid username",
"contributionscores-invalidmetric": "Invalid metric"
}
}

8
i18n/ext.json Normal file
View File

@@ -0,0 +1,8 @@
{
"@metadata": {
"authors": [
"Better"
]
},
"contributionscores-days": "Úrtimus $1 dias"
}

View File

@@ -1,7 +1,6 @@
{
"@metadata": {
"authors": [
"01miki10",
"Crt",
"Nike",
"Pxos",
@@ -19,7 +18,7 @@
"contributionscores-rank": "Sija",
"contributionscores-pages": "Sivuja",
"contributionscores-changes": "Muutoksia",
"contributionscores-username": "Käyttäjänimi",
"contributionscores-invalidusername": "Virheellinen käyttäjänimi",
"contributionscores-username": "Käyttäjätunnus",
"contributionscores-invalidusername": "Virheellinen käyttäjätunnus",
"contributionscores-invalidmetric": "Virheellinen muuttuja"
}

View File

@@ -21,6 +21,6 @@
"contributionscores-pages": "Pages",
"contributionscores-changes": "Changements",
"contributionscores-username": "Nom dutilisateur",
"contributionscores-invalidusername": "Nom dutilisateur incorrect",
"contributionscores-invalidusername": "Nom dutilisateur invalide",
"contributionscores-invalidmetric": "Métrique incorrecte"
}

View File

@@ -6,12 +6,11 @@
},
"contributionscores": "Contes de contribution",
"contributionscores-desc": "Calcula li funde de data del wiki por max alt [[Special:ContributionScores|volúmine de contribution de usator]]",
"contributionscores-info": "Li calcul primarimen mesura págines unic redactet, considerant li alt volume de redactiones.",
"contributionscores-info": "Contes es calculat quam seque:\n*Un (1) punctu por chascun págine unic redactet\n*Fonte de quadrat de (total de redactiones fat) - (total unique pages) * 2\nContes calculat in ti diversitá de redaction in pesa maniere súper de volúmine de redaction.\nBasicmen, ti conte mesura primarimen págines unic redactet, che consideration por alt volúmine de redaction - suposit esser un págine de alt qualitá.",
"contributionscores-top": "(Prim $1)",
"contributionscores-days": "Ultim {{PLURAL:$1|die|$1 dies}}",
"contributionscores-allrevisions": "Omni témpor",
"contributionscores-score": "Conte",
"contributionscores-rank": "Classification",
"contributionscores-pages": "Págines",
"contributionscores-changes": "Changes",
"contributionscores-username": "Nómine de usator",

View File

@@ -5,8 +5,6 @@
]
},
"contributionscores-days": "Lasta {{PLURAL:$1|dio|$1 dii}}",
"contributionscores-score": "Nombro di punti",
"contributionscores-pages": "Pagini",
"contributionscores-changes": "Modifikuri",
"contributionscores-username": "Uzeronomo"
}

View File

@@ -1,12 +1,10 @@
{
"@metadata": {
"authors": [
"Balyozxane",
"George Animal"
]
},
"contributionscores-allrevisions": "Hemû dem",
"contributionscores-pages": "Rûpel",
"contributionscores-changes": "Guhartin",
"contributionscores-username": "Navê bikarhêner"
}

View File

@@ -1,11 +1,9 @@
{
"@metadata": {
"authors": [
"Abbas dhothar",
"BukhariSaeed"
]
},
"contributionscores-days": "پچھلے{{PLURAL:$1|دن|$1 دناں}}",
"contributionscores-changes": "تبدیلیاں",
"contributionscores-username": "ورتن آلے دا ناں"
}

View File

@@ -3,18 +3,16 @@
"authors": [
"Helix84",
"Luky001",
"Teslaton",
"Yardom78"
"Teslaton"
]
},
"contributionscores": "Skóre príspevkov",
"contributionscores-desc": "Získava údaje z databázy wiki o [[Special:ContributionScores|množstve používateľských príspevkov]]",
"contributionscores-desc": "Zisťuje naväčší [[Special:ContributionScores|objem používateľských príspevkov]] z databázy wiki",
"contributionscores-info": "Skóre primárne meria jedinečné editované stránky s ohľadom na veľké množstvo úprav.",
"contributionscores-top": "(Najlepších $1)",
"contributionscores-days": "{{PLURAL:$1|Posledný $1 deň|Posledné $1 dni|Posledných $1 dní}}",
"contributionscores-allrevisions": "Celkom",
"contributionscores-allrevisions": "Celá história",
"contributionscores-score": "Skóre",
"contributionscores-rank": "Poradie",
"contributionscores-pages": "Stránky",
"contributionscores-changes": "Zmeny",
"contributionscores-username": "Používateľské meno",

View File

@@ -2,7 +2,6 @@
"@metadata": {
"authors": [
"Acamicamacaraca",
"Kizule",
"Milicevic01",
"Rancher",
"Sasa Stefanovic",

View File

@@ -6,5 +6,5 @@
},
"contributionscores-pages": "ገጻት",
"contributionscores-changes": "ለውጥታት",
"contributionscores-username": "ም ተጠቃሚ"
"contributionscores-username": "ም ተጠቃሚ"
}

View File

@@ -1,9 +1,7 @@
{
"@metadata": {
"authors": [
"Moon0319",
"PhiLiP",
"Roy17",
"Shinjiman",
"Shirayuki",
"Yueman"
@@ -14,7 +12,7 @@
"contributionscores-info": "呢個分數係會依主要嘅唯一編輯過嘅頁,同埋考慮高編輯量。",
"contributionscores-top": "(最高$1名)",
"contributionscores-days": "最近$1日",
"contributionscores-allrevisions": "有史以來",
"contributionscores-allrevisions": "全部時間",
"contributionscores-score": "分數",
"contributionscores-rank": "等級",
"contributionscores-pages": "版",

6543
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,9 +5,9 @@
"test": "grunt test"
},
"devDependencies": {
"eslint-config-wikimedia": "0.32.4",
"grunt": "1.6.2",
"grunt-banana-checker": "0.13.0",
"grunt-eslint": "24.3.0"
"eslint-config-wikimedia": "0.15.3",
"grunt": "1.5.3",
"grunt-banana-checker": "0.9.0",
"grunt-eslint": "22.0.0"
}
}

View File

@@ -1,507 +0,0 @@
<?php
/** \file
* \brief Contains code for the ContributionScores Class (extends SpecialPage).
*/
use MediaWiki\Html\Html;
use MediaWiki\Linker\Linker;
use MediaWiki\MediaWikiServices;
use MediaWiki\User\ActorMigration;
/// Special page class for the Contribution Scores extension
/**
* Special page that generates a list of wiki contributors based
* on edit diversity (unique pages edited) and edit volume (total
* number of edits.
*
* @ingroup Extensions
* @author Tim Laqua <t.laqua@gmail.com>
* @author Eunakria <eunakria@gmail.com>
*/
class ContributionScores extends IncludableSpecialPage {
const CONTRIBUTIONSCORES_MAXINCLUDELIMIT = 50;
public function __construct() {
parent::__construct( 'ContributionScores' );
}
public static function onParserFirstCallInit( Parser $parser ) {
$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, $wgContribScoreIgnoreMinorEdits;
if ( $wgContribScoreDisableCache ) {
$parser->getOutput()->updateCacheExpiry( 0 );
}
$user = User::newFromName( $usertext );
$loadBalancer = MediaWikiServices::getInstance()->getDBLoadBalancer();
$dbr = $loadBalancer->getConnection( DB_REPLICA );
if ( $user instanceof User && $user->isRegistered() ) {
global $wgLang;
$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(
$revTables,
[ 'wiki_rank' => $wikiRankSql ],
$revConds,
__METHOD__,
[],
$revJoins
);
$output = $wgLang->formatNum( round( $row->wiki_rank, 0 ) );
} elseif ( $metric == 'changes' ) {
$row = $dbr->selectRow(
$revTables,
[ 'rev_count' => $revVar ],
$revConds,
__METHOD__,
[],
$revJoins
);
$output = $wgLang->formatNum( $row->rev_count );
} elseif ( $metric == 'pages' ) {
$row = $dbr->selectRow(
$revTables,
[ 'page_count' => 'COUNT(DISTINCT rev_page)' ],
$revConds,
__METHOD__,
[],
$revJoins
);
$output = $wgLang->formatNum( $row->page_count );
} else {
$output = wfMessage( 'contributionscores-invalidmetric' )->text();
}
} else {
$output = wfMessage( 'contributionscores-invalidusername' )->text();
}
return $parser->insertStripItem( $output, $parser->getStripState() );
}
/**
* Function fetch Contribution Scores data from database
*
* @param int $days Days in the past to run report for
* @param int $limit Maximum number of users to return (default 50)
* @return array Data including the requested Contribution Scores.
*/
public static function getContributionScoreData( $days, $limit ) {
global $wgContribScoreIgnoreBots, $wgContribScoreIgnoreBlockedUsers, $wgContribScoreIgnoreUsernames,
$wgContribScoreIgnoreMinorEdits;
$loadBalancer = MediaWikiServices::getInstance()->getDBLoadBalancer();
$dbr = $loadBalancer->getConnection( DB_REPLICA );
$revQuery = ActorMigration::newMigration()->getJoin( 'rev_user' );
$revQuery['tables'] = array_merge( [ 'revision' ], $revQuery['tables'] );
$revUser = $revQuery['fields']['rev_user'];
$revUsername = $revQuery['fields']['rev_user_text'];
$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 ) );
}
$sqlVars = [
'rev_user' => $revUser,
'page_count' => 'COUNT(DISTINCT rev_page)'
];
if ( self::shouldUseRoughEditCount() ) {
$revQuery['tables'][] = 'user';
$revQuery['joins']['user'] = [ 'LEFT JOIN', [ "$revUser != 0", "user_id = $revUser" ] ];
$sqlVars['rev_count'] = 'user_editcount';
} else {
$sqlVars['rev_count'] = 'COUNT(rev_id)';
}
if ( $wgContribScoreIgnoreBlockedUsers ) {
$sqlWhere[] = "{$revUser} NOT IN " .
$dbr->buildSelectSubquery(
[
'block',
'block_target'
],
'bt_user',
'bt_user <> 0',
__METHOD__,
[],
[
'block_target' => [ 'JOIN', [
'bl_target=bt_id'
] ]
]
);
}
if ( $wgContribScoreIgnoreBots ) {
$sqlWhere[] = "{$revUser} NOT IN " .
$dbr->buildSelectSubquery( 'user_groups', 'ug_user', [
'ug_group' => 'bot',
'ug_expiry IS NULL OR ug_expiry >= ' . $dbr->addQuotes( $dbr->timestamp() )
], __METHOD__ );
}
if ( count( $wgContribScoreIgnoreUsernames ) ) {
$listIgnoredUsernames = $dbr->makeList( $wgContribScoreIgnoreUsernames );
$sqlWhere[] = "{$revUsername} NOT IN ($listIgnoredUsernames)";
}
if ( $dbr->unionSupportsOrderAndLimit() ) {
$order = [
'GROUP BY' => 'rev_user',
'ORDER BY' => 'page_count DESC',
'LIMIT' => $limit
];
} else {
$order = [ 'GROUP BY' => 'rev_user' ];
}
$sqlMostPages = $dbr->selectSQLText(
$revQuery['tables'],
$sqlVars,
$sqlWhere,
__METHOD__,
$order,
$revQuery['joins']
);
if ( $dbr->unionSupportsOrderAndLimit() ) {
$order['ORDER BY'] = 'rev_count DESC';
}
$sqlMostRevs = $dbr->selectSQLText(
$revQuery['tables'],
$sqlVars,
$sqlWhere,
__METHOD__,
$order,
$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(
[
'u' => 'user',
's' => new Wikimedia\Rdbms\Subquery( $sqlMostPagesOrRevs ),
],
[
'user_id',
'user_name',
'user_real_name',
'page_count',
'rev_count',
'wiki_rank' => $wikiRankSql,
],
[],
__METHOD__,
[
'ORDER BY' => 'wiki_rank DESC',
'LIMIT' => $limit,
],
[
's' => [
'JOIN',
'user_id=rev_user'
]
]
);
$ret = iterator_to_array( $res );
return $ret;
}
/// Generates a "Contribution Scores" table for a given LIMIT and date range
/**
* Function generates Contribution Scores tables in HTML format (not wikiText)
*
* @param int $days Days in the past to run report for
* @param int $limit Maximum number of users to return (default 50)
* @param string|null $title The title of the table
* @param array $options array of options (default none; nosort/notools)
* @return string Html Table representing the requested Contribution Scores.
*/
function genContributionScoreTable( $days, $limit, $title = null, $options = 'none' ) {
global $wgContribScoresUseRealName, $wgContribScoreCacheTTL;
$opts = explode( ',', strtolower( $options ) );
$sortable = in_array( 'nosort', $opts ) ? '' : ' sortable';
$output = "<table class=\"wikitable contributionscores plainlinks{$sortable}\" >\n" .
"<tr class='header'>\n" .
Html::element( 'th', [], $this->msg( 'contributionscores-rank' )->text() ) .
Html::element( 'th', [], $this->msg( 'contributionscores-score' )->text() ) .
Html::element( 'th', [], $this->msg( 'contributionscores-pages' )->text() ) .
Html::element( 'th', [], $this->msg( 'contributionscores-changes' )->text() ) .
Html::element( 'th', [], $this->msg( 'contributionscores-username' )->text() );
$cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
$data = $cache->getWithSetCallback(
$cache->makeKey( 'contributionscores', 'data-' . (string)$days ),
$wgContribScoreCacheTTL * 60,
function () use ( $days ) {
// Use max limit, as limit doesn't matter with performance.
// Avoid purge multiple times since limit on transclusion can be vary.
return self::getContributionScoreData( $days, self::CONTRIBUTIONSCORES_MAXINCLUDELIMIT );
} );
$lang = $this->getLanguage();
$altrow = '';
$user_rank = 1;
foreach ( $data as $row ) {
if ( $user_rank > $limit ) {
break;
}
// Use real name if option used and real name present.
if ( $wgContribScoresUseRealName && $row->user_real_name !== '' ) {
$userLink = Linker::userLink(
$row->user_id,
$row->user_name,
$row->user_real_name
);
} else {
$userLink = Linker::userLink(
$row->user_id,
$row->user_name
);
}
$output .= Html::closeElement( 'tr' );
$output .= "<tr class='{$altrow}'>\n" .
"<td class='content' style='padding-right:10px;text-align:right;'>" .
$lang->formatNum( $user_rank ) .
"\n</td><td class='content' style='padding-right:10px;text-align:right;'>" .
$lang->formatNum( round( $row->wiki_rank, 0 ) ) .
"\n</td><td class='content' style='padding-right:10px;text-align:right;'>" .
$lang->formatNum( $row->page_count ) .
"\n</td><td class='content' style='padding-right:10px;text-align:right;'>" .
$lang->formatNum( $row->rev_count ) .
"\n</td><td class='content'>" .
$userLink;
# Option to not display user tools
if ( !in_array( 'notools', $opts ) ) {
$output .= Linker::userToolLinks( $row->user_id, $row->user_name );
}
$output .= Html::closeElement( 'td' ) . "\n";
if ( $altrow == '' && empty( $sortable ) ) {
$altrow = 'odd ';
} else {
$altrow = '';
}
$user_rank++;
}
$output .= Html::closeElement( 'tr' );
$output .= Html::closeElement( 'table' );
// Transcluded on a normal wiki page.
if ( !empty( $title ) ) {
$output = Html::rawElement( 'table',
[
'style' => 'border-spacing: 0; padding: 0',
'class' => 'contributionscores-wrapper',
'lang' => htmlspecialchars( $lang->getCode() ),
'dir' => $lang->getDir()
],
"\n" .
"<tr>\n" .
"<td style='padding: 0px;'>{$title}</td>\n" .
"</tr>\n" .
"<tr>\n" .
"<td style='padding: 0px;'>{$output}</td>\n" .
"</tr>\n"
);
}
return $output;
}
function execute( $par ) {
$this->setHeaders();
if ( $this->including() ) {
$this->showInclude( $par );
} else {
$this->showPage();
}
return true;
}
/**
* Called when being included on a normal wiki page.
* Cache is disabled so it can depend on the user language.
* @param string|null $par A subpage give to the special page
*/
function showInclude( $par ) {
$days = null;
$limit = null;
$options = 'none';
if ( !empty( $par ) ) {
$params = explode( '/', $par );
$limit = intval( $params[0] );
if ( isset( $params[1] ) ) {
$days = intval( $params[1] );
}
if ( isset( $params[2] ) ) {
$options = $params[2];
}
}
if ( empty( $limit ) || $limit < 1 || $limit > self::CONTRIBUTIONSCORES_MAXINCLUDELIMIT ) {
$limit = 10;
}
if ( $days === null || $days < 0 ) {
$days = 7;
}
if ( $days > 0 ) {
$reportTitle = $this->msg( 'contributionscores-days' )->numParams( $days )->text();
} else {
$reportTitle = $this->msg( 'contributionscores-allrevisions' )->text();
}
$reportTitle .= ' ' . $this->msg( 'contributionscores-top' )->numParams( $limit )->text();
$title = Html::element( 'h4',
[ 'class' => 'contributionscores-title' ],
$reportTitle
) . "\n";
$this->getOutput()->addHTML( $this->genContributionScoreTable(
$days,
$limit,
$title,
$options
) );
}
/**
* Show the special page
*/
function showPage() {
global $wgContribScoreReports;
if ( !is_array( $wgContribScoreReports ) ) {
$wgContribScoreReports = [
[ 7, 50 ],
[ 30, 50 ],
[ 0, 50 ]
];
}
$out = $this->getOutput();
$out->addWikiMsg( 'contributionscores-info' );
foreach ( $wgContribScoreReports as $scoreReport ) {
[ $days, $revs ] = $scoreReport;
if ( $days > 0 ) {
$reportTitle = $this->msg( 'contributionscores-days' )->numParams( $days )->text();
} else {
$reportTitle = $this->msg( 'contributionscores-allrevisions' )->text();
}
$reportTitle .= ' ' . $this->msg( 'contributionscores-top' )->numParams( $revs )->text();
$title = Html::element( 'h2',
[ 'class' => 'contributionscores-title' ],
$reportTitle
) . "\n";
$out->addHTML( $title );
$out->addHTML( $this->genContributionScoreTable( $days, $revs ) );
}
}
public function maxIncludeCacheTime() {
global $wgContribScoreDisableCache, $wgContribScoreCacheTTL;
return $wgContribScoreDisableCache ? 0 : $wgContribScoreCacheTTL;
}
/**
* @inheritDoc
*/
protected function getGroupName() {
return 'wiki';
}
}