summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'MLEB/Translate/specials/SpecialSupportedLanguages.php')
-rw-r--r--MLEB/Translate/specials/SpecialSupportedLanguages.php284
1 files changed, 84 insertions, 200 deletions
diff --git a/MLEB/Translate/specials/SpecialSupportedLanguages.php b/MLEB/Translate/specials/SpecialSupportedLanguages.php
index 63f78109..07113217 100644
--- a/MLEB/Translate/specials/SpecialSupportedLanguages.php
+++ b/MLEB/Translate/specials/SpecialSupportedLanguages.php
@@ -8,6 +8,13 @@
* @license GPL-2.0-or-later
*/
+use MediaWiki\Extensions\Translate\Services;
+use MediaWiki\Extensions\Translate\Statistics\StatisticsUnavailable;
+use MediaWiki\Extensions\Translate\Statistics\TranslatorActivity;
+use MediaWiki\Extensions\Translate\Statistics\TranslatorActivityQuery;
+use MediaWiki\Logger\LoggerFactory;
+use MediaWiki\MediaWikiServices;
+
/**
* Implements special page Special:SupportedLanguages. The wiki administrator
* must define NS_PORTAL, otherwise this page does not work. This page displays
@@ -19,18 +26,24 @@
* @ingroup SpecialPage TranslateSpecialPage Stats
*/
class SpecialSupportedLanguages extends SpecialPage {
- /// Whether to skip and regenerate caches
- protected $purge = false;
+ private $options;
+
+ /** @var TranslatorActivity */
+ private $translatorActivity;
/// Cutoff time for inactivity in days
protected $period = 180;
public function __construct() {
parent::__construct( 'SupportedLanguages' );
+ // TODO: Use construction injection when 1.33 is no longer supported
+ // TODO: Only inject the needed configuration options when 1.33 is no longer supported
+ $this->options = MediaWikiServices::getInstance()->getMainConfig();
+ $this->translatorActivity = Services::getInstance()->getTranslatorActivity();
}
protected function getGroupName() {
- return 'wiki';
+ return 'translation';
}
public function getDescription() {
@@ -41,9 +54,6 @@ class SpecialSupportedLanguages extends SpecialPage {
$out = $this->getOutput();
$lang = $this->getLanguage();
- // Only for manual debugging nowdays
- $this->purge = false;
-
$this->setHeaders();
$out->addModules( 'ext.translate.special.supportedlanguages' );
$out->addModuleStyles( 'ext.translate.special.supportedlanguages' );
@@ -72,32 +82,29 @@ class SpecialSupportedLanguages extends SpecialPage {
$this->outputLanguageCloud( $languages, $names );
$out->addWikiMsg( 'supportedlanguages-count', $lang->formatNum( count( $languages ) ) );
- if ( $par && Language::isKnownLanguageTag( $par ) ) {
- $code = $par;
-
- $out->addWikiMsg( 'supportedlanguages-colorlegend', $this->getColorLegend() );
-
- $users = $this->fetchTranslators( $code );
- if ( $users === false ) {
- // generic-pool-error is from MW core
- $out->wrapWikiMsg( '<div class="warningbox">$1</div>', 'generic-pool-error' );
- return;
- }
+ if ( !$par || !Language::isKnownLanguageTag( $par ) ) {
+ return;
+ }
- global $wgTranslateAuthorBlacklist;
- $users = $this->filterUsers( $users, $code, $wgTranslateAuthorBlacklist );
- $this->preQueryUsers( $users );
- $this->showLanguage( $code, $users );
+ $language = $par;
+ try {
+ $data = $this->translatorActivity->inLanguage( $language );
+ } catch ( StatisticsUnavailable $e ) {
+ // generic-pool-error is from MW core
+ $out->wrapWikiMsg( '<div class="warningbox">$1</div>', 'generic-pool-error' );
+ return;
}
+
+ $users = $data['users'];
+ $users = $this->filterUsers( $users, $language );
+ $this->preQueryUsers( $users );
+ $this->showLanguage( $language, $users, $data['asOfTime'] );
}
- protected function showLanguage( $code, $users ) {
+ protected function showLanguage( string $code, array $users, int $cachedAt ): void {
$out = $this->getOutput();
$lang = $this->getLanguage();
- $usernames = array_keys( $users );
- $userStats = $this->getUserStats( $usernames );
-
// Information to be used inside the foreach loop.
$linkInfo = [];
$linkInfo['rc']['title'] = SpecialPage::getTitleFor( 'Recentchanges' );
@@ -142,21 +149,23 @@ class SpecialSupportedLanguages extends SpecialPage {
$linkList = $lang->listToText( $links );
$out->addHTML( '<p>' . $linkList . "</p>\n" );
- $this->makeUserList( $users, $userStats );
+ $this->makeUserList( $users );
+
+ $ageString = $this->getLanguage()->formatTimePeriod(
+ time() - $cachedAt,
+ [ 'noabbrevs' => true, 'avoid' => 'avoidseconds' ]
+ );
+ $out->addWikiMsg( 'supportedlanguages-colorlegend', $this->getColorLegend() );
+ $out->addWikiMsg( 'translate-supportedlanguages-cached', $ageString );
}
protected function languageCloud() {
- global $wgTranslateMessageNamespaces;
-
$cache = wfGetCache( CACHE_ANYTHING );
$cachekey = wfMemcKey( 'translate-supportedlanguages-language-cloud' );
- if ( $this->purge ) {
- $cache->delete( $cachekey );
- } else {
- $data = $cache->get( $cachekey );
- if ( is_array( $data ) ) {
- return $data;
- }
+
+ $data = $cache->get( $cachekey );
+ if ( is_array( $data ) ) {
+ return $data;
}
$dbr = wfGetDB( DB_REPLICA );
@@ -166,7 +175,7 @@ class SpecialSupportedLanguages extends SpecialPage {
$conds = [
# Without the quotes the rc_timestamp index isn't used and this query is much slower
"rc_timestamp > '$timestamp'",
- 'rc_namespace' => $wgTranslateMessageNamespaces,
+ 'rc_namespace' => $this->options->get( 'TranslateMessageNamespaces' ),
'rc_title' . $dbr->buildLike( $dbr->anyString(), '/', $dbr->anyString() ),
];
$options = [ 'GROUP BY' => 'lang', 'HAVING' => 'count > 20', 'ORDER BY' => 'NULL' ];
@@ -183,106 +192,16 @@ class SpecialSupportedLanguages extends SpecialPage {
return $data;
}
- /**
- * Fetch the translators for a language with caching
- *
- * @param string $code
- * @return array|bool Map of (user name => page count) or false on failure
- */
- public function fetchTranslators( $code ) {
- $cache = wfGetCache( CACHE_ANYTHING );
- $cachekey = wfMemcKey( 'translate-supportedlanguages-translator-list-v1', $code );
-
- if ( $this->purge ) {
- $cache->delete( $cachekey );
- $data = false;
- } else {
- $staleCutoffUnix = time() - 3600;
- $data = $cache->get( $cachekey );
- if ( is_array( $data ) && $data['asOfTime'] > $staleCutoffUnix ) {
- return $data['users'];
- }
- }
-
- $that = $this;
- $work = new PoolCounterWorkViaCallback(
- 'TranslateFetchTranslators',
- "TranslateFetchTranslators-$code",
- [
- 'doWork' => function () use ( $that, $code, $cache, $cachekey ) {
- $users = $that->loadTranslators( $code );
- $newData = [ 'users' => $users, 'asOfTime' => time() ];
- $cache->set( $cachekey, $newData, 86400 );
- return $users;
- },
- 'doCachedWork' => function () use ( $cache, $cachekey ) {
- $newData = $cache->get( $cachekey );
- // Use new cache value from other thread
- return is_array( $newData ) ? $newData['users'] : false;
- },
- 'fallback' => function () use ( $data ) {
- // Use stale cache if possible
- return is_array( $data ) ? $data['users'] : false;
- }
- ]
- );
-
- return $work->execute();
- }
-
- /**
- * Fetch the translators for a language
- *
- * @param string $code
- * @return array Map of (user name => page count)
- */
- public function loadTranslators( $code ) {
- global $wgTranslateMessageNamespaces;
-
- $dbr = wfGetDB( DB_REPLICA, 'vslow' );
-
- if ( class_exists( ActorMigration::class ) ) {
- $actorQuery = ActorMigration::newMigration()->getJoin( 'rev_user' );
- } else {
- $actorQuery = [
- 'tables' => [],
- 'fields' => [ 'rev_user_text' => 'rev_user_text' ],
- 'joins' => [],
- ];
- }
-
- $tables = [ 'page', 'revision' ] + $actorQuery['tables'];
- $fields = [
- 'rev_user_text' => $actorQuery['fields']['rev_user_text'],
- 'count(page_id) as count'
- ];
- $conds = [
- 'page_title' . $dbr->buildLike( $dbr->anyString(), '/', $code ),
- 'page_namespace' => $wgTranslateMessageNamespaces,
- ];
- $options = [ 'GROUP BY' => $actorQuery['fields']['rev_user_text'], 'ORDER BY' => 'NULL' ];
- $joins = [
- 'revision' => [ 'JOIN', 'page_id=rev_page' ],
- ] + $actorQuery['joins'];
-
- $res = $dbr->select( $tables, $fields, $conds, __METHOD__, $options, $joins );
-
- $data = [];
- foreach ( $res as $row ) {
- $data[$row->rev_user_text] = $row->count;
- }
-
- return $data;
- }
+ protected function filterUsers( array $users, string $code ): array {
+ $blacklist = $this->options->get( 'TranslateAuthorBlacklist' );
- protected function filterUsers( array $users, $code, $blacklist ) {
foreach ( array_keys( $users ) as $username ) {
# We do not know the group
$hash = "#;$code;$username";
$blacklisted = false;
foreach ( $blacklist as $rule ) {
- list( $type, $regex ) = $rule;
+ [ $type, $regex ] = $rule;
if ( preg_match( $regex, $hash ) ) {
if ( $type === 'white' ) {
@@ -324,7 +243,7 @@ class SpecialSupportedLanguages extends SpecialPage {
$out->addHTML( '</div>' );
}
- protected function makeUserList( $users, $stats ) {
+ protected function makeUserList( array $userStats ): void {
$day = 60 * 60 * 24;
// Scale of the activity colors, anything
@@ -334,30 +253,41 @@ class SpecialSupportedLanguages extends SpecialPage {
$links = [];
$statsTable = new StatsTable();
- arsort( $users );
- foreach ( $users as $username => $count ) {
+ // List users in descending order by number of translations in this language
+ uasort( $userStats, function ( $a, $b ) {
+ return -(
+ $a[TranslatorActivityQuery::USER_TRANSLATIONS]
+ <=>
+ $b[TranslatorActivityQuery::USER_TRANSLATIONS]
+ );
+ } );
+
+ foreach ( $userStats as $username => $stats ) {
$title = Title::makeTitleSafe( NS_USER, $username );
+ if ( !$title ) {
+ LoggerFactory::getInstance( 'Translate' )->warning(
+ "T248125: Got Title-invalid username '{username}'",
+ [ 'username' => $username ]
+ );
+ continue;
+ }
+
+ $count = $stats[TranslatorActivityQuery::USER_TRANSLATIONS];
+ $lastTranslationTimestamp = $stats[TranslatorActivityQuery::USER_LAST_ACTIVITY];
+
$enc = htmlspecialchars( $username );
$attribs = [];
$styles = [];
- if ( isset( $stats[$username][0] ) ) {
- if ( $count === -1 ) {
- $count = $stats[$username][0];
- }
+ $styles['font-size'] = round( log( $count, 10 ) * 30 ) + 70 . '%';
- $styles['font-size'] = round( log( $count, 10 ) * 30 ) + 70 . '%';
-
- $last = wfTimestamp( TS_UNIX ) - wfTimestamp( TS_UNIX, $stats[$username][1] );
- $last = round( $last / $day );
- $attribs['title'] = $this->msg( 'supportedlanguages-activity', $username )
- ->numParams( $count, $last )->text();
- $last = max( 1, min( $period, $last ) );
- $styles['border-bottom'] = '3px solid #' .
- $statsTable->getBackgroundColor( ( $period - $last ) / $period );
- } else {
- $enc = "<del>$enc</del>";
- }
+ $last = wfTimestamp( TS_UNIX ) - wfTimestamp( TS_UNIX, $lastTranslationTimestamp );
+ $last = round( $last / $day );
+ $attribs['title'] = $this->msg( 'supportedlanguages-activity', $username )
+ ->numParams( $count, $last )->text();
+ $last = max( 1, min( $period, $last ) );
+ $styles['border-bottom'] = '3px solid #' .
+ $statsTable->getBackgroundColor( ( $period - $last ) / $period );
$stylestr = $this->formatStyle( $styles );
if ( $stylestr ) {
@@ -368,10 +298,9 @@ class SpecialSupportedLanguages extends SpecialPage {
}
// for GENDER support
- $username = '';
- if ( count( $users ) === 1 ) {
- $keys = array_keys( $users );
- $username = $keys[0];
+ $usernameForGender = '';
+ if ( count( $userStats ) === 1 ) {
+ $usernameForGender = array_key_first( $userStats );
}
$linkList = $this->getLanguage()->listToText( $links );
@@ -379,57 +308,12 @@ class SpecialSupportedLanguages extends SpecialPage {
$html .= $this->msg( 'supportedlanguages-translators' )
->rawParams( $linkList )
->numParams( count( $links ) )
- ->params( $username )
+ ->params( $usernameForGender )
->escaped();
$html .= "</p>\n";
$this->getOutput()->addHTML( $html );
}
- protected function getUserStats( $users ) {
- $cache = wfGetCache( CACHE_ANYTHING );
- $dbr = wfGetDB( DB_REPLICA );
- $keys = [];
-
- foreach ( $users as $username ) {
- $keys[] = wfMemcKey( 'translate', 'sl-usertats', $username );
- }
-
- $cached = $cache->getMulti( $keys );
- $data = [];
-
- foreach ( $users as $index => $username ) {
- $cachekey = $keys[$index];
-
- if ( !$this->purge && isset( $cached[$cachekey] ) ) {
- $data[$username] = $cached[$cachekey];
- continue;
- }
-
- if ( class_exists( ActorMigration::class ) ) {
- $actorQuery = ActorMigration::newMigration()->getJoin( 'rev_user' );
- $tables = [ 'user', 'r' => [ 'revision' ] + $actorQuery['tables'] ];
- $joins = [
- 'r' => [ 'JOIN', 'user_id = rev_user' ],
- ] + $actorQuery['joins'];
- } else {
- $tables = [ 'user', 'revision' ];
- $joins = [ 'revision' => [ 'JOIN', 'user_id = rev_user' ] ];
- }
-
- $fields = [ 'user_name', 'user_editcount', 'MAX(rev_timestamp) as lastedit' ];
- $conds = [
- 'user_name' => $username,
- ];
-
- $res = $dbr->selectRow( $tables, $fields, $conds, __METHOD__, [], $joins );
- $data[$username] = [ $res->user_editcount, $res->lastedit ];
-
- $cache->set( $cachekey, $data[$username], 3600 );
- }
-
- return $data;
- }
-
protected function formatStyle( $styles ) {
$stylestr = '';
foreach ( $styles as $key => $value ) {
@@ -439,9 +323,9 @@ class SpecialSupportedLanguages extends SpecialPage {
return $stylestr;
}
- protected function preQueryUsers( $users ) {
+ protected function preQueryUsers( array $users ): void {
$lb = new LinkBatch;
- foreach ( $users as $user => $count ) {
+ foreach ( $users as $user => $data ) {
$user = Title::capitalize( $user, NS_USER );
$lb->add( NS_USER, $user );
$lb->add( NS_USER_TALK, $user );