summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrian Evans <grknight@gentoo.org>2021-07-19 15:20:22 -0400
committerBrian Evans <grknight@gentoo.org>2021-07-19 15:20:22 -0400
commit9f092345e6bbecfde8c19e6d1490a6031a35f61f (patch)
tree2abb2398cd0df686e8608e15097ddc58b8995615 /MLEB/Translate/specials
parentOAuth: Update for fixes and security (diff)
downloadextensions-9f092345e6bbecfde8c19e6d1490a6031a35f61f.tar.gz
extensions-9f092345e6bbecfde8c19e6d1490a6031a35f61f.tar.bz2
extensions-9f092345e6bbecfde8c19e6d1490a6031a35f61f.zip
Update to MLEB 2021.06
Signed-off-by: Brian Evans <grknight@gentoo.org>
Diffstat (limited to 'MLEB/Translate/specials')
-rw-r--r--MLEB/Translate/specials/SpecialAggregateGroups.php15
-rw-r--r--MLEB/Translate/specials/SpecialExportTranslations.php67
-rw-r--r--MLEB/Translate/specials/SpecialImportTranslations.php26
-rw-r--r--MLEB/Translate/specials/SpecialLanguageStats.php87
-rw-r--r--MLEB/Translate/specials/SpecialManageGroups.php531
-rw-r--r--MLEB/Translate/specials/SpecialManageTranslatorSandbox.php335
-rw-r--r--MLEB/Translate/specials/SpecialMessageGroupStats.php5
-rw-r--r--MLEB/Translate/specials/SpecialSearchTranslations.php8
-rw-r--r--MLEB/Translate/specials/SpecialSupportedLanguages.php350
-rw-r--r--MLEB/Translate/specials/SpecialTranslate.php28
-rw-r--r--MLEB/Translate/specials/SpecialTranslationStash.php195
-rw-r--r--MLEB/Translate/specials/SpecialTranslationStats.php961
-rw-r--r--MLEB/Translate/specials/SpecialTranslations.php13
13 files changed, 542 insertions, 2079 deletions
diff --git a/MLEB/Translate/specials/SpecialAggregateGroups.php b/MLEB/Translate/specials/SpecialAggregateGroups.php
index 197d2462..afc39032 100644
--- a/MLEB/Translate/specials/SpecialAggregateGroups.php
+++ b/MLEB/Translate/specials/SpecialAggregateGroups.php
@@ -26,7 +26,7 @@ class SpecialAggregateGroups extends SpecialPage {
$this->addHelpLink( 'Help:Extension:Translate/Page translation administration' );
$out = $this->getOutput();
- $out->addModuleStyles( 'ext.translate.special.aggregategroups.styles' );
+ $out->addModuleStyles( 'ext.translate.specialpages.styles' );
// Check permissions
if ( $this->getUser()->isAllowed( 'translate-manage' ) ) {
@@ -48,7 +48,7 @@ class SpecialAggregateGroups extends SpecialPage {
$pages[] = $group;
} elseif ( $group instanceof AggregateMessageGroup ) {
$subgroups = TranslateMetadata::getSubgroups( $group->getId() );
- if ( $subgroups !== false ) {
+ if ( $subgroups !== null ) {
$aggregates[] = $group;
}
}
@@ -168,9 +168,7 @@ class SpecialAggregateGroups extends SpecialPage {
return $out;
}
- /**
- * @param array $aggregates
- */
+ /** @param array $aggregates */
protected function showAggregateGroups( array $aggregates ) {
$out = $this->getOutput();
$out->addModules( 'ext.translate.special.aggregategroups' );
@@ -183,11 +181,8 @@ class SpecialAggregateGroups extends SpecialPage {
$out->addHTML( $nojs );
- /**
- * @var AggregateMessageGroup $group
- */
+ /** @var AggregateMessageGroup $group */
foreach ( $aggregates as $group ) {
- // @phan-suppress-next-line SecurityCheck-XSS
$out->addHTML( $this->showAggregateGroup( $group ) );
}
@@ -230,7 +225,7 @@ class SpecialAggregateGroups extends SpecialPage {
$out = Html::openElement( 'ol', [ 'id' => $id ] );
// Not calling $parent->getGroups() because it has done filtering already
- $subgroupIds = TranslateMetadata::getSubgroups( $parent->getId() );
+ $subgroupIds = TranslateMetadata::getSubgroups( $parent->getId() ) ?? [];
// Get the respective groups and sort them
$subgroups = MessageGroups::getGroupsById( $subgroupIds );
diff --git a/MLEB/Translate/specials/SpecialExportTranslations.php b/MLEB/Translate/specials/SpecialExportTranslations.php
index 2b23040d..2e39f3c0 100644
--- a/MLEB/Translate/specials/SpecialExportTranslations.php
+++ b/MLEB/Translate/specials/SpecialExportTranslations.php
@@ -1,4 +1,5 @@
<?php
+
/**
* @license GPL-2.0-or-later
* @ingroup SpecialPage TranslateSpecialPage
@@ -11,13 +12,10 @@ class SpecialExportTranslations extends SpecialPage {
/** @var string */
protected $language;
-
/** @var string */
protected $format;
-
/** @var string */
protected $groupId;
-
/** @var string[] */
public static $validFormats = [ 'export-as-po', 'export-to-file' ];
@@ -25,9 +23,7 @@ class SpecialExportTranslations extends SpecialPage {
parent::__construct( 'ExportTranslations' );
}
- /**
- * @param null|string $par
- */
+ /** @param null|string $par */
public function execute( $par ) {
$out = $this->getOutput();
$request = $this->getRequest();
@@ -84,8 +80,7 @@ class SpecialExportTranslations extends SpecialPage {
'default' => $this->format,
],
];
- $form = HTMLForm::factory( 'ooui', $fields, $this->getContext() );
- $form
+ HTMLForm::factory( 'ooui', $fields, $this->getContext() )
->setMethod( 'get' )
->setWrapperLegendMsg( 'translate-page-settings-legend' )
->setSubmitTextMsg( 'translate-submit' )
@@ -93,9 +88,7 @@ class SpecialExportTranslations extends SpecialPage {
->displayForm( false );
}
- /**
- * @return array
- */
+ /** @return array */
protected function getGroupOptions() {
$selected = $this->groupId;
$groups = MessageGroups::getAllGroups();
@@ -115,9 +108,7 @@ class SpecialExportTranslations extends SpecialPage {
return $options;
}
- /**
- * @return array
- */
+ /** @return array */
protected function getLanguageOptions() {
$languages = TranslateUtils::getLanguageNames( 'en' );
$options = [];
@@ -128,9 +119,7 @@ class SpecialExportTranslations extends SpecialPage {
return $options;
}
- /**
- * @return array
- */
+ /** @return array */
protected function getFormatOptions() {
$options = [];
foreach ( self::$validFormats as $format ) {
@@ -140,9 +129,7 @@ class SpecialExportTranslations extends SpecialPage {
return $options;
}
- /**
- * @return Status
- */
+ /** @return Status */
protected function checkInput() {
$status = Status::newGood();
@@ -226,26 +213,30 @@ class SpecialExportTranslations extends SpecialPage {
default:
// @todo Add web viewing for groups other than WikiPageMessageGroup
- $pageTranslation = $this->getConfig()->get( 'EnablePageTranslation' );
- if ( $pageTranslation && $group instanceof WikiPageMessageGroup ) {
- $collection->loadTranslations();
- $page = TranslatablePage::newFromTitle( $group->getTitle() );
- $text = $page->getParse()->getTranslationPageText( $collection );
- $displayTitle = $page->getPageDisplayTitle( $this->language );
- if ( $displayTitle ) {
- $text = "{{DISPLAYTITLE:$displayTitle}}$text";
- }
- $box = Html::element(
- 'textarea',
- [ 'id' => 'wpTextbox', 'rows' => 40, ],
- $text
- );
- $out->addHTML( $box );
- return;
+ if ( !$group instanceof WikiPageMessageGroup ) {
+ // This should have been prevented at validation. See checkInput().
+ throw new LogicException( 'Unexpected export format.' );
+ }
+
+ $translatablePage = TranslatablePage::newFromTitle( $group->getTitle() );
+ $translationPage = $translatablePage->getTranslationPage( $collection->getLanguage() );
+
+ $translationPage->filterMessageCollection( $collection );
+ $messages = $translationPage->extractMessages( $collection );
+ $text = $translationPage->generateSourceFromTranslations( $messages );
+
+ $displayTitle = $translatablePage->getPageDisplayTitle( $this->language );
+ if ( $displayTitle ) {
+ $text = "{{DISPLAYTITLE:$displayTitle}}$text";
}
- // This should have been prevented at validation. See checkInput().
- throw new Exception( 'Unexpected export format.' );
+ $box = Html::element(
+ 'textarea',
+ [ 'id' => 'wpTextbox', 'rows' => 40, ],
+ $text
+ );
+ $out->addHTML( $box );
+
}
}
diff --git a/MLEB/Translate/specials/SpecialImportTranslations.php b/MLEB/Translate/specials/SpecialImportTranslations.php
index 7f6228e2..eea01221 100644
--- a/MLEB/Translate/specials/SpecialImportTranslations.php
+++ b/MLEB/Translate/specials/SpecialImportTranslations.php
@@ -201,7 +201,12 @@ class SpecialImportTranslations extends SpecialPage {
$ffs = new GettextFFS( $group );
- $parseOutput = $ffs->readFromVariable( $data );
+ try {
+ $parseOutput = $ffs->readFromVariable( $data );
+ } catch ( GettextParseException $e ) {
+ return [ 'no-headers' ];
+ }
+
// Special data added by GettextFFS
$metadata = $parseOutput['EXTRA']['METADATA'];
@@ -213,20 +218,27 @@ class SpecialImportTranslations extends SpecialPage {
return [ 'ok', $parseOutput ];
}
+ private function getCache() {
+ return ObjectCache::getInstance( CACHE_DB );
+ }
+
protected function setCachedData( $data ) {
- $key = wfMemcKey( 'translate', 'webimport', $this->getUser()->getId() );
- wfGetCache( CACHE_DB )->set( $key, $data, 60 * 30 );
+ $cache = self::getCache();
+ $key = $cache->makeKey( 'translate', 'webimport', $this->getUser()->getId() );
+ $cache->set( $key, $data, 60 * 30 );
}
protected function getCachedData() {
- $key = wfMemcKey( 'translate', 'webimport', $this->getUser()->getId() );
+ $cache = self::getCache();
+ $key = $cache->makeKey( 'translate', 'webimport', $this->getUser()->getId() );
- return wfGetCache( CACHE_DB )->get( $key );
+ return $cache->get( $key );
}
protected function deleteCachedData() {
- $key = wfMemcKey( 'translate', 'webimport', $this->getUser()->getId() );
+ $cache = self::getCache();
+ $key = $cache->makeKey( 'translate', 'webimport', $this->getUser()->getId() );
- return wfGetCache( CACHE_DB )->delete( $key );
+ return $cache->delete( $key );
}
}
diff --git a/MLEB/Translate/specials/SpecialLanguageStats.php b/MLEB/Translate/specials/SpecialLanguageStats.php
index 161b9683..62aa3f79 100644
--- a/MLEB/Translate/specials/SpecialLanguageStats.php
+++ b/MLEB/Translate/specials/SpecialLanguageStats.php
@@ -20,56 +20,43 @@
* @ingroup SpecialPage TranslateSpecialPage Stats
*/
class SpecialLanguageStats extends SpecialPage {
- /**
- * @var StatsTable
- */
+ /** @var StatsTable */
protected $table;
-
- /**
- * @var Array
- */
+ /** @var array */
protected $targetValueName = [ 'code', 'language' ];
-
/**
* Most of the displayed numbers added together at the bottom of the table.
*/
protected $totals;
-
/**
* Flag to set if nothing to show.
* @var bool
*/
protected $nothing = false;
-
/**
* Flag to set if not all numbers are available.
* @var bool
*/
protected $incomplete = false;
-
/**
* Whether to hide rows which are fully translated.
* @var bool
*/
protected $noComplete = true;
-
/**
* Whether to hide rows which are fully untranslated.
* @var bool
*/
protected $noEmpty = false;
-
/**
* The target of stats, language code or group id.
*/
protected $target;
-
/**
* Whether to regenerate stats. Activated by action=purge in query params.
* @var bool
*/
protected $purge;
-
/**
* Helper variable to avoid overcounting message groups that appear
* multiple times in the list with different parents. Aggregate message
@@ -78,10 +65,7 @@ class SpecialLanguageStats extends SpecialPage {
* @var array
*/
protected $statsCounted = [];
-
- /**
- * @var array
- */
+ /** @var array */
protected $states;
public function __construct() {
@@ -472,7 +456,7 @@ class SpecialLanguageStats extends SpecialPage {
/**
* Actually creates the table for single message group, unless it
- * is blacklisted or hidden by filters.
+ * is in the exclusion list or hidden by filters.
* @param MessageGroup $group
* @param array $cache
* @param MessageGroup|null $parent
@@ -483,7 +467,7 @@ class SpecialLanguageStats extends SpecialPage {
) {
$groupId = $group->getId();
- if ( $this->table->isBlacklisted( $groupId, $this->target ) !== null ) {
+ if ( $this->table->isExcluded( $groupId, $this->target ) !== null ) {
return '';
}
@@ -521,35 +505,40 @@ class SpecialLanguageStats extends SpecialPage {
$params[] = $this->getLanguage()->getCode();
$params[] = md5( $this->target );
$params[] = $parent ? $parent->getId() : '!';
- $cachekey = wfMemcKey( __METHOD__ . '-v3', implode( '-', $params ) );
- $cacheval = wfGetCache( CACHE_ANYTHING )->get( $cachekey );
- if ( is_string( $cacheval ) ) {
- return $cacheval;
- }
- // Any data variable read below should be part of the cache key above
- $extra = [];
- if ( $translated === $total ) {
- $extra = [ 'action' => 'proofread' ];
- }
-
- $rowParams = [];
- $rowParams['data-groupid'] = $groupId;
- $rowParams['class'] = get_class( $group );
- if ( $parent ) {
- $rowParams['data-parentgroup'] = $parent->getId();
- }
-
- $out = "\t" . Html::openElement( 'tr', $rowParams );
- $out .= "\n\t\t" . Html::rawElement( 'td', [],
- $this->table->makeGroupLink( $group, $this->target, $extra ) );
- $out .= $this->table->makeNumberColumns( $stats );
- $out .= $this->getWorkflowStateCell( $groupId, $state );
- $out .= "\n\t" . Html::closeElement( 'tr' ) . "\n";
-
- wfGetCache( CACHE_ANYTHING )->set( $cachekey, $out, 3600 * 24 );
-
- return $out;
+ $cache = ObjectCache::getInstance( CACHE_ANYTHING );
+
+ return $cache->getWithSetCallback(
+ $cache->makeKey( __METHOD__ . '-v3', implode( '-', $params ) ),
+ $cache::TTL_DAY,
+ function () use ( $translated, $total, $groupId, $group, $parent, $stats, $state ) {
+ // Any data variable read below should be part of the cache key above
+ $extra = [];
+ if ( $translated === $total ) {
+ $extra = [ 'action' => 'proofread' ];
+ }
+
+ $rowParams = [];
+ $rowParams['data-groupid'] = $groupId;
+ $rowParams['class'] = get_class( $group );
+ if ( $parent ) {
+ $rowParams['data-parentgroup'] = $parent->getId();
+ }
+
+ return "\t" .
+ Html::openElement( 'tr', $rowParams ) .
+ "\n\t\t" .
+ Html::rawElement(
+ 'td',
+ [],
+ $this->table->makeGroupLink( $group, $this->target, $extra )
+ ) . $this->table->makeNumberColumns( $stats ) .
+ $this->getWorkflowStateCell( $groupId, $state ) .
+ "\n\t" .
+ Html::closeElement( 'tr' ) .
+ "\n";
+ }
+ );
}
protected function getWorkflowStates( $field = 'tgr_group', $filter = 'tgr_lang' ) {
diff --git a/MLEB/Translate/specials/SpecialManageGroups.php b/MLEB/Translate/specials/SpecialManageGroups.php
index 71f28021..5db58fa2 100644
--- a/MLEB/Translate/specials/SpecialManageGroups.php
+++ b/MLEB/Translate/specials/SpecialManageGroups.php
@@ -1,17 +1,14 @@
<?php
-/**
- * Implements special page for group management, where file based message
- * groups are be managed.
- *
- * @file
- * @author Niklas Laxström
- * @author Siebrand Mazeland
- * @license GPL-2.0-or-later
- */
+declare( strict_types = 1 );
-use MediaWiki\Extensions\Translate\MessageSync\MessageSourceChange;
+use MediaWiki\Extension\Translate\MessageSync\MessageSourceChange;
+use MediaWiki\Extension\Translate\Synchronization\DisplayGroupSynchronizationInfo;
+use MediaWiki\Extension\Translate\Synchronization\GroupSynchronizationCache;
+use MediaWiki\Extension\Translate\Synchronization\MessageUpdateParameter;
use MediaWiki\MediaWikiServices;
+use MediaWiki\Revision\RevisionLookup;
use MediaWiki\Revision\SlotRecord;
+use OOUI\ButtonInputWidget;
/**
* Class for special page Special:ManageMessageGroups. On this special page
@@ -19,30 +16,44 @@ use MediaWiki\Revision\SlotRecord;
* allows updating of the file cache, import and fuzzy for source language
* messages, as well as import/update of messages in other languages.
*
+ * @author Niklas Laxström
+ * @author Siebrand Mazeland
* @ingroup SpecialPage TranslateSpecialPage
- * Rewritten in 2012-04-23
+ * @license GPL-2.0-or-later
*/
class SpecialManageGroups extends SpecialPage {
+ private const GROUP_SYNC_INFO_WRAPPER_CLASS = 'smg-group-sync-cache-info';
private const RIGHT = 'translate-manage';
-
- /**
- * @var DifferenceEngine
- */
+ /** @var DifferenceEngine */
protected $diff;
-
- /**
- * @var string Path to the change cdb file.
- */
+ /** @var string Path to the change cdb file. */
protected $cdb;
-
- /**
- * @var bool Has the necessary right specified by the RIGHT constant
- */
+ /** @var bool Has the necessary right specified by the RIGHT constant */
protected $hasRight = false;
-
- public function __construct() {
+ /** @var Language */
+ private $contLang;
+ /** @var NamespaceInfo */
+ private $nsInfo;
+ /** @var RevisionLookup */
+ private $revLookup;
+ /** @var GroupSynchronizationCache */
+ private $synchronizationCache;
+ /** @var DisplayGroupSynchronizationInfo */
+ private $displayGroupSyncInfo;
+
+ public function __construct(
+ Language $contLang,
+ NamespaceInfo $nsInfo,
+ RevisionLookup $revLookup,
+ GroupSynchronizationCache $synchronizationCache
+ ) {
// Anyone is allowed to see, but actions are restricted
parent::__construct( 'ManageMessageGroups' );
+ $this->contLang = $contLang;
+ $this->nsInfo = $nsInfo;
+ $this->revLookup = $revLookup;
+ $this->synchronizationCache = $synchronizationCache;
+ $this->displayGroupSyncInfo = new DisplayGroupSynchronizationInfo( $this, $this->getLinkRenderer() );
}
public function doesWrites() {
@@ -61,7 +72,7 @@ class SpecialManageGroups extends SpecialPage {
$this->setHeaders();
$out = $this->getOutput();
- $out->addModuleStyles( 'ext.translate.special.managegroups.styles' );
+ $out->addModuleStyles( 'ext.translate.specialpages.styles' );
$out->addModules( 'ext.translate.special.managegroups' );
$out->addHelpLink( 'Help:Extension:Translate/Group_management' );
@@ -69,6 +80,23 @@ class SpecialManageGroups extends SpecialPage {
$this->cdb = MessageChangeStorage::getCdbPath( $name );
if ( !MessageChangeStorage::isValidCdbName( $name ) || !file_exists( $this->cdb ) ) {
+ if ( $this->getConfig()->get( 'TranslateGroupSynchronizationCache' ) ) {
+ $out->addHTML(
+ $this->displayGroupSyncInfo->getGroupsInSyncHtml(
+ $this->synchronizationCache->getGroupsInSync(),
+ self::GROUP_SYNC_INFO_WRAPPER_CLASS
+ )
+ );
+
+ $out->addHTML(
+ $this->displayGroupSyncInfo->getHtmlForGroupsWithError(
+ $this->synchronizationCache,
+ self::GROUP_SYNC_INFO_WRAPPER_CLASS,
+ $this->getLanguage()
+ )
+ );
+ }
+
// @todo Tell them when changes was last checked/process
// or how to initiate recheck.
$out->addWikiMsg( 'translate-smg-nochanges' );
@@ -98,7 +126,7 @@ class SpecialManageGroups extends SpecialPage {
* How many changes can be shown per page.
* @return int
*/
- protected function getLimit() {
+ protected function getLimit(): int {
$limits = [
1000, // Default max
ini_get( 'max_input_vars' ),
@@ -107,10 +135,10 @@ class SpecialManageGroups extends SpecialPage {
];
// Ignore things not set
$limits = array_filter( $limits );
- return min( $limits );
+ return (int)min( $limits );
}
- protected function getLegend() {
+ protected function getLegend(): string {
$text = $this->diff->addHeader(
'',
$this->msg( 'translate-smg-left' )->escaped(),
@@ -120,9 +148,7 @@ class SpecialManageGroups extends SpecialPage {
return Html::rawElement( 'div', [ 'class' => 'mw-translate-smg-header' ], $text );
}
- protected function showChanges( $limit ) {
- $contLang = MediaWikiServices::getInstance()->getContentLanguage();
-
+ protected function showChanges( int $limit ): void {
$diff = new DifferenceEngine( $this->getContext() );
$diff->showDiffStyle();
$diff->setReducedLineNumbers();
@@ -142,7 +168,24 @@ class SpecialManageGroups extends SpecialPage {
);
// The above count as three
- $limit = $limit - 3;
+ $limit -= 3;
+
+ if ( $this->getConfig()->get( 'TranslateGroupSynchronizationCache' ) ) {
+ $out->addHTML(
+ $this->displayGroupSyncInfo->getGroupsInSyncHtml(
+ $this->synchronizationCache->getGroupsInSync(),
+ self::GROUP_SYNC_INFO_WRAPPER_CLASS
+ )
+ );
+
+ $out->addHTML(
+ $this->displayGroupSyncInfo->getHtmlForGroupsWithError(
+ $this->synchronizationCache,
+ self::GROUP_SYNC_INFO_WRAPPER_CLASS,
+ $this->getLanguage()
+ )
+ );
+ }
$reader = \Cdb\Reader::open( $this->cdb );
$groups = $this->getGroupsFromCdb( $reader );
@@ -155,7 +198,7 @@ class SpecialManageGroups extends SpecialPage {
// Reduce page existance queries to one per group
$lb = new LinkBatch();
$ns = $group->getNamespace();
- $isCap = MWNamespace::isCapitalized( $ns );
+ $isCap = $this->nsInfo->isCapitalized( $ns );
$languages = $sourceChanges->getLanguages();
foreach ( $languages as $language ) {
@@ -165,7 +208,7 @@ class SpecialManageGroups extends SpecialPage {
// Constructing title objects is way slower
$key = $params['key'];
if ( $isCap ) {
- $key = $contLang->ucfirst( $key );
+ $key = $this->contLang->ucfirst( $key );
}
$lb->add( $ns, "$key/$language" );
}
@@ -182,7 +225,7 @@ class SpecialManageGroups extends SpecialPage {
foreach ( $changes as $type => $messages ) {
foreach ( $messages as $params ) {
- $change = $this->formatChange( $group, $language, $type, $params, $limit );
+ $change = $this->formatChange( $group, $sourceChanges, $language, $type, $params, $limit );
$out->addHTML( $change );
if ( $limit <= 0 ) {
@@ -199,28 +242,31 @@ class SpecialManageGroups extends SpecialPage {
}
}
- $attribs = [ 'type' => 'submit', 'class' => 'mw-translate-smg-submit' ];
- if ( !$this->hasRight ) {
- $attribs['disabled'] = 'disabled';
- $attribs['title'] = $this->msg( 'translate-smg-notallowed' )->text();
- }
- $button = Html::element( 'button', $attribs, $this->msg( 'translate-smg-submit' )->text() );
+ $out->enableOOUI();
+ $button = new ButtonInputWidget( [
+ 'type' => 'submit',
+ 'label' => $this->msg( 'translate-smg-submit' )->plain(),
+ 'disabled' => !$this->hasRight ? 'disabled' : null,
+ 'classes' => [ 'mw-translate-smg-submit' ],
+ 'title' => !$this->hasRight ? $this->msg( 'translate-smg-notallowed' )->plain() : null,
+ 'flags' => [ 'primary', 'progressive' ],
+ ] );
$out->addHTML( $button );
$out->addHTML( Html::closeElement( 'form' ) );
}
- /**
- * @param MessageGroup $group
- * @param string $language
- * @param string $type
- * @param array $params
- * @param int &$limit
- * @return string HTML
- */
- protected function formatChange( MessageGroup $group, $language, $type, $params, &$limit ) {
+ protected function formatChange(
+ MessageGroup $group,
+ MessageSourceChange $changes,
+ string $language,
+ string $type,
+ array $params,
+ int &$limit
+ ): string {
$key = $params['key'];
$title = Title::makeTitleSafe( $group->getNamespace(), "$key/$language" );
$id = self::changeId( $group->getId(), $language, $type, $key );
+ $noticeHtml = '';
if ( $title && $type === 'addition' && $title->exists() ) {
// The message has for some reason dropped out from cache
@@ -231,6 +277,7 @@ class SpecialManageGroups extends SpecialPage {
// forever and will prevent rebuilding the cache, which
// leads to many other annoying problems.
$type = 'change';
+ $noticeHtml .= Html::warningBox( $this->msg( 'translate-manage-key-reused' )->text() );
} elseif ( $title && ( $type === 'deletion' || $type === 'change' ) && !$title->exists() ) {
// This happens if a message key has been renamed
// The change can be ignored.
@@ -242,30 +289,60 @@ class SpecialManageGroups extends SpecialPage {
if ( $type === 'deletion' ) {
$wiki = ContentHandler::getContentText(
- MediaWikiServices::getInstance()
- ->getRevisionLookup()
+ $this->revLookup
->getRevisionByTitle( $title )
->getContent( SlotRecord::MAIN )
);
+
+ if ( $wiki === '' ) {
+ $noticeHtml .= Html::warningBox(
+ $this->msg( 'translate-manage-empty-content' )->text()
+ );
+ }
+
$oldContent = ContentHandler::makeContent( $wiki, $title );
$newContent = ContentHandler::makeContent( '', $title );
-
$this->diff->setContent( $oldContent, $newContent );
-
- $text = $this->diff->getDiff( $titleLink, '' );
+ $text = $this->diff->getDiff( $titleLink, '', $noticeHtml );
} elseif ( $type === 'addition' ) {
+ $menu = '';
+ $sourceLanguage = $group->getSourceLanguage();
+ if ( $sourceLanguage === $language ) {
+ if ( $this->hasRight ) {
+ $menu = Html::rawElement(
+ 'button',
+ [
+ 'class' => 'smg-rename-actions',
+ 'type' => 'button',
+ 'data-group-id' => $group->getId(),
+ 'data-lang' => $language,
+ 'data-msgkey' => $key,
+ 'data-msgtitle' => $title->getFullText()
+ ],
+ ''
+ );
+ }
+ } elseif ( !self::isMessageDefinitionPresent( $group, $changes, $key ) ) {
+ $noticeHtml .= Html::warningBox(
+ $this->msg( 'translate-manage-source-message-not-found' )->text(),
+ 'mw-translate-smg-notice-important'
+ );
+
+ // Automatically ignore messages that don't have a definitions
+ $menu = Html::hidden( "msg/$id", 'ignore', [ 'id' => "i/$id" ] );
+ $limit--;
+ }
+
+ if ( $params['content'] === '' ) {
+ $noticeHtml .= Html::warningBox(
+ $this->msg( 'translate-manage-empty-content' )->text()
+ );
+ }
+
$oldContent = ContentHandler::makeContent( '', $title );
$newContent = ContentHandler::makeContent( $params['content'], $title );
-
$this->diff->setContent( $oldContent, $newContent );
- $menu = '';
- if ( $group->getSourceLanguage() === $language && $this->hasRight ) {
- $menu = Html::rawElement( 'button', [
- 'class' => 'smg-rename-actions', 'type' => 'button',
- 'data-group-id' => $group->getId(), 'data-lang' => $language, 'data-msgkey' => $key,
- 'data-msgtitle' => $title->getFullText() ], '' );
- }
- $text = $this->diff->getDiff( '', $titleLink . $menu );
+ $text = $this->diff->getDiff( '', $titleLink . $menu, $noticeHtml );
} elseif ( $type === 'change' ) {
$wiki = TranslateUtils::getContentForTitle( $title, true );
@@ -288,7 +365,7 @@ class SpecialManageGroups extends SpecialPage {
$newContent = ContentHandler::makeContent( $params['content'], $title );
$this->diff->setContent( $oldContent, $newContent );
- $text .= $this->diff->getDiff( $titleLink, $actions );
+ $text .= $this->diff->getDiff( $titleLink, $actions, $noticeHtml );
}
$hidden = Html::hidden( $id, 1 );
@@ -304,7 +381,7 @@ class SpecialManageGroups extends SpecialPage {
return Html::rawElement( 'div', [ 'class' => $classes ], $text );
}
- protected function processSubmit() {
+ protected function processSubmit(): void {
$req = $this->getRequest();
$out = $this->getOutput();
$errorGroups = [];
@@ -331,20 +408,33 @@ class SpecialManageGroups extends SpecialPage {
$languages = $sourceChanges->getLanguages();
foreach ( $languages as $language ) {
// Handle changes, additions, deletions
- $this->handleModificationsSubmit( $group, $sourceChanges, $req,
- $language, $postponed, $groupModificationJobs );
+ $this->handleModificationsSubmit(
+ $group,
+ $sourceChanges,
+ $req,
+ $language,
+ $postponed,
+ $groupModificationJobs
+ );
// Handle renames, this might also add modification jobs based on user selection.
- $this->handleRenameSubmit( $group, $sourceChanges, $req, $language,
- $postponed, $groupRenameJobData, $groupModificationJobs );
+ $this->handleRenameSubmit(
+ $group,
+ $sourceChanges,
+ $req,
+ $language,
+ $postponed,
+ $groupRenameJobData,
+ $groupModificationJobs
+ );
if ( !isset( $postponed[$groupId][$language] ) ) {
$group->getMessageGroupCache( $language )->create();
}
}
- $modificationJobs = array_merge( $modificationJobs, $groupModificationJobs );
- $renameJobData = array_merge( $renameJobData, $groupRenameJobData );
+ $modificationJobs[$groupId] = $groupModificationJobs;
+ $renameJobData[$groupId] = $groupRenameJobData;
} catch ( Exception $e ) {
error_log(
"SpecialManageGroups: Error in processSubmit. Group: $groupId\n" .
@@ -355,9 +445,8 @@ class SpecialManageGroups extends SpecialPage {
}
}
- JobQueueGroup::singleton()->push( MessageIndexRebuildJob::newJob() );
- JobQueueGroup::singleton()->push( $modificationJobs );
- JobQueueGroup::singleton()->push( $this->createRenameJobs( $renameJobData ) );
+ $renameJobs = $this->createRenameJobs( $renameJobData );
+ $this->startSync( $modificationJobs, $renameJobs );
$reader->close();
rename( $this->cdb, $this->cdb . '-' . wfTimestamp() );
@@ -384,7 +473,12 @@ class SpecialManageGroups extends SpecialPage {
}
}
- protected static function changeId( $groupId, $language, $type, $key ) {
+ protected static function changeId(
+ string $groupId,
+ string $language,
+ string $type,
+ string $key
+ ): string {
return 'smg/' . substr( sha1( "$groupId/$language/$type/$key" ), 0, 7 );
}
@@ -394,9 +488,8 @@ class SpecialManageGroups extends SpecialPage {
* @since 2012-05-14
* @param Skin $skin
* @param array &$tabs
- * @return true
*/
- public static function tabify( Skin $skin, array &$tabs ) {
+ public static function tabify( Skin $skin, array &$tabs ): void {
$title = $skin->getTitle();
$specialPageFactory = MediaWikiServices::getInstance()->getSpecialPageFactory();
[ $alias, ] = $specialPageFactory->resolveAlias( $title->getText() );
@@ -408,7 +501,7 @@ class SpecialManageGroups extends SpecialPage {
'TranslationStats' => 'views',
];
if ( !isset( $pagesInGroup[$alias] ) ) {
- return true;
+ return;
}
$skin->getOutput()->addModuleStyles( 'ext.translate.tabgroup' );
@@ -429,21 +522,40 @@ class SpecialManageGroups extends SpecialPage {
'class' => $alias === $spName ? 'selected' : '',
];
}
-
- return true;
}
/**
- * Displays renames
+ * Check if the message definition is present as an incoming addition
+ * OR exists already on the wiki
+ *
+ * @internal - For internal use only
* @param MessageGroup $group
- * @param MessageSourceChange $sourceChanges
- * @param OutputPage $out
- * @param string $language
- * @param int &$limit
+ * @param MessageSourceChange $changes
+ * @param string $msgKey
+ * @return bool
*/
- protected function showRenames(
- MessageGroup $group, MessageSourceChange $sourceChanges, OutputPage $out, $language, &$limit
- ) {
+ private static function isMessageDefinitionPresent(
+ MessageGroup $group,
+ MessageSourceChange $changes,
+ string $msgKey
+ ): bool {
+ $sourceLanguage = $group->getSourceLanguage();
+ if ( $changes->findMessage( $sourceLanguage, $msgKey, [ MessageSourceChange::ADDITION ] ) ) {
+ return true;
+ }
+
+ $namespace = $group->getNamespace();
+ $sourceHandle = new MessageHandle( Title::makeTitle( $namespace, $msgKey ) );
+ return $sourceHandle->isValid();
+ }
+
+ private function showRenames(
+ MessageGroup $group,
+ MessageSourceChange $sourceChanges,
+ OutputPage $out,
+ string $language,
+ int &$limit
+ ): void {
$changes = $sourceChanges->getRenames( $language );
foreach ( $changes as $key => $params ) {
if ( !isset( $changes[$key] ) ) {
@@ -462,9 +574,13 @@ class SpecialManageGroups extends SpecialPage {
$secondKey = $sourceChanges->getMatchedKey( $language, $key );
$secondMsg = $sourceChanges->getMatchedMessage( $language, $key );
- if ( $sourceChanges->isPreviousState(
- $language, $key, [ MessageSourceChange::ADDITION, MessageSourceChange::CHANGE ]
- ) ) {
+ if (
+ $sourceChanges->isPreviousState(
+ $language,
+ $key,
+ [ MessageSourceChange::ADDITION, MessageSourceChange::CHANGE ]
+ )
+ ) {
$addedMsg = $firstMsg;
$deletedMsg = $secondMsg;
} else {
@@ -494,18 +610,14 @@ class SpecialManageGroups extends SpecialPage {
}
}
- /**
- * @param MessageGroup $group
- * @param array $addedMsg
- * @param array $deletedMsg
- * @param string $language
- * @param bool $isEqual Are the renamed messages equal
- * @param int &$limit
- * @return string HTML
- */
- protected function formatRename(
- MessageGroup $group, $addedMsg, $deletedMsg, $language, $isEqual, &$limit
- ) {
+ private function formatRename(
+ MessageGroup $group,
+ array $addedMsg,
+ array $deletedMsg,
+ string $language,
+ bool $isEqual,
+ int &$limit
+ ): string {
$addedKey = $addedMsg['key'];
$deletedKey = $deletedMsg['key'];
$actions = '';
@@ -526,7 +638,7 @@ class SpecialManageGroups extends SpecialPage {
}
$label = $this->msg( 'translate-manage-action-rename' )->text();
- $actions .= Xml::radioLabel( $label, "msg/$id", "rename", "imp/$id", $renameSelected );
+ $actions .= Xml::radioLabel( $label, "msg/$id", "rename", "imp/$id", $renameSelected );
} else {
$label = $this->msg( 'translate-manage-action-import' )->text();
$actions .= Xml::radioLabel( $label, "msg/$id", "import", "imp/$id", true );
@@ -546,11 +658,16 @@ class SpecialManageGroups extends SpecialPage {
$menu = '';
if ( $group->getSourceLanguage() === $language && $this->hasRight ) {
// Only show rename and add as new option for source language.
- $menu = Html::rawElement( 'button', [
- 'class' => 'smg-rename-actions', 'type' => 'button',
- 'data-group-id' => $group->getId(), 'data-msgkey' => $addedKey,
- 'data-msgtitle' => $addedTitle->getFullText()
- ], '' );
+ $menu = Html::rawElement(
+ 'button',
+ [
+ 'class' => 'smg-rename-actions',
+ 'type' => 'button',
+ 'data-group-id' => $group->getId(),
+ 'data-msgkey' => $addedKey,
+ 'data-msgtitle' => $addedTitle->getFullText()
+ ], ''
+ );
}
$actions = Html::rawElement( 'div', [ 'class' => 'smg-change-import-options' ], $actions );
@@ -565,23 +682,21 @@ class SpecialManageGroups extends SpecialPage {
$limit--;
$text .= $hidden;
- return Html::rawElement( 'div',
- [ 'class' => 'mw-translate-smg-change smg-change-rename' ], $text );
+ return Html::rawElement(
+ 'div',
+ [ 'class' => 'mw-translate-smg-change smg-change-rename' ],
+ $text
+ );
}
- /**
- * @param array $currentMsg
- * @param MessageSourceChange $sourceChanges
- * @param string $languageCode
- * @param int $groupNamespace
- * @param string $selectedVal
- * @param bool $isSourceLang
- * @return ?array
- */
- protected function getRenameJobParams(
- $currentMsg, MessageSourceChange $sourceChanges, $languageCode,
- $groupNamespace, $selectedVal, $isSourceLang = true
- ) {
+ private function getRenameJobParams(
+ array $currentMsg,
+ MessageSourceChange $sourceChanges,
+ string $languageCode,
+ int $groupNamespace,
+ string $selectedVal,
+ bool $isSourceLang = true
+ ): ?array {
if ( $selectedVal === 'ignore' ) {
return null;
}
@@ -592,9 +707,13 @@ class SpecialManageGroups extends SpecialPage {
$matchedMsg = $sourceChanges->getMatchedMessage( $languageCode, $currentMsgKey );
$matchedMsgKey = $matchedMsg['key'];
- if ( $sourceChanges->isPreviousState( $languageCode, $currentMsgKey, [
- MessageSourceChange::ADDITION, MessageSourceChange::CHANGE
- ] ) ) {
+ if (
+ $sourceChanges->isPreviousState(
+ $languageCode,
+ $currentMsgKey,
+ [ MessageSourceChange::ADDITION, MessageSourceChange::CHANGE ]
+ )
+ ) {
$params['target'] = $matchedMsgKey;
$params['replacement'] = $currentMsgKey;
$replacementContent = $currentMsg['content'];
@@ -604,11 +723,7 @@ class SpecialManageGroups extends SpecialPage {
$replacementContent = $matchedMsg['content'];
}
- if ( $selectedVal === 'renamefuzzy' ) {
- $params['fuzzy'] = 'fuzzy';
- } else {
- $params['fuzzy'] = false;
- }
+ $params['fuzzy'] = $selectedVal === 'renamefuzzy';
$params['content'] = $replacementContent;
@@ -623,9 +738,15 @@ class SpecialManageGroups extends SpecialPage {
return $params;
}
- protected function handleRenameSubmit( MessageGroup $group, MessageSourceChange $sourceChanges,
- WebRequest $req, $language, &$postponed, &$jobData, &$modificationJobs
- ) {
+ private function handleRenameSubmit(
+ MessageGroup $group,
+ MessageSourceChange $sourceChanges,
+ WebRequest $req,
+ string $language,
+ array &$postponed,
+ array &$jobData,
+ array &$modificationJobs
+ ): void {
$groupId = $group->getId();
$renames = $sourceChanges->getRenames( $language );
$isSourceLang = $group->getSourceLanguage() === $language;
@@ -639,7 +760,13 @@ class SpecialManageGroups extends SpecialPage {
$id = self::changeId( $groupId, $language, MessageSourceChange::RENAME, $key );
[ $renameMissing, $isCurrentKeyPresent ] = $this->isRenameMissing(
- $req, $sourceChanges, $id, $key, $language, $groupId, $isSourceLang
+ $req,
+ $sourceChanges,
+ $id,
+ $key,
+ $language,
+ $groupId,
+ $isSourceLang
);
if ( $renameMissing ) {
@@ -656,7 +783,12 @@ class SpecialManageGroups extends SpecialPage {
$selectedVal = $req->getVal( "msg/$id" );
$jobParams = $this->getRenameJobParams(
- $params, $sourceChanges, $language, $groupNamespace, $selectedVal, $isSourceLang
+ $params,
+ $sourceChanges,
+ $language,
+ $groupNamespace,
+ $selectedVal,
+ $isSourceLang
);
if ( $jobParams === null ) {
@@ -687,10 +819,14 @@ class SpecialManageGroups extends SpecialPage {
}
}
- protected function handleModificationsSubmit(
- MessageGroup $group, MessageSourceChange $sourceChanges, WebRequest $req,
- $language, &$postponed, &$messageUpdateJob
- ) {
+ private function handleModificationsSubmit(
+ MessageGroup $group,
+ MessageSourceChange $sourceChanges,
+ WebRequest $req,
+ string $language,
+ array &$postponed,
+ array &$messageUpdateJob
+ ): void {
$groupId = $group->getId();
$subchanges = $sourceChanges->getModificationsForLanguage( $language );
@@ -719,20 +855,30 @@ class SpecialManageGroups extends SpecialPage {
continue;
}
- $fuzzy = $selectedVal === 'fuzzy' ? 'fuzzy' : false;
+ $fuzzy = $selectedVal === 'fuzzy';
$messageUpdateJob[] = MessageUpdateJob::newJob( $title, $params['content'], $fuzzy );
}
}
}
- protected function createRenameJobs( $jobParams ) {
+ /**
+ * @param array $jobParams
+ * @return MessageUpdateJob[][]
+ */
+ private function createRenameJobs( array $jobParams ): array {
$jobs = [];
- foreach ( $jobParams as $params ) {
- $jobs[] = MessageUpdateJob::newRenameJob(
- $params['targetTitle'], $params['target'],
- $params['replacement'], $params['fuzzy'], $params['content'],
- $params['others']
- );
+ foreach ( $jobParams as $groupId => $groupJobParams ) {
+ $jobs[$groupId] = $jobs[$groupId] ?? [];
+ foreach ( $groupJobParams as $params ) {
+ $jobs[$groupId][] = MessageUpdateJob::newRenameJob(
+ $params['targetTitle'],
+ $params['target'],
+ $params['replacement'],
+ $params['fuzzy'],
+ $params['content'],
+ $params['others']
+ );
+ }
}
return $jobs;
@@ -740,13 +886,14 @@ class SpecialManageGroups extends SpecialPage {
/**
* Checks if a title still exists and can be processed.
- *
* @param Title $title
* @param string $type
* @return bool
*/
- protected function isTitlePresent( Title $title, $type ) {
- if ( ( $type === MessageSourceChange::DELETION || $type === MessageSourceChange::CHANGE ) &&
+ private function isTitlePresent( Title $title, string $type ): bool {
+ // phpcs:ignore SlevomatCodingStandard.ControlStructures.UselessIfConditionWithReturn
+ if (
+ ( $type === MessageSourceChange::DELETION || $type === MessageSourceChange::CHANGE ) &&
!$title->exists()
) {
// This means that this change was probably introduced due to a rename
@@ -774,10 +921,15 @@ class SpecialManageGroups extends SpecialPage {
* 1 => (bool) Was the current $id found?
* ]
*/
- protected function isRenameMissing(
- WebRequest $req, MessageSourceChange $sourceChanges, $id, $key,
- $language, $groupId, $isSourceLang
- ) {
+ private function isRenameMissing(
+ WebRequest $req,
+ MessageSourceChange $sourceChanges,
+ string $id,
+ string $key,
+ string $language,
+ string $groupId,
+ bool $isSourceLang
+ ): array {
if ( $req->getCheck( $id ) ) {
return [ false, true ];
}
@@ -800,9 +952,7 @@ class SpecialManageGroups extends SpecialPage {
return [ true, $isCurrentKeyPresent ];
}
- protected function getProcessingErrorMessage(
- array $errorGroups, int $totalGroupCount
- ): string {
+ private function getProcessingErrorMessage( array $errorGroups, int $totalGroupCount ): string {
// Number of error groups, are less than the total groups processed.
if ( count( $errorGroups ) < $totalGroupCount ) {
$errorMsg = $this->msg( 'translate-smg-submitted-with-failure' )
@@ -835,4 +985,63 @@ class SpecialManageGroups extends SpecialPage {
}
return array_filter( $groups );
}
+
+ /**
+ * Add jobs to the queue, updates the interim cache, and start sync process for the group.
+ * @param MessageUpdateJob[][] $modificationJobs
+ * @param MessageUpdateJob[][] $renameJobs
+ * @return void
+ */
+ private function startSync( array $modificationJobs, array $renameJobs ): void {
+ // We are adding an empty array for groups that have no jobs. This is mainly done to
+ // avoid adding unnecessary checks. Remove those using array_filter
+ $modificationGroupIds = array_keys( array_filter( $modificationJobs ) );
+ $renameGroupIds = array_keys( array_filter( $renameJobs ) );
+ $uniqueGroupIds = array_unique( array_merge( $modificationGroupIds, $renameGroupIds ) );
+ $messageIndexInstance = MessageIndex::singleton();
+ $jobQueueInstance = JobQueueGroup::singleton();
+
+ foreach ( $uniqueGroupIds as $groupId ) {
+ $messages = [];
+ $messageKeys = [];
+ $groupJobs = [];
+
+ $groupRenameJobs = $renameJobs[$groupId] ?? [];
+ /** @var MessageUpdateJob $job */
+ foreach ( $groupRenameJobs as $job ) {
+ $groupJobs[] = $job;
+ $messageUpdateParam = MessageUpdateParameter::createFromJob( $job );
+ $messages[] = $messageUpdateParam;
+
+ // Build the handle to add the message key in interim cache
+ $replacement = $messageUpdateParam->getReplacementValue();
+ $targetTitle = Title::makeTitle( $job->getTitle()->getNamespace(), $replacement );
+ $messageKeys[] = ( new MessageHandle( $targetTitle ) )->getKey();
+ }
+
+ $groupModificationJobs = $modificationJobs[$groupId] ?? [];
+ /** @var MessageUpdateJob $job */
+ foreach ( $groupModificationJobs as $job ) {
+ $groupJobs[] = $job;
+ $messageUpdateParam = MessageUpdateParameter::createFromJob( $job );
+ $messages[] = $messageUpdateParam;
+
+ $messageKeys[] = ( new MessageHandle( $job->getTitle() ) )->getKey();
+ }
+
+ // Store all message keys in the interim cache - we're particularly interested in new
+ // and renamed messages, but it's cleaner to just store everything.
+ $group = MessageGroups::getGroup( $groupId );
+ $messageIndexInstance->storeInterim( $group, $messageKeys );
+
+ if ( $this->getConfig()->get( 'TranslateGroupSynchronizationCache' ) ) {
+ $this->synchronizationCache->addMessages( $groupId, ...$messages );
+ $this->synchronizationCache->markGroupForSync( $groupId );
+ }
+
+ $jobQueueInstance->push( $groupJobs );
+ }
+
+ $jobQueueInstance->push( MessageIndexRebuildJob::newJob() );
+ }
}
diff --git a/MLEB/Translate/specials/SpecialManageTranslatorSandbox.php b/MLEB/Translate/specials/SpecialManageTranslatorSandbox.php
deleted file mode 100644
index b619d975..00000000
--- a/MLEB/Translate/specials/SpecialManageTranslatorSandbox.php
+++ /dev/null
@@ -1,335 +0,0 @@
-<?php
-/**
- * Contains logic for Special:ManageTranslatorSandbox
- *
- * @file
- * @author Niklas Laxström
- * @author Amir E. Aharoni
- * @license GPL-2.0-or-later
- */
-
-/**
- * Special page for managing sandboxed users.
- *
- * @ingroup SpecialPage TranslateSpecialPage
- */
-class SpecialManageTranslatorSandbox extends SpecialPage {
- /** @var TranslationStashStorage */
- protected $stash;
-
- public function __construct() {
- global $wgTranslateUseSandbox;
- parent::__construct(
- 'ManageTranslatorSandbox',
- 'translate-sandboxmanage',
- $wgTranslateUseSandbox
- );
- }
-
- public function doesWrites() {
- return true;
- }
-
- protected function getGroupName() {
- return 'translation';
- }
-
- public function execute( $params ) {
- $this->setHeaders();
- $this->checkPermissions();
- $out = $this->getOutput();
- $out->addModuleStyles( [
- 'ext.translate.special.managetranslatorsandbox.styles',
- 'mediawiki.ui.button',
- 'jquery.uls.grid'
- ] );
- $out->addModules( 'ext.translate.special.managetranslatorsandbox' );
- $this->stash = new TranslationStashStorage( wfGetDB( DB_MASTER ) );
-
- $this->prepareForTests();
- $this->showPage();
- }
-
- /**
- * Deletes a user page if it exists.
- * This is needed especially when deleting sandbox users
- * that were created as part of the integration tests.
- * @param User $user
- */
- protected function deleteUserPage( $user ) {
- $userpage = WikiPage::factory( $user->getUserPage() );
- if ( $userpage->exists() ) {
- $reason = wfMessage( 'tsb-delete-userpage-summary' )->inContentLanguage()->text();
- if ( version_compare( MW_VERSION, '1.35', '<' ) ) {
- $dummyError = '';
- $userpage->doDeleteArticleReal(
- $reason,
- false,
- 0,
- true,
- $dummyError,
- $this->getUser()
- );
- } else {
- $userpage->doDeleteArticleReal( $reason, $this->getUser() );
- }
- }
- }
-
- /**
- * Add users to the sandbox or delete them to facilitate browsers tests.
- * Use with caution!
- */
- public function prepareForTests() {
- $request = $this->getRequest();
-
- if ( $request->getVal( 'integrationtesting' ) === 'populate' ) {
- // Empty all the users, even if they were created manually
- // to ensure the number of users is what the tests expect
- $this->emptySandbox();
-
- $textUsernamePrefixes = [ 'Pupu', 'Orava' ];
- $testLanguages = [ 'fi', 'uk', 'nl', 'he', 'bn' ];
- $testLanguagesCount = count( $testLanguages );
-
- foreach ( $textUsernamePrefixes as $prefix ) {
- for ( $i = 0; $i < $testLanguagesCount; $i++ ) {
- $name = "$prefix$i";
-
- // Get rid of users, even if promoted during tests
- $userToDelete = User::newFromName( $name, false );
- $this->deleteUserPage( $userToDelete );
- TranslateSandbox::deleteUser( $userToDelete, 'force' );
-
- $user = TranslateSandbox::addUser( $name, "$name@blackhole.io", 'porkkana' );
- $user->setOption(
- 'translate-sandbox',
- FormatJson::encode( [
- 'languages' => [ $testLanguages[$i] ],
- 'comment' => '',
- ] )
- );
-
- $reminders = [];
- // @phan-suppress-next-line PhanSuspiciousValueComparison
- for ( $reminderIndex = 0; $reminderIndex < $i; $reminderIndex++ ) {
- $reminders[] = wfTimestamp() - $reminderIndex * $i * 10000;
- }
-
- $user->setOption(
- 'translate-sandbox-reminders',
- implode( '|', $reminders )
- );
- $user->saveSettings();
-
- // @phan-suppress-next-line PhanSuspiciousValueComparison
- for ( $j = 0; $j < $i; $j++ ) {
- $title = Title::makeTitle(
- NS_MEDIAWIKI,
- wfRandomString( 24 ) . '/' . $testLanguages[$i]
- );
- $translation = 'plop';
- $stashedTranslation = new StashedTranslation( $user, $title, $translation );
- $this->stash->addTranslation( $stashedTranslation );
- }
- }
- }
-
- // Another account for testing a translator to multiple languages
- $oldPolyglotUser = User::newFromName( 'Kissa', false );
- $this->deleteUserPage( $oldPolyglotUser );
- TranslateSandbox::deleteUser( $oldPolyglotUser, 'force' );
-
- $polyglotUser = TranslateSandbox::addUser( 'Kissa', 'kissa@blackhole.io', 'porkkana' );
- $polyglotUser->setOption(
- 'translate-sandbox',
- FormatJson::encode( [
- 'languages' => $testLanguages,
- 'comment' => "I know some languages, and I'm a developer.",
- ] )
- );
- $polyglotUser->saveSettings();
- for ( $polyglotLang = 0; $polyglotLang < $testLanguagesCount; $polyglotLang++ ) {
- $title = Title::makeTitle(
- NS_MEDIAWIKI,
- wfRandomString( 24 ) . '/' . $testLanguages[$polyglotLang]
- );
- $translation = "plop in $testLanguages[$polyglotLang]";
- $stashedTranslation = new StashedTranslation( $polyglotUser, $title, $translation );
- $this->stash->addTranslation( $stashedTranslation );
- }
- } elseif ( $request->getVal( 'integrationtesting' ) === 'empty' ) {
- $this->emptySandbox();
- }
- }
-
- /**
- * Delete all the users in the sandbox.
- * Use with caution!
- * To facilitate browsers tests.
- */
- protected function emptySandbox() {
- $users = TranslateSandbox::getUsers();
- foreach ( $users as $user ) {
- TranslateSandbox::deleteUser( $user );
- }
- }
-
- /**
- * Generates the whole page html and appends it to output
- */
- protected function showPage() {
- $out = $this->getOutput();
-
- $nojs = Html::element(
- 'div',
- [ 'class' => 'tux-nojs errorbox' ],
- $this->msg( 'tux-nojs' )->plain()
- );
- $out->addHTML( $nojs );
-
- $out->addHTML( <<<HTML
-<div class="grid tsb-container">
- <div class="row">
- <div class="nine columns pane filter">{$this->makeFilter()}</div>
- <div class="three columns pane search">{$this->makeSearchBox()}</div>
- </div>
- <div class="row tsb-body">
- <div class="four columns pane requests">
- {$this->makeList()}
- <div class="request-footer">
- <span class="selected-counter">
- {$this->msg( 'tsb-selected-count' )->numParams( 0 )->escaped()}
- </span>
- &nbsp;
- <a href="#" class="older-requests-indicator"></a>
- </div>
- </div>
- <div class="eight columns pane details"></div>
- </div>
-</div>
-HTML
- );
- }
-
- protected function makeFilter() {
- return $this->msg( 'tsb-filter-pending' )->escaped();
- }
-
- protected function makeSearchBox() {
- return <<<HTML
-<input class="request-filter-box right"
- placeholder="{$this->msg( 'tsb-search-requests' )->escaped()}" type="search">
-</input>
-HTML;
- }
-
- protected function makeList() {
- $items = [];
- $requests = [];
- $users = TranslateSandbox::getUsers();
-
- /** @var User $user */
- foreach ( $users as $user ) {
- $reminders = $user->getOption( 'translate-sandbox-reminders' );
- $reminders = $reminders ? explode( '|', $reminders ) : [];
- $remindersCount = count( $reminders );
- if ( $remindersCount ) {
- $lastReminderTimestamp = new MWTimestamp( end( $reminders ) );
- $lastReminderAgo = htmlspecialchars(
- $lastReminderTimestamp->getHumanTimestamp()
- );
- } else {
- $lastReminderAgo = '';
- }
-
- $requests[] = [
- 'username' => $user->getName(),
- 'email' => $user->getEmail(),
- 'gender' => $user->getOption( 'gender' ),
- 'registrationdate' => $user->getRegistration(),
- 'translations' => count( $this->stash->getTranslations( $user ) ),
- 'languagepreferences' => FormatJson::decode( $user->getOption( 'translate-sandbox' ) ),
- 'userid' => $user->getId(),
- 'reminderscount' => $remindersCount,
- 'lastreminder' => $lastReminderAgo,
- ];
- }
-
- // Sort the requests based on translations and registration date
- usort( $requests, [ __CLASS__, 'translatorRequestSort' ] );
-
- foreach ( $requests as $request ) {
- // @phan-suppress-next-line SecurityCheck-DoubleEscaped
- $items[] = $this->makeRequestItem( $request );
- }
-
- $requestsList = implode( "\n", $items );
-
- return <<<HTML
-<div class="row request-header">
- <div class="four columns">
- <button class="language-selector unselected">
- {$this->msg( 'tsb-all-languages-button-label' )->escaped()}
- </button>
- </div>
- <div class="five columns request-count"></div>
- <div class="three columns center">
- <input class="request-selector-all" name="request" type="checkbox" />
- </div>
-</div>
-<div class="requests-list">
- {$requestsList}
-</div>
-HTML;
- }
-
- protected function makeRequestItem( $request ) {
- $requestdataEnc = htmlspecialchars( FormatJson::encode( $request ) );
- $nameEnc = htmlspecialchars( $request['username'] );
- $nameEncForId = htmlspecialchars( Sanitizer::escapeIdForAttribute( 'tsb-request-' . $request['username'] ) );
- $emailEnc = htmlspecialchars( $request['email'] );
- $countEnc = htmlspecialchars( $request['translations'] );
- $timestamp = new MWTimestamp( $request['registrationdate'] );
- $agoEnc = htmlspecialchars( $timestamp->getHumanTimestamp() );
-
- return <<<HTML
-<div class="row request" data-data="$requestdataEnc" id="$nameEncForId">
- <div class="two columns amount">
- <div class="translation-count">$countEnc</div>
- </div>
- <div class="seven columns request-info">
- <div class="row username">$nameEnc</div>
- <div class="row email" dir="ltr">$emailEnc</div>
- </div>
- <div class="three columns approval center">
- <input class="row request-selector" name="request" type="checkbox" />
- <div class="row signup-age">$agoEnc</div>
- </div>
-</div>
-HTML;
- }
-
- /**
- * Sorts groups by descending order of number of translations,
- * registration date and username
- *
- * @since 2013.12
- * @param array $a Translation request
- * @param array $b Translation request
- * @return int comparison result
- */
- public static function translatorRequestSort( $a, $b ) {
- $translationCountDiff = $b['translations'] - $a['translations'];
- if ( $translationCountDiff !== 0 ) {
- return $translationCountDiff;
- }
-
- $registrationDateDiff = $b['registrationdate'] - $a['registrationdate'];
- if ( $registrationDateDiff !== 0 ) {
- return $registrationDateDiff;
- }
-
- return strcmp( $a['username'], $b['username'] );
- }
-}
diff --git a/MLEB/Translate/specials/SpecialMessageGroupStats.php b/MLEB/Translate/specials/SpecialMessageGroupStats.php
index 443d271c..338f70c0 100644
--- a/MLEB/Translate/specials/SpecialMessageGroupStats.php
+++ b/MLEB/Translate/specials/SpecialMessageGroupStats.php
@@ -21,11 +21,8 @@ class SpecialMessageGroupStats extends SpecialLanguageStats {
protected $noComplete = false;
/// Overwritten from SpecialLanguageStats
protected $noEmpty = true;
-
protected $names;
-
protected $translate;
-
/** @var int */
private $numberOfShownLanguages;
@@ -149,7 +146,7 @@ class SpecialMessageGroupStats extends SpecialLanguageStats {
sort( $languages );
$this->filterPriorityLangs( $languages, $this->target, $stats );
foreach ( $languages as $code ) {
- if ( $table->isBlacklisted( $this->target, $code ) !== null ) {
+ if ( $table->isExcluded( $this->target, $code ) !== null ) {
continue;
}
$out .= $this->makeRow( $code, $stats );
diff --git a/MLEB/Translate/specials/SpecialSearchTranslations.php b/MLEB/Translate/specials/SpecialSearchTranslations.php
index 377d95d8..dd6fcd1d 100644
--- a/MLEB/Translate/specials/SpecialSearchTranslations.php
+++ b/MLEB/Translate/specials/SpecialSearchTranslations.php
@@ -15,7 +15,6 @@
class SpecialSearchTranslations extends SpecialPage {
/** @var FormOptions */
protected $opts;
-
/**
* Placeholders used for highlighting. Search backend can mark the beginning and
* end but we need to run htmlspecialchars on the result first and then
@@ -24,7 +23,6 @@ class SpecialSearchTranslations extends SpecialPage {
* @var array
*/
protected $hl = [];
-
/**
* How many search results to display per page
* @var int
@@ -51,7 +49,7 @@ class SpecialSearchTranslations extends SpecialPage {
$out = $this->getOutput();
$out->addModuleStyles( 'jquery.uls.grid' );
- $out->addModuleStyles( 'ext.translate.special.searchtranslations.styles' );
+ $out->addModuleStyles( 'ext.translate.specialpages.styles' );
$out->addModuleStyles( 'ext.translate.special.translate.styles' );
$out->addModuleStyles( [ 'mediawiki.ui.button', 'mediawiki.ui.input', 'mediawiki.ui.checkbox' ] );
$out->addModules( 'ext.translate.special.searchtranslations' );
@@ -536,9 +534,7 @@ HTML
$container .
Html::closeElement( 'li' );
- $output .= Html::closeElement( 'ul' );
- $output .= Html::closeElement( 'div' );
- $output .= Html::closeElement( 'div' );
+ $output .= Html::closeElement( 'ul' ) . Html::closeElement( 'div' ) . Html::closeElement( 'div' );
return $output;
}
diff --git a/MLEB/Translate/specials/SpecialSupportedLanguages.php b/MLEB/Translate/specials/SpecialSupportedLanguages.php
deleted file mode 100644
index 07113217..00000000
--- a/MLEB/Translate/specials/SpecialSupportedLanguages.php
+++ /dev/null
@@ -1,350 +0,0 @@
-<?php
-/**
- * Contains logic for special page Special:SupportedLanguages
- *
- * @file
- * @author Niklas Laxström
- * @author Siebrand Mazeland
- * @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
- * a list of language portals for all portals corresponding with a language
- * code defined for MediaWiki and a subpage called "translators". The subpage
- * "translators" must contain the template [[:{{ns:template}}:User|User]],
- * taking a user name as parameter.
- *
- * @ingroup SpecialPage TranslateSpecialPage Stats
- */
-class SpecialSupportedLanguages extends SpecialPage {
- 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 'translation';
- }
-
- public function getDescription() {
- return $this->msg( 'supportedlanguages' )->text();
- }
-
- public function execute( $par ) {
- $out = $this->getOutput();
- $lang = $this->getLanguage();
-
- $this->setHeaders();
- $out->addModules( 'ext.translate.special.supportedlanguages' );
- $out->addModuleStyles( 'ext.translate.special.supportedlanguages' );
-
- $out->addHelpLink(
- 'Help:Extension:Translate/Statistics_and_reporting#List_of_languages_and_translators'
- );
-
- $this->outputHeader( 'supportedlanguages-summary' );
- $dbr = wfGetDB( DB_REPLICA );
- if ( $dbr->getType() === 'sqlite' ) {
- $out->wrapWikiMsg(
- '<div class="errorbox">$1</div>',
- 'supportedlanguages-sqlite-error'
- );
- return;
- }
-
- $out->addWikiMsg( 'supportedlanguages-localsummary' );
-
- $names = Language::fetchLanguageNames( null, 'all' );
- $languages = $this->languageCloud();
- // There might be all sorts of subpages which are not languages
- $languages = array_intersect_key( $languages, $names );
-
- $this->outputLanguageCloud( $languages, $names );
- $out->addWikiMsg( 'supportedlanguages-count', $lang->formatNum( count( $languages ) ) );
-
- if ( !$par || !Language::isKnownLanguageTag( $par ) ) {
- return;
- }
-
- $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( string $code, array $users, int $cachedAt ): void {
- $out = $this->getOutput();
- $lang = $this->getLanguage();
-
- // Information to be used inside the foreach loop.
- $linkInfo = [];
- $linkInfo['rc']['title'] = SpecialPage::getTitleFor( 'Recentchanges' );
- $linkInfo['rc']['msg'] = $this->msg( 'supportedlanguages-recenttranslations' )->text();
- $linkInfo['stats']['title'] = SpecialPage::getTitleFor( 'LanguageStats' );
- $linkInfo['stats']['msg'] = $this->msg( 'languagestats' )->text();
-
- $local = Language::fetchLanguageName( $code, $lang->getCode(), 'all' );
- $native = Language::fetchLanguageName( $code, null, 'all' );
-
- if ( $local !== $native ) {
- $headerText = $this->msg( 'supportedlanguages-portallink' )
- ->params( $code, $local, $native )->escaped();
- } else {
- // No CLDR, so a less localised header and link title.
- $headerText = $this->msg( 'supportedlanguages-portallink-nocldr' )
- ->params( $code, $native )->escaped();
- }
-
- $out->addHTML( Html::rawElement( 'h2', [ 'id' => $code ], $headerText ) );
-
- // Add useful links for language stats and recent changes for the language.
- $links = [];
- $links[] = $this->getLinkRenderer()->makeKnownLink(
- $linkInfo['stats']['title'],
- $linkInfo['stats']['msg'],
- [],
- [
- 'code' => $code,
- 'suppresscomplete' => '1'
- ]
- );
- $links[] = $this->getLinkRenderer()->makeKnownLink(
- $linkInfo['rc']['title'],
- $linkInfo['rc']['msg'],
- [],
- [
- 'translations' => 'only',
- 'trailer' => '/' . $code
- ]
- );
- $linkList = $lang->listToText( $links );
-
- $out->addHTML( '<p>' . $linkList . "</p>\n" );
- $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() {
- $cache = wfGetCache( CACHE_ANYTHING );
- $cachekey = wfMemcKey( 'translate-supportedlanguages-language-cloud' );
-
- $data = $cache->get( $cachekey );
- if ( is_array( $data ) ) {
- return $data;
- }
-
- $dbr = wfGetDB( DB_REPLICA );
- $tables = [ 'recentchanges' ];
- $fields = [ 'substring_index(rc_title, \'/\', -1) as lang', 'count(*) as count' ];
- $timestamp = $dbr->timestamp( wfTimestamp( TS_UNIX ) - 60 * 60 * 24 * $this->period );
- $conds = [
- # Without the quotes the rc_timestamp index isn't used and this query is much slower
- "rc_timestamp > '$timestamp'",
- 'rc_namespace' => $this->options->get( 'TranslateMessageNamespaces' ),
- 'rc_title' . $dbr->buildLike( $dbr->anyString(), '/', $dbr->anyString() ),
- ];
- $options = [ 'GROUP BY' => 'lang', 'HAVING' => 'count > 20', 'ORDER BY' => 'NULL' ];
-
- $res = $dbr->select( $tables, $fields, $conds, __METHOD__, $options );
-
- $data = [];
- foreach ( $res as $row ) {
- $data[$row->lang] = $row->count;
- }
-
- $cache->set( $cachekey, $data, 3600 );
-
- return $data;
- }
-
- protected function filterUsers( array $users, string $code ): array {
- $blacklist = $this->options->get( 'TranslateAuthorBlacklist' );
-
- foreach ( array_keys( $users ) as $username ) {
- # We do not know the group
- $hash = "#;$code;$username";
-
- $blacklisted = false;
- foreach ( $blacklist as $rule ) {
- [ $type, $regex ] = $rule;
-
- if ( preg_match( $regex, $hash ) ) {
- if ( $type === 'white' ) {
- $blacklisted = false;
- break;
- } else {
- $blacklisted = true;
- }
- }
- }
-
- if ( $blacklisted ) {
- unset( $users[$username] );
- }
- }
-
- return $users;
- }
-
- protected function outputLanguageCloud( array $languages, array $names ) {
- $out = $this->getOutput();
-
- $out->addHTML( '<div class="tagcloud autonym">' );
-
- foreach ( $languages as $k => $v ) {
- $name = $names[$k];
- $size = round( log( $v ) * 20 ) + 10;
-
- $params = [
- 'href' => $this->getPageTitle( $k )->getLocalURL(),
- 'class' => 'tag',
- 'style' => "font-size:$size%",
- 'lang' => $k,
- ];
-
- $tag = Html::element( 'a', $params, $name );
- $out->addHTML( $tag . "\n" );
- }
- $out->addHTML( '</div>' );
- }
-
- protected function makeUserList( array $userStats ): void {
- $day = 60 * 60 * 24;
-
- // Scale of the activity colors, anything
- // longer than this is just inactive
- $period = $this->period;
-
- $links = [];
- $statsTable = new StatsTable();
-
- // 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 = [];
- $styles['font-size'] = round( log( $count, 10 ) * 30 ) + 70 . '%';
-
- $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 ) {
- $attribs['style'] = $stylestr;
- }
-
- $links[] = $this->getLinkRenderer()->makeLink( $title, new HtmlArmor( $enc ), $attribs );
- }
-
- // for GENDER support
- $usernameForGender = '';
- if ( count( $userStats ) === 1 ) {
- $usernameForGender = array_key_first( $userStats );
- }
-
- $linkList = $this->getLanguage()->listToText( $links );
- $html = "<p class='mw-translate-spsl-translators'>";
- $html .= $this->msg( 'supportedlanguages-translators' )
- ->rawParams( $linkList )
- ->numParams( count( $links ) )
- ->params( $usernameForGender )
- ->escaped();
- $html .= "</p>\n";
- $this->getOutput()->addHTML( $html );
- }
-
- protected function formatStyle( $styles ) {
- $stylestr = '';
- foreach ( $styles as $key => $value ) {
- $stylestr .= "$key:$value;";
- }
-
- return $stylestr;
- }
-
- protected function preQueryUsers( array $users ): void {
- $lb = new LinkBatch;
- foreach ( $users as $user => $data ) {
- $user = Title::capitalize( $user, NS_USER );
- $lb->add( NS_USER, $user );
- $lb->add( NS_USER_TALK, $user );
- }
- $lb->execute();
- }
-
- protected function getColorLegend() {
- $legend = '';
- $period = $this->period;
- $statsTable = new StatsTable();
-
- for ( $i = 0; $i <= $period; $i += 30 ) {
- $iFormatted = htmlspecialchars( $this->getLanguage()->formatNum( $i ) );
- $legend .= '<span style="background-color:#' .
- $statsTable->getBackgroundColor( ( $period - $i ) / $period ) .
- "\"> $iFormatted</span>";
- }
-
- return $legend;
- }
-}
diff --git a/MLEB/Translate/specials/SpecialTranslate.php b/MLEB/Translate/specials/SpecialTranslate.php
index d7e7d6bd..7294e6f0 100644
--- a/MLEB/Translate/specials/SpecialTranslate.php
+++ b/MLEB/Translate/specials/SpecialTranslate.php
@@ -19,7 +19,6 @@ use MediaWiki\MediaWikiServices;
class SpecialTranslate extends SpecialPage {
/** @var MessageGroup */
protected $group;
-
protected $defaults;
protected $nondefaults = [];
protected $options;
@@ -82,7 +81,7 @@ class SpecialTranslate extends SpecialPage {
$defaults = [
/* str */'language' => $this->getLanguage()->getCode(),
- /* str */'group' => '!additions',
+ /* str */'group' => '!additions',
];
// Dump everything here
@@ -122,7 +121,9 @@ class SpecialTranslate extends SpecialPage {
throw new MWException( '$r was not set' );
}
- wfAppendToArrayIfNotDefault( $v, $r, $defaults, $nondefaults );
+ if ( $defaults[$v] !== $r ) {
+ $nondefaults[$v] = $r;
+ }
}
$this->defaults = $defaults;
@@ -232,16 +233,17 @@ class SpecialTranslate extends SpecialPage {
$output .= Html::closeElement( 'ul' );
$output .= Html::closeElement( 'div' ); // close nine columns
$output .= Html::openElement( 'div', [ 'class' => 'three columns' ] );
- $output .= Html::openElement( 'div', [ 'class' => 'tux-message-filter-wrapper' ] );
- $output .= Html::element( 'input', [
- 'class' => 'tux-message-filter-box',
- 'type' => 'search',
- ] );
- $output .= Html::closeElement( 'div' ); // close tux-message-filter-wrapper
-
- $output .= Html::closeElement( 'div' ); // close three columns
+ $output .= Html::rawElement(
+ 'div',
+ [ 'class' => 'tux-message-filter-wrapper' ],
+ Html::element( 'input', [
+ 'class' => 'tux-message-filter-box',
+ 'type' => 'search',
+ ] )
+ );
- $output .= Html::closeElement( 'div' ); // close the row
+ // close three columns and the row
+ $output .= Html::closeElement( 'div' ) . Html::closeElement( 'div' );
return $output;
}
@@ -353,7 +355,7 @@ class SpecialTranslate extends SpecialPage {
list( $alias, $sub ) = MediaWikiServices::getInstance()
->getSpecialPageFactory()->resolveAlias( $title->getText() );
- $pagesInGroup = [ 'Translate', 'LanguageStats', 'MessageGroupStats' ];
+ $pagesInGroup = [ 'Translate', 'LanguageStats', 'MessageGroupStats', 'ExportTranslations' ];
if ( !in_array( $alias, $pagesInGroup, true ) ) {
return true;
}
diff --git a/MLEB/Translate/specials/SpecialTranslationStash.php b/MLEB/Translate/specials/SpecialTranslationStash.php
deleted file mode 100644
index 50a309ed..00000000
--- a/MLEB/Translate/specials/SpecialTranslationStash.php
+++ /dev/null
@@ -1,195 +0,0 @@
-<?php
-/**
- * TranslationStash - Translator screening page
- *
- * @file
- * @author Santhosh Thottingal
- * @license GPL-2.0-or-later
- */
-
-/**
- * Special page for new users to translate example messages.
- *
- * @ingroup SpecialPage TranslateSpecialPage
- */
-class SpecialTranslationStash extends SpecialPage {
- /** @var TranslationStashStorage */
- protected $stash;
-
- public function __construct() {
- parent::__construct( 'TranslationStash' );
- }
-
- public function doesWrites() {
- return true;
- }
-
- protected function getGroupName() {
- return 'translation';
- }
-
- public function execute( $params ) {
- global $wgTranslateSandboxLimit, $wgTranslateSecondaryPermissionUrl;
-
- $this->setHeaders();
- $out = $this->getOutput();
-
- $this->stash = new TranslationStashStorage( wfGetDB( DB_MASTER ) );
-
- if ( !$this->hasPermissionToUse() ) {
- if ( $wgTranslateSecondaryPermissionUrl && $this->getUser()->isLoggedIn() ) {
- $out->redirect(
- Title::newFromText( $wgTranslateSecondaryPermissionUrl )->getLocalURL()
- );
-
- return;
- }
-
- $out->redirect( Title::newMainPage()->getLocalURL() );
-
- return;
- }
-
- $out->addJsConfigVars( 'wgTranslateSandboxLimit', $wgTranslateSandboxLimit );
- $out->addModules( 'ext.translate.special.translationstash' );
- $out->addModuleStyles( 'mediawiki.ui.button' );
- $this->showPage();
- }
-
- /**
- * Checks that the user is in the sandbox. Also handles special overrides
- * mainly used for integration testing.
- *
- * @return bool
- */
- protected function hasPermissionToUse() {
- $user = $this->getUser();
-
- if ( !TranslateSandbox::isSandboxed( $user ) ) {
- return false;
- }
-
- return true;
- }
-
- /**
- * Generates the whole page html and appends it to output
- */
- protected function showPage() {
- $out = $this->getOutput();
- $user = $this->getUser();
-
- $count = count( $this->stash->getTranslations( $user ) );
- if ( $count === 0 ) {
- $progress = $this->msg( 'translate-translationstash-initialtranslation' )->parse();
- } else {
- $progress = $this->msg( 'translate-translationstash-translations' )
- ->numParams( $count )->parse();
- }
-
- $out->addHTML( <<<HTML
-<div class="grid">
- <div class="row translate-welcome-header">
- <h1>
- {$this->msg( 'translate-translationstash-welcome', $user->getName() )->parse()}
- </h1>
- <p>
- {$this->msg( 'translate-translationstash-welcome-note' )->parse()}
- </p>
- </div>
- <div class="row translate-stash-control">
- <div class="six columns stash-stats">
- {$progress}
- </div>
- <div class="six columns ext-translate-language-selector right">
- {$this->tuxLanguageSelector()}
- </div>
- </div>
- {$this->getMessageTable()}
- <div class="row limit-reached hide"></div>
-</div>
-HTML
- );
- }
-
- protected function getMessageTable() {
- $sourceLang = $this->getSourceLanguage();
- $targetLang = $this->getTargetLanguage();
-
- $list = Html::element( 'div', [
- 'class' => 'row tux-messagelist',
- 'data-sourcelangcode' => $sourceLang->getCode(),
- 'data-sourcelangdir' => $sourceLang->getDir(),
- 'data-targetlangcode' => $targetLang->getCode(),
- 'data-targetlangdir' => $targetLang->getDir(),
- ] );
-
- return $list;
- }
-
- protected function tuxLanguageSelector() {
- // The name will be displayed in the UI language,
- // so use for lang and dir
- $language = $this->getTargetLanguage();
- $targetLangName = Language::fetchLanguageName( $language->getCode() );
-
- $label = Html::element(
- 'span',
- [ 'class' => 'ext-translate-language-selector-label' ],
- $this->msg( 'tux-languageselector' )->text()
- );
-
- $trigger = Html::element(
- 'span',
- [
- 'class' => 'uls',
- 'lang' => $language->getHtmlCode(),
- 'dir' => $language->getDir(),
- ],
- $targetLangName
- );
-
- // No-break space is added for spacing after the label
- // and to ensure separation of words (in Arabic, for example)
- return "$label&#160;$trigger";
- }
-
- /**
- * Returns the source language for messages.
- * @return Language
- */
- protected function getSourceLanguage() {
- // Bad
- return Language::factory( 'en' );
- }
-
- /**
- * Returns the default target language for messages.
- * @return Language
- */
- protected function getTargetLanguage() {
- $ui = $this->getLanguage();
- $source = $this->getSourceLanguage();
- if ( !$ui->equals( $source ) ) {
- return $ui;
- }
-
- $options = FormatJson::decode( $this->getUser()->getOption( 'translate-sandbox' ), true );
- $supported = TranslateUtils::getLanguageNames( 'en' );
-
- if ( isset( $options['languages' ] ) ) {
- foreach ( $options['languages'] as $code ) {
- if ( !isset( $supported[$code] ) ) {
- continue;
- }
-
- if ( $code !== $source->getCode() ) {
- return Language::factory( $code );
- }
- }
- }
-
- // User has not chosen any valid language. Pick the source.
- return Language::factory( $source->getCode() );
- }
-}
diff --git a/MLEB/Translate/specials/SpecialTranslationStats.php b/MLEB/Translate/specials/SpecialTranslationStats.php
index 6bafaa02..abc7e085 100644
--- a/MLEB/Translate/specials/SpecialTranslationStats.php
+++ b/MLEB/Translate/specials/SpecialTranslationStats.php
@@ -8,7 +8,8 @@
* @license GPL-2.0-or-later
*/
-use MediaWiki\MediaWikiServices;
+use MediaWiki\Extension\Translate\Services;
+use MediaWiki\Extension\Translate\Statistics\TranslationStatsGraphOptions;
/**
* @defgroup Stats Statistics
@@ -21,17 +22,17 @@ use MediaWiki\MediaWikiServices;
* @ingroup SpecialPage TranslateSpecialPage Stats
*/
class SpecialTranslationStats extends SpecialPage {
- /// @since 2012-03-05
- protected static $graphs = [
- 'edits' => 'TranslatePerLanguageStats',
- 'users' => 'TranslatePerLanguageStats',
- 'registrations' => 'TranslateRegistrationStats',
- 'reviews' => 'ReviewPerLanguageStats',
- 'reviewers' => 'ReviewPerLanguageStats',
- ];
+
+ /** @var \MediaWiki\Extension\Translate\Statistics\TranslationStatsDataProvider */
+ private $dataProvider;
+
+ private const GRAPH_CONTAINER_ID = 'translationStatsGraphContainer';
+
+ private const GRAPH_CONTAINER_CLASS = 'mw-translate-translationstats-container';
public function __construct() {
parent::__construct( 'TranslationStats' );
+ $this->dataProvider = Services::getInstance()->getTranslationStatsDataProvider();
}
public function isIncludable() {
@@ -42,102 +43,27 @@ class SpecialTranslationStats extends SpecialPage {
return 'translation';
}
- /**
- * @since 2012-03-05
- * @return array List of graph types
- */
- public function getGraphTypes() {
- return array_keys( self::$graphs );
- }
-
- /**
- * @since 2012-03-05
- * @param string $type
- * @return string
- */
- public function getGraphClass( $type ) {
- return self::$graphs[$type];
- }
-
public function execute( $par ) {
- $this->getOutput()->addModules( 'ext.translate.special.translationstats' );
-
- $opts = new FormOptions();
- $opts->add( 'graphit', false );
- $opts->add( 'preview', false );
- $opts->add( 'language', '' );
- $opts->add( 'count', 'edits' );
- $opts->add( 'scale', 'days' );
- $opts->add( 'days', 30 );
- $opts->add( 'width', 600 );
- $opts->add( 'height', 400 );
- $opts->add( 'group', '' );
- $opts->add( 'uselang', '' );
- $opts->add( 'start', '' );
- $opts->add( 'imagescale', 1.0 );
- $opts->fetchValuesFromRequest( $this->getRequest() );
+ $graphOpts = new TranslationStatsGraphOptions();
+ $graphOpts->bindArray( $this->getRequest()->getValues() );
$pars = explode( ';', $par );
-
foreach ( $pars as $item ) {
if ( strpos( $item, '=' ) === false ) {
continue;
}
list( $key, $value ) = array_map( 'trim', explode( '=', $item, 2 ) );
- if ( isset( $opts[$key] ) ) {
- $opts[$key] = $value;
+ if ( $graphOpts->hasValue( $key ) ) {
+ $graphOpts->setValue( $key, $value );
}
}
- $opts->validateIntBounds( 'days', 1, 10000 );
- $opts->validateIntBounds( 'width', 200, 1000 );
- $opts->validateIntBounds( 'height', 200, 1000 );
- $opts->validateBounds( 'imagescale', 1.0, 4.0 );
-
- if ( $opts['start'] !== '' ) {
- $opts['start'] = rtrim( wfTimestamp( TS_ISO_8601, $opts['start'] ), 'Z' );
- }
-
- $validScales = [ 'months', 'weeks', 'days', 'hours' ];
- if ( !in_array( $opts['scale'], $validScales ) ) {
- $opts['scale'] = 'days';
- }
-
- if ( $opts['scale'] === 'hours' ) {
- $opts->validateIntBounds( 'days', 1, 4 );
- }
-
- $validCounts = $this->getGraphTypes();
- if ( !in_array( $opts['count'], $validCounts ) ) {
- $opts['count'] = 'edits';
- }
-
- foreach ( [ 'group', 'language' ] as $t ) {
- $values = array_map( 'trim', explode( ',', $opts[$t] ) );
- $values = array_splice( $values, 0, 4 );
- if ( $t === 'group' ) {
- // BC for old syntax which replaced _ to | which was not allowed
- $values = preg_replace( '~^page_~', 'page-', $values );
- }
- $opts[$t] = implode( ',', $values );
- }
+ $graphOpts->normalize( $this->dataProvider->getGraphTypes() );
+ $opts = $graphOpts->getFormOptions();
if ( $this->including() ) {
- $this->getOutput()->addHTML( $this->image( $opts ) );
- } elseif ( $opts['graphit'] ) {
- if ( !class_exists( PHPlot::class ) ) {
- header( 'HTTP/1.0 500 Multi fail' );
- echo 'PHPlot not found';
- }
-
- if ( !$this->getRequest()->getBool( 'debug' ) ) {
- $this->getOutput()->disable();
- header( 'Content-Type: image/png' );
- header( 'Cache-Control: private, max-age=3600' );
- header( 'Expires: ' . wfTimestamp( TS_RFC2822, time() + 3600 ) );
- }
- $this->draw( $opts );
+ $this->getOutput()->addHTML( $this->embed( $opts ) );
} else {
$this->form( $opts );
}
@@ -150,22 +76,19 @@ class SpecialTranslationStats extends SpecialPage {
*/
protected function form( FormOptions $opts ) {
global $wgScript;
-
$this->setHeaders();
$out = $this->getOutput();
+ $out->addModules( 'ext.translate.special.translationstats' );
$out->addHelpLink( 'Help:Extension:Translate/Statistics_and_reporting' );
$out->addWikiMsg( 'translate-statsf-intro' );
-
$out->addHTML(
Xml::fieldset( $this->msg( 'translate-statsf-options' )->text() ) .
- Html::openElement( 'form', [ 'action' => $wgScript ] ) .
+ Html::openElement( 'form', [ 'action' => $wgScript, 'id' => 'translationStatsConfig' ] ) .
Html::hidden( 'title', $this->getPageTitle()->getPrefixedText() ) .
Html::hidden( 'preview', 1 ) .
'<table>'
);
-
$submit = Xml::submitButton( $this->msg( 'translate-statsf-submit' )->text() );
-
$out->addHTML(
$this->eInput( 'width', $opts ) .
$this->eInput( 'height', $opts ) .
@@ -173,55 +96,63 @@ class SpecialTranslationStats extends SpecialPage {
$this->eInput( 'start', $opts, 24 ) .
$this->eInput( 'days', $opts ) .
$this->eRadio( 'scale', $opts, [ 'months', 'weeks', 'days', 'hours' ] ) .
- $this->eRadio( 'count', $opts, $this->getGraphTypes() ) .
+ $this->eRadio( 'count', $opts, $this->dataProvider->getGraphTypes() ) .
'<tr><td colspan="2"><hr /></td></tr>' .
$this->eLanguage( 'language', $opts ) .
$this->eGroup( 'group', $opts ) .
'<tr><td colspan="2"><hr /></td></tr>' .
'<tr><td colspan="2">' . $submit . '</td></tr>'
);
-
$out->addHTML(
'</table>' .
'</form>' .
'</fieldset>'
);
-
if ( !$opts['preview'] ) {
return;
}
-
$spiParams = '';
foreach ( $opts->getChangedValues() as $key => $v ) {
if ( $key === 'preview' ) {
continue;
}
-
if ( $spiParams !== '' ) {
$spiParams .= ';';
}
+ if ( is_array( $v ) ) {
+ $v = implode( ',', $v );
+ if ( !strlen( $v ) ) {
+ continue;
+ }
+ }
$spiParams .= wfEscapeWikiText( "$key=$v" );
}
-
if ( $spiParams !== '' ) {
$spiParams = '/' . $spiParams;
}
-
$titleText = $this->getPageTitle()->getPrefixedText();
-
$out->addHTML(
- Html::element( 'hr' ) .
- Html::element( 'pre', [], "{{{$titleText}{$spiParams}}}" )
+ Html::element( 'hr' )
+ );
+ // Element to render the graph
+ $out->addHTML(
+ Html::rawElement(
+ 'div',
+ [
+ 'id' => self::GRAPH_CONTAINER_ID ,
+ 'style' => 'margin: 2em auto; display: block',
+ 'class' => self::GRAPH_CONTAINER_CLASS
+ ]
+ )
);
$out->addHTML(
- Html::element( 'hr' ) .
- Html::rawElement(
- 'div',
- [ 'style' => 'margin: 1em auto; text-align: center;' ],
- $this->image( $opts )
- )
+ Html::element(
+ 'pre',
+ [ 'aria-label' => $this->msg( 'translate-statsf-embed' )->text() ],
+ "{{{$titleText}{$spiParams}}}"
+ )
);
}
@@ -234,7 +165,6 @@ class SpecialTranslationStats extends SpecialPage {
*/
protected function eInput( $name, FormOptions $opts, $width = 4 ) {
$value = $opts[$name];
-
return '<tr><td>' . $this->eLabel( $name ) . '</td><td>' .
Xml::input( $name, $width, $value, [ 'id' => $name ] ) .
'</td></tr>' . "\n";
@@ -252,7 +182,6 @@ class SpecialTranslationStats extends SpecialPage {
// translate-statsf-language, translate-statsf-group
$label = 'translate-statsf-' . $name;
$label = $this->msg( $label )->escaped();
-
return Xml::tags( 'label', [ 'for' => $name ], $label );
}
@@ -269,7 +198,6 @@ class SpecialTranslationStats extends SpecialPage {
$label = 'translate-statsf-' . $name;
$label = $this->msg( $label )->escaped();
$s = '<tr><td>' . $label . '</td><td>';
-
$options = [];
foreach ( $alts as $alt ) {
$id = "$name-$alt";
@@ -277,10 +205,8 @@ class SpecialTranslationStats extends SpecialPage {
[ 'id' => $id ] ) . ' ';
$options[] = $radio . ' ' . $this->eLabel( $id );
}
-
$s .= implode( ' ', $options );
$s .= '</td></tr>' . "\n";
-
return $s;
}
@@ -291,11 +217,10 @@ class SpecialTranslationStats extends SpecialPage {
* @return string Html.
*/
protected function eLanguage( $name, FormOptions $opts ) {
- $value = $opts[$name];
+ $value = implode( ',', $opts[$name] );
$select = $this->languageSelector();
$select->setTargetId( 'language' );
-
return '<tr><td>' . $this->eLabel( $name ) . '</td><td>' .
$select->getHtmlAndPrepareJS() . '<br />' .
Xml::input( $name, 20, $value, [ 'id' => $name ] ) .
@@ -308,16 +233,12 @@ class SpecialTranslationStats extends SpecialPage {
*/
protected function languageSelector() {
$languages = TranslateUtils::getLanguageNames( $this->getLanguage()->getCode() );
-
ksort( $languages );
-
$selector = new XmlSelect( 'mw-language-selector', 'mw-language-selector' );
foreach ( $languages as $code => $name ) {
$selector->addOption( "$code - $name", $code );
}
-
$jsSelect = new JsSelectToInput( $selector );
-
return $jsSelect;
}
@@ -328,11 +249,10 @@ class SpecialTranslationStats extends SpecialPage {
* @return string Html.
*/
protected function eGroup( $name, FormOptions $opts ) {
- $value = $opts[$name];
+ $value = implode( ',', $opts[$name] );
$select = $this->groupSelector();
$select->setTargetId( 'group' );
-
return '<tr><td>' . $this->eLabel( $name ) . '</td><td>' .
$select->getHtmlAndPrepareJS() . '<br />' .
Xml::input( $name, 20, $value, [ 'id' => $name ] ) .
@@ -345,801 +265,34 @@ class SpecialTranslationStats extends SpecialPage {
*/
protected function groupSelector() {
$groups = MessageGroups::singleton()->getGroups();
- /**
- * @var MessageGroup $group
- */
+ /** @var MessageGroup $group */
foreach ( $groups as $key => $group ) {
if ( !$group->exists() ) {
unset( $groups[$key] );
continue;
}
}
-
ksort( $groups );
-
$selector = new XmlSelect( 'mw-group-selector', 'mw-group-selector' );
- /**
- * @var MessageGroup $name
- */
+ /** @var MessageGroup $name */
foreach ( $groups as $code => $name ) {
$selector->addOption( $name->getLabel(), $code );
}
-
$jsSelect = new JsSelectToInput( $selector );
-
return $jsSelect;
}
- /**
- * Returns an \<img> tag for graph.
- * @param FormOptions $opts
- * @return string Html.
- */
- protected function image( FormOptions $opts ) {
- $title = $this->getPageTitle();
-
- $params = $opts->getChangedValues();
- $params[ 'graphit' ] = true;
- $src = $title->getLocalURL( $params );
-
- $srcsets = [];
- foreach ( [ 1.5, 2, 3 ] as $scale ) {
- $params[ 'imagescale' ] = $scale;
- $srcsets[] = "{$title->getLocalURL( $params )} {$scale}x";
- }
-
- return Xml::element( 'img',
+ protected function embed( FormOptions $opts ) {
+ $this->getOutput()->addModules( 'ext.translate.translationstats.embedded' );
+ return Html::rawElement(
+ 'div',
[
- 'src' => $src,
- 'srcset' => implode( ', ', $srcsets ),
- 'width' => $opts['width'],
- 'height' => $opts['height'],
- ]
+ 'class' => self::GRAPH_CONTAINER_CLASS
+ ],
+ Html::hidden(
+ 'translationStatsGraphOptions',
+ json_encode( $opts->getAllValues() )
+ )
);
}
-
- /**
- * Fetches and preprocesses graph data that can be fed to graph drawer.
- * @param FormOptions $opts
- * @return array ( string => array ) Data indexed by their date labels.
- */
- protected function getData( FormOptions $opts ) {
- $dbr = wfGetDB( DB_REPLICA );
-
- $class = $this->getGraphClass( $opts['count'] );
- $so = new $class( $opts );
-
- $fixedStart = $opts->getValue( 'start' ) !== '';
-
- $now = time();
- $period = 3600 * 24 * $opts->getValue( 'days' );
-
- if ( $fixedStart ) {
- $cutoff = wfTimestamp( TS_UNIX, $opts->getValue( 'start' ) );
- } else {
- $cutoff = $now - $period;
- }
- $cutoff = self::roundTimestampToCutoff( $opts['scale'], $cutoff, 'earlier' );
-
- $start = $cutoff;
-
- if ( $fixedStart ) {
- $end = self::roundTimestampToCutoff( $opts['scale'], $start + $period, 'later' ) - 1;
- } else {
- $end = null;
- }
-
- $tables = [];
- $fields = [];
- $conds = [];
- $type = __METHOD__;
- $options = [];
- $joins = [];
-
- $so->preQuery( $tables, $fields, $conds, $type, $options, $joins, $start, $end );
- $res = $dbr->select( $tables, $fields, $conds, $type, $options, $joins );
- wfDebug( __METHOD__ . "-queryend\n" );
-
- // Start processing the data
- $dateFormat = $so->getDateFormat();
- $increment = self::getIncrement( $opts['scale'] );
-
- $labels = $so->labels();
- $keys = array_keys( $labels );
- $values = array_pad( [], count( $labels ), 0 );
- $defaults = array_combine( $keys, $values );
-
- $data = [];
- // Allow 10 seconds in the future for processing time
- $lastValue = $end ?? $now + 10;
- $lang = $this->getLanguage();
- while ( $cutoff <= $lastValue ) {
- $date = $lang->sprintfDate( $dateFormat, wfTimestamp( TS_MW, $cutoff ) );
- $cutoff += $increment;
- $data[$date] = $defaults;
- }
-
- // Processing
- $labelToIndex = array_flip( $labels );
-
- foreach ( $res as $row ) {
- $indexLabels = $so->indexOf( $row );
- if ( $indexLabels === false ) {
- continue;
- }
-
- foreach ( (array)$indexLabels as $i ) {
- if ( !isset( $labelToIndex[$i] ) ) {
- continue;
- }
- $date = $lang->sprintfDate( $dateFormat, $so->getTimestamp( $row ) );
- // Ignore values outside range
- if ( !isset( $data[$date] ) ) {
- continue;
- }
-
- $data[$date][$labelToIndex[$i]]++;
- }
- }
-
- // Don't display dummy label
- if ( count( $labels ) === 1 && $labels[0] === 'all' ) {
- $labels = [];
- }
-
- foreach ( $labels as &$label ) {
- if ( strpos( $label, '@' ) === false ) {
- continue;
- }
- list( $groupId, $code ) = explode( '@', $label, 2 );
- if ( $code && $groupId ) {
- $code = TranslateUtils::getLanguageName( $code, $lang->getCode() ) . " ($code)";
- $group = MessageGroups::getGroup( $groupId );
- $group = $group ? $group->getLabel() : $groupId;
- $label = "$group @ $code";
- } elseif ( $code ) {
- $label = TranslateUtils::getLanguageName( $code, $lang->getCode() ) . " ($code)";
- } elseif ( $groupId ) {
- $group = MessageGroups::getGroup( $groupId );
- $label = $group ? $group->getLabel() : $groupId;
- }
- }
-
- if ( $end === null ) {
- $last = array_splice( $data, -1, 1 );
- // Indicator that the last value is not full
- $data[key( $last ) . '*'] = current( $last );
- }
-
- return [ $labels, $data ];
- }
-
- /**
- * Gets the closest earlieast timestamp that corresponds to start of a
- * period in given scale, like, midnight, monday or first day of the month.
- * @param string $scale One of hours, days, weeks, months
- * @param int $cutoff Timestamp in unix format.
- * @param string $direction One of earlier, later
- * @return int
- */
- protected static function roundTimestampToCutoff( $scale, $cutoff, $direction = 'earlier' ) {
- $dir = $direction === 'earlier' ? -1 : 1;
-
- /* Ensure that the first item in the graph has full data even
- * if it doesn't align with the given 'days' boundary */
- if ( $scale === 'hours' ) {
- $cutoff += self::roundingAddition( $cutoff, 3600, $dir );
- } elseif ( $scale === 'days' ) {
- $cutoff += self::roundingAddition( $cutoff, 86400, $dir );
- } elseif ( $scale === 'weeks' ) {
- /* Here we assume that week starts on monday, which does not
- * always hold true. Go Xwards day by day until we are on monday */
- while ( date( 'D', $cutoff ) !== 'Mon' ) {
- $cutoff += $dir * 86400;
- }
- // Round to nearest day
- $cutoff -= ( $cutoff % 86400 );
- } elseif ( $scale === 'months' ) {
- // Go Xwards/ day by day until we are on the first day of the month
- while ( date( 'j', $cutoff ) !== '1' ) {
- $cutoff += $dir * 86400;
- }
- // Round to nearest day
- $cutoff -= ( $cutoff % 86400 );
- }
-
- return $cutoff;
- }
-
- /**
- * @param int $ts
- * @param int $amount
- * @param int $dir
- * @return int
- */
- protected static function roundingAddition( $ts, $amount, $dir ) {
- if ( $dir === -1 ) {
- return -1 * ( $ts % $amount );
- } else {
- return $amount - ( $ts % $amount );
- }
- }
-
- /**
- * Adds raw image data of the graph to the output.
- * @param FormOptions $opts
- */
- public function draw( FormOptions $opts ) {
- global $wgTranslatePHPlotFont;
-
- $imageScale = $opts->getValue( 'imagescale' );
- $width = $opts->getValue( 'width' );
- $height = $opts->getValue( 'height' );
- // Define the object
- $plot = new PHPlot( $width * $imageScale, $height * $imageScale );
-
- list( $legend, $resData ) = $this->getData( $opts );
- $count = count( $resData );
- $skip = (int)( $count / ( $width / 60 ) - 1 );
- $i = $count;
- $data = [];
-
- foreach ( $resData as $date => $edits ) {
- if ( $skip > 0 &&
- ( $count - $i ) % $skip !== 0
- ) {
- $date = '';
- }
-
- if ( strpos( $date, ';' ) !== false ) {
- list( , $date ) = explode( ';', $date, 2 );
- }
-
- array_unshift( $edits, $date );
- $data[] = $edits;
- $i--;
- }
-
- $font = FCFontFinder::findFile( $this->getLanguage()->getCode() );
- if ( !$font ) {
- $font = $wgTranslatePHPlotFont;
- }
- $numberFont = FCFontFinder::findFile( 'en' );
- $plot->SetDefaultTTFont( $font );
- $plot->SetFontTTF( 'generic', $font, 12 * $imageScale );
- $plot->SetFontTTF( 'legend', $font, 12 * $imageScale );
- $plot->SetFontTTF( 'x_title', $font, 10 * $imageScale );
- $plot->SetFontTTF( 'y_title', $font, 10 * $imageScale );
- $plot->SetFontTTF( 'x_label', $numberFont, 8 * $imageScale );
- $plot->SetFontTTF( 'y_label', $numberFont, 8 * $imageScale );
-
- $plot->SetDataValues( $data );
-
- if ( $legend !== null ) {
- $plot->SetLegend( $legend );
- }
-
- // Give grep a chance to find the usages:
- // translate-stats-edits, translate-stats-users, translate-stats-registrations,
- // translate-stats-reviews, translate-stats-reviewers
- $yTitle = $this->msg( 'translate-stats-' . $opts['count'] )->escaped();
-
- // Turn off X axis ticks and labels because they get in the way:
- $plot->SetYTitle( $yTitle );
- $plot->SetXTickLabelPos( 'none' );
- $plot->SetXTickPos( 'none' );
- $plot->SetXLabelAngle( 45 );
-
- $max = max( array_map( 'max', $resData ) );
- $max = self::roundToSignificant( $max, 1 );
- $max = round( $max, (int)( -log( $max, 10 ) ) );
-
- $yTick = 10;
- while ( $max / $yTick > $height / 20 ) {
- $yTick *= 2;
- }
-
- // If we have very small case, ensure that there is at least one tick
- $yTick = min( $max, $yTick );
- $yTick = self::roundToSignificant( $yTick );
- $plot->SetYTickIncrement( $yTick );
- $plot->SetPlotAreaWorld( null, 0, null, max( $max, 10 ) );
-
- $plot->SetTransparentColor( 'white' );
- $plot->SetBackgroundColor( 'white' );
-
- // Draw it
- $plot->DrawGraph();
- }
-
- /**
- * Enhanced version of round that supports rounding up to a given scale
- * relative to the number itself. Examples:
- * - roundToSignificant( 1234, 0 ) = 10000
- * - roundToSignificant( 1234, 1 ) = 2000
- * - roundToSignificant( 1234, 2 ) = 1300
- * - roundToSignificant( 1234, 3 ) = 1240
- *
- * @param int $number Number to round.
- * @param int $significant How many signficant numbers to keep.
- * @return int Rounded number.
- */
- public static function roundToSignificant( $number, $significant = 1 ) {
- $log = (int)log( $number, 10 );
- $nonSignificant = max( 0, $log - $significant + 1 );
- $factor = pow( 10, $nonSignificant );
-
- return (int)( ceil( $number / $factor ) * $factor );
- }
-
- /**
- * Returns an increment in seconds for a given scale.
- * The increment must be small enough that we will hit every item in the
- * scale when using different multiples of the increment. It should be
- * large enough to avoid hitting the same item multiple times.
- * @param string $scale Either months, weeks, days or hours.
- * @return int Number of seconds in the increment.
- */
- public static function getIncrement( $scale ) {
- $increment = 3600 * 24;
- if ( $scale === 'months' ) {
- /* We use increment to fill up the values. Use number small enough
- * to ensure we hit each month */
- $increment = 3600 * 24 * 15;
- } elseif ( $scale === 'weeks' ) {
- $increment = 3600 * 24 * 7;
- } elseif ( $scale === 'hours' ) {
- $increment = 3600;
- }
-
- return $increment;
- }
-}
-
-/**
- * Interface for producing different kinds of graphs.
- * The graphs are based on data queried from the database.
- * @ingroup Stats
- */
-interface TranslationStatsInterface {
- /**
- * Constructor. The implementation can access the graph options, but not
- * define new ones.
- * @param FormOptions $opts
- */
- public function __construct( FormOptions $opts );
-
- /**
- * Query details that the graph must fill.
- * @param array &$tables Empty list. Append table names.
- * @param array &$fields Empty list. Append field names.
- * @param array &$conds Empty array. Append select conditions.
- * @param string &$type Append graph type (used to identify queries).
- * @param array &$options Empty array. Append extra query options.
- * @param array &$joins Empty array. Append extra join conditions.
- * @param string $start Precalculated start cutoff timestamp
- * @param string $end Precalculated end cutoff timestamp
- */
- public function preQuery( &$tables, &$fields, &$conds, &$type, &$options, &$joins, $start, $end );
-
- /**
- * Return the indexes which this result contributes to.
- * Return 'all' if only one variable is measured. Return false if none.
- * @param stdClass $row Database Result Row
- * @return array|false
- */
- public function indexOf( $row );
-
- /**
- * Return the names of the variables being measured.
- * Return 'all' if only one variable is measured. Must match indexes
- * returned by indexOf() and contain them all.
- * @return string[]
- */
- public function labels();
-
- /**
- * Return the timestamp associated with this result row.
- * @param stdClass $row Database Result Row
- * @return string Timestamp.
- */
- public function getTimestamp( $row );
-
- /**
- * Return time formatting string.
- * @see Language::sprintfDate()
- * @return string
- */
- public function getDateFormat();
-}
-
-/**
- * Provides some hand default implementations for TranslationStatsInterface.
- * @ingroup Stats
- */
-abstract class TranslationStatsBase implements TranslationStatsInterface {
- /**
- * @var FormOptions Graph options.
- */
- protected $opts;
-
- public function __construct( FormOptions $opts ) {
- $this->opts = $opts;
- }
-
- public function indexOf( $row ) {
- return [ 'all' ];
- }
-
- public function labels() {
- return [ 'all' ];
- }
-
- public function getDateFormat() {
- $dateFormat = 'Y-m-d';
- if ( $this->opts['scale'] === 'months' ) {
- $dateFormat = 'Y-m';
- } elseif ( $this->opts['scale'] === 'weeks' ) {
- $dateFormat = 'Y-\WW';
- } elseif ( $this->opts['scale'] === 'hours' ) {
- $dateFormat .= ';H';
- }
-
- return $dateFormat;
- }
-
- protected static function makeTimeCondition( $field, $start, $end ) {
- $db = wfGetDB( DB_REPLICA );
-
- $conds = [];
- if ( $start !== null ) {
- $conds[] = "$field >= '{$db->timestamp( $start )}'";
- }
- if ( $end !== null ) {
- $conds[] = "$field <= '{$db->timestamp( $end )}'";
- }
-
- return $conds;
- }
-
- /**
- * @since 2012-03-05
- * @param array $groupIds
- * @return array
- */
- protected static function namespacesFromGroups( $groupIds ) {
- $namespaces = [];
- foreach ( $groupIds as $id ) {
- $group = MessageGroups::getGroup( $id );
- if ( $group ) {
- $namespace = $group->getNamespace();
- $namespaces[$namespace] = true;
- }
- }
-
- return array_keys( $namespaces );
- }
-}
-
-/**
- * Graph which provides statistics on active users and number of translations.
- * @ingroup Stats
- */
-class TranslatePerLanguageStats extends TranslationStatsBase {
- /** @var int[][] array( string => int ) Cache used to count active users only once per day. */
- protected $usercache;
-
- protected $codes, $groups;
-
- public function __construct( FormOptions $opts ) {
- parent::__construct( $opts );
- // This query is slow... ensure a lower limit.
- $opts->validateIntBounds( 'days', 1, 200 );
- }
-
- public function preQuery( &$tables, &$fields, &$conds, &$type, &$options, &$joins, $start, $end ) {
- global $wgTranslateMessageNamespaces;
-
- $db = wfGetDB( DB_REPLICA );
-
- $tables = [ 'recentchanges' ];
- $fields = [ 'rc_timestamp' ];
- $joins = [];
-
- $conds = [
- 'rc_namespace' => $wgTranslateMessageNamespaces,
- 'rc_bot' => 0,
- 'rc_type != ' . RC_LOG,
- ];
-
- $timeConds = self::makeTimeCondition( 'rc_timestamp', $start, $end );
- $conds = array_merge( $conds, $timeConds );
-
- $options = [ 'ORDER BY' => 'rc_timestamp' ];
-
- $this->groups = array_filter( array_map( 'trim', explode( ',', $this->opts['group'] ) ) );
- $this->groups = array_map( 'MessageGroups::normalizeId', $this->groups );
- $this->codes = array_filter( array_map( 'trim', explode( ',', $this->opts['language'] ) ) );
-
- $namespaces = self::namespacesFromGroups( $this->groups );
- if ( count( $namespaces ) ) {
- $conds['rc_namespace'] = $namespaces;
- }
-
- $languages = [];
- foreach ( $this->codes as $code ) {
- $languages[] = 'rc_title ' . $db->buildLike( $db->anyString(), "/$code" );
- }
- if ( count( $languages ) ) {
- $conds[] = $db->makeList( $languages, LIST_OR );
- }
-
- $fields[] = 'rc_title';
-
- if ( $this->groups ) {
- $fields[] = 'rc_namespace';
- }
-
- if ( $this->opts['count'] === 'users' ) {
- if ( class_exists( ActorMigration::class ) ) {
- $actorQuery = ActorMigration::newMigration()->getJoin( 'rc_user' );
- $tables += $actorQuery['tables'];
- $fields['rc_user_text'] = $actorQuery['fields']['rc_user_text'];
- $joins += $actorQuery['joins'];
- } else {
- $fields[] = 'rc_user_text';
- }
- }
-
- $type .= '-perlang';
- }
-
- public function indexOf( $row ) {
- // We need to check that there is only one user per day.
- if ( $this->opts['count'] === 'users' ) {
- $date = $this->formatTimestamp( $row->rc_timestamp );
-
- if ( isset( $this->usercache[$date][$row->rc_user_text] ) ) {
- return false;
- } else {
- $this->usercache[$date][$row->rc_user_text] = 1;
- }
- }
-
- // Do not consider language-less pages.
- if ( strpos( $row->rc_title, '/' ) === false ) {
- return false;
- }
-
- // No filters, just one key to track.
- if ( !$this->groups && !$this->codes ) {
- return [ 'all' ];
- }
-
- // The key-building needs to be in sync with ::labels().
- list( $key, $code ) = TranslateUtils::figureMessage( $row->rc_title );
-
- $groups = [];
- $codes = [];
-
- if ( $this->groups ) {
- /*
- * Get list of keys that the message belongs to, and filter
- * out those which are not requested.
- */
- $groups = TranslateUtils::messageKeyToGroups( $row->rc_namespace, $key );
- $groups = array_intersect( $this->groups, $groups );
- }
-
- if ( $this->codes ) {
- $codes = [ $code ];
- }
-
- return $this->combineTwoArrays( $groups, $codes );
- }
-
- public function labels() {
- return $this->combineTwoArrays( $this->groups, $this->codes );
- }
-
- public function getTimestamp( $row ) {
- return $row->rc_timestamp;
- }
-
- /**
- * Makes a label for variable. If group or language code filters, or both
- * are used, combine those in a pretty way.
- * @param string $group Group name.
- * @param string $code Language code.
- * @return string Label.
- */
- protected function makeLabel( $group, $code ) {
- if ( $group || $code ) {
- return "$group@$code";
- } else {
- return 'all';
- }
- }
-
- /**
- * Cross-product of two lists with string results, where either
- * list can be empty.
- * @param string[] $groups Group names.
- * @param string[] $codes Language codes.
- * @return string[] Labels.
- */
- protected function combineTwoArrays( $groups, $codes ) {
- if ( !count( $groups ) ) {
- $groups[] = false;
- }
-
- if ( !count( $codes ) ) {
- $codes[] = false;
- }
-
- $items = [];
- foreach ( $groups as $group ) {
- foreach ( $codes as $code ) {
- $items[] = $this->makeLabel( $group, $code );
- }
- }
-
- return $items;
- }
-
- /**
- * Returns unique index for given item in the scale being used.
- * Called a lot, so performance intensive.
- * @param string $timestamp Timestamp in mediawiki format.
- * @return string
- */
- protected function formatTimestamp( $timestamp ) {
- switch ( $this->opts['scale'] ) {
- case 'hours' :
- $cut = 4;
- break;
- case 'days' :
- $cut = 6;
- break;
- case 'months':
- $cut = 8;
- break;
- default :
- return MediaWikiServices::getInstance()->getContentLanguage()
- ->sprintfDate( $this->getDateFormat(), $timestamp );
- }
-
- return substr( $timestamp, 0, -$cut );
- }
-}
-
-/**
- * Graph which provides statistics about amount of registered users in a given time.
- * @ingroup Stats
- */
-class TranslateRegistrationStats extends TranslationStatsBase {
- public function preQuery( &$tables, &$fields, &$conds, &$type, &$options, &$joins, $start, $end ) {
- $tables = 'user';
- $fields = 'user_registration';
- $conds = self::makeTimeCondition( 'user_registration', $start, $end );
- $type .= '-registration';
- $options = [];
- $joins = [];
- }
-
- public function getTimestamp( $row ) {
- return $row->user_registration;
- }
-}
-
-/**
- * Graph which provides statistics on number of reviews and reviewers.
- * @since 2012-03-05
- * @ingroup Stats
- */
-class ReviewPerLanguageStats extends TranslatePerLanguageStats {
- public function preQuery( &$tables, &$fields, &$conds, &$type, &$options, &$joins, $start, $end ) {
- global $wgTranslateMessageNamespaces;
-
- $db = wfGetDB( DB_REPLICA );
-
- $tables = [ 'logging' ];
- $fields = [ 'log_timestamp' ];
- $joins = [];
-
- $conds = [
- 'log_namespace' => $wgTranslateMessageNamespaces,
- 'log_action' => 'message',
- ];
-
- $timeConds = self::makeTimeCondition( 'log_timestamp', $start, $end );
- $conds = array_merge( $conds, $timeConds );
-
- $options = [ 'ORDER BY' => 'log_timestamp' ];
-
- $this->groups = array_filter( array_map( 'trim', explode( ',', $this->opts['group'] ) ) );
- $this->codes = array_filter( array_map( 'trim', explode( ',', $this->opts['language'] ) ) );
-
- $namespaces = self::namespacesFromGroups( $this->groups );
- if ( count( $namespaces ) ) {
- $conds['log_namespace'] = $namespaces;
- }
-
- $languages = [];
- foreach ( $this->codes as $code ) {
- $languages[] = 'log_title ' . $db->buildLike( $db->anyString(), "/$code" );
- }
- if ( count( $languages ) ) {
- $conds[] = $db->makeList( $languages, LIST_OR );
- }
-
- $fields[] = 'log_title';
-
- if ( $this->groups ) {
- $fields[] = 'log_namespace';
- }
-
- if ( $this->opts['count'] === 'reviewers' ) {
- if ( class_exists( ActorMigration::class ) ) {
- $actorQuery = ActorMigration::newMigration()->getJoin( 'log_user' );
- $tables += $actorQuery['tables'];
- $fields['log_user_text'] = $actorQuery['fields']['log_user_text'];
- $joins += $actorQuery['joins'];
- } else {
- $fields[] = 'log_user_text';
- }
- }
-
- $type .= '-reviews';
- }
-
- public function indexOf( $row ) {
- // We need to check that there is only one user per day.
- if ( $this->opts['count'] === 'reviewers' ) {
- $date = $this->formatTimestamp( $row->log_timestamp );
-
- if ( isset( $this->usercache[$date][$row->log_user_text] ) ) {
- return false;
- } else {
- $this->usercache[$date][$row->log_user_text] = 1;
- }
- }
-
- // Do not consider language-less pages.
- if ( strpos( $row->log_title, '/' ) === false ) {
- return false;
- }
-
- // No filters, just one key to track.
- if ( !$this->groups && !$this->codes ) {
- return [ 'all' ];
- }
-
- // The key-building needs to be in sync with ::labels().
- list( $key, $code ) = TranslateUtils::figureMessage( $row->log_title );
-
- $groups = [];
- $codes = [];
-
- if ( $this->groups ) {
- /* Get list of keys that the message belongs to, and filter
- * out those which are not requested. */
- $groups = TranslateUtils::messageKeyToGroups( $row->log_namespace, $key );
- $groups = array_intersect( $this->groups, $groups );
- }
-
- if ( $this->codes ) {
- $codes = [ $code ];
- }
-
- return $this->combineTwoArrays( $groups, $codes );
- }
-
- public function labels() {
- return $this->combineTwoArrays( $this->groups, $this->codes );
- }
-
- public function getTimestamp( $row ) {
- return $row->log_timestamp;
- }
}
diff --git a/MLEB/Translate/specials/SpecialTranslations.php b/MLEB/Translate/specials/SpecialTranslations.php
index ec795627..ebdf1787 100644
--- a/MLEB/Translate/specials/SpecialTranslations.php
+++ b/MLEB/Translate/specials/SpecialTranslations.php
@@ -18,7 +18,8 @@ use MediaWiki\MediaWikiServices;
*/
class SpecialTranslations extends SpecialAllPages {
public function __construct() {
- parent::__construct( 'Translations' );
+ parent::__construct();
+ $this->mName = 'Translations';
}
protected function getGroupName() {
@@ -115,8 +116,7 @@ class SpecialTranslations extends SpecialAllPages {
$context = new DerivativeContext( $this->getContext() );
$context->setTitle( $this->getPageTitle() ); // Remove subpage
- $htmlForm = HTMLForm::factory( 'ooui', $formDescriptor, $context );
- $htmlForm
+ HTMLForm::factory( 'ooui', $formDescriptor, $context )
->setMethod( 'get' )
->setSubmitTextMsg( 'allpagessubmit' )
->setWrapperLegendMsg( 'translate-translations-fieldset-title' )
@@ -197,7 +197,7 @@ class SpecialTranslations extends SpecialAllPages {
$pageInfo = TranslateUtils::getContents( $titles, $namespace );
$tableheader = Xml::openElement( 'table', [
- 'class' => 'mw-sp-translate-table sortable'
+ 'class' => 'mw-sp-translate-table sortable wikitable'
] );
$tableheader .= Xml::openElement( 'tr' );
@@ -239,10 +239,9 @@ class SpecialTranslations extends SpecialAllPages {
[ 'action' => 'history' ]
);
+ $class = '';
if ( MessageHandle::hasFuzzyString( $pageInfo[$key][0] ) || $tHandle->isFuzzy() ) {
- $class = 'orig';
- } else {
- $class = 'def';
+ $class = 'mw-sp-translate-fuzzy';
}
$languageAttributes = [];