39 private LanguageNameUtils $languageNameUtils;
41 private array $targetValueName = [
'code',
'language' ];
43 private array $totals;
45 private bool $nothing =
false;
47 private bool $incomplete =
false;
49 private bool $noComplete =
true;
51 private bool $noEmpty =
false;
53 private string $target;
61 private array $statsCounted = [];
62 private array $states = [];
63 private LinkBatchFactory $linkBatchFactory;
65 private JobQueueGroup $jobQueueGroup;
67 private BagOStuff $cache;
69 public function __construct(
70 LinkBatchFactory $linkBatchFactory,
72 LanguageNameUtils $languageNameUtils,
73 JobQueueGroup $jobQueueGroup,
75 ObjectCacheFactory $objectCacheFactory
77 parent::__construct(
'LanguageStats' );
79 $this->linkBatchFactory = $linkBatchFactory;
80 $this->progressStatsTableFactory = $progressStatsTableFactory;
81 $this->languageNameUtils = $languageNameUtils;
82 $this->jobQueueGroup = $jobQueueGroup;
83 $this->groupReviewStore = $groupReviewStore;
84 $this->cache = $objectCacheFactory->getInstance( CACHE_ANYTHING );
87 public function isIncludable() {
91 protected function getGroupName() {
95 public function execute( $par ) {
96 $this->target = $this->getLanguage()->getCode();
97 $request = $this->getRequest();
99 $this->purge = $request->getVal(
'action' ) ===
'purge';
100 if ( $this->purge && !$request->wasPosted() ) {
101 self::showPurgeForm( $this->getContext() );
105 $this->table = $this->progressStatsTableFactory->newFromContext( $this->getContext() );
108 $this->outputHeader();
110 $out = $this->getOutput();
112 $out->addModules(
'ext.translate.special.languagestats' );
113 $out->addModuleStyles(
'ext.translate.statstable' );
115 $params = $par ? explode(
'/', $par ) : [];
117 if ( isset( $params[0] ) && trim( $params[0] ) ) {
118 $this->target = $params[0];
121 if ( isset( $params[1] ) ) {
122 $this->noComplete = (bool)$params[1];
125 if ( isset( $params[2] ) ) {
126 $this->noEmpty = (bool)$params[2];
130 $submitted = !$this->including() && $request->getVal(
'x' ) ===
'D';
133 foreach ( $this->targetValueName as $key ) {
134 $this->target = $request->getVal( $key, $this->target );
136 $this->noComplete = $request->getBool(
138 $this->noComplete && !$submitted
140 $this->noEmpty = $request->getBool(
'suppressempty', $this->noEmpty && !$submitted );
142 if ( !$this->including() ) {
143 $out->addHelpLink(
'Help:Extension:Translate/Statistics_and_reporting' );
147 if ( $this->isValidValue( $this->target ) ) {
148 $this->outputIntroduction();
151 $output = $this->getTable( $stats );
152 if ( $this->incomplete ) {
154 "<div class='error'>$1</div>",
155 'translate-langstats-incomplete'
159 if ( $this->incomplete || $this->purge ) {
160 DeferredUpdates::addCallableUpdate(
function () {
171 $jobParams = $this->getCacheRebuildJobParameters( $this->target );
172 $jobParams[
'purge' ] = $this->purge;
173 $this->jobQueueGroup->push( RebuildMessageGroupStatsJob::newJob( $jobParams ) );
176 if ( !$this->purge ) {
177 $this->loadStatistics( $this->target );
181 if ( $this->nothing ) {
182 $out->wrapWikiMsg(
"<div class='error'>$1</div>",
'translate-mgs-nothing' );
184 $out->addHTML( $output );
185 } elseif ( $submitted ) {
186 $this->invalidTarget();
196 private function loadStatistics(
string $target,
int $flags = 0 ): array {
200 private function getCacheRebuildJobParameters( $target ): array {
201 return [
'languagecode' => $target ];
205 private function isValidValue(
string $value ):
bool {
206 $langs = $this->languageNameUtils->getLanguageNames();
208 return isset( $langs[$value] );
212 private function invalidTarget():
void {
213 $this->getOutput()->wrapWikiMsg(
214 "<div class='error'>$1</div>",
215 'translate-page-no-such-language'
219 public static function showPurgeForm( IContextSource $context ):
void {
223 'vertical-label' =>
true,
225 'default' => $context->msg(
'confirm-purge-top' )->parse()
229 $derivativeContext =
new DerivativeContext( $context );
230 $requestValues = $derivativeContext->getRequest()->getQueryValues();
232 HTMLForm::factory(
'ooui', $formDescriptor, $derivativeContext )
233 ->setWrapperLegendMsg(
'confirm-purge-title' )
234 ->setSubmitTextMsg(
'confirm_purge_button' )
235 ->addHiddenFields( $requestValues )
240 private function addForm():
void {
244 'name' =>
'language',
246 'label' => $this->msg(
'translate-language-code-field-name' )->text(),
248 'default' => $this->target,
250 'suppresscomplete' => [
252 'label' => $this->msg(
'translate-suppress-complete' )->text(),
253 'name' =>
'suppresscomplete',
254 'id' =>
'suppresscomplete',
255 'default' => $this->noComplete,
259 'label' => $this->msg(
'translate-ls-noempty' )->text(),
260 'name' =>
'suppressempty',
261 'id' =>
'suppressempty',
262 'default' => $this->noEmpty,
266 $context =
new DerivativeContext( $this->getContext() );
267 $context->setTitle( $this->getPageTitle() );
269 $htmlForm = HTMLForm::factory(
'ooui', $formDescriptor, $context );
274 $val = $this->getRequest()->getVal(
'group' );
275 if ( $val !==
null ) {
276 $htmlForm->addHiddenField(
'group', $val );
280 ->addHiddenField(
'x',
'D' )
282 ->setSubmitTextMsg(
'translate-ls-submit' )
283 ->setWrapperLegendMsg(
'translate-mgs-fieldset' )
285 ->displayForm(
false );
289 private function outputIntroduction():
void {
290 $languageName = Utilities::getLanguageName(
292 $this->getLanguage()->getCode()
295 $rcInLangLink = $this->getLinkRenderer()->makeKnownLink(
296 SpecialPage::getTitleFor(
'Translate',
'!recent' ),
297 $this->msg(
'languagestats-recenttranslations' )->text(),
300 'action' =>
'proofread',
301 'language' => $this->target
305 $out = $this->msg(
'languagestats-stats-for', $languageName )->rawParams( $rcInLangLink )
307 $this->getOutput()->addHTML( $out );
310 private function getWorkflowStateCell(
string $messageGroupId ):
string {
311 if ( $this->states === [] ) {
315 return $this->table->makeWorkflowStateCell(
316 $this->states[$messageGroupId] ??
null,
317 MessageGroups::getGroup( $messageGroupId ),
322 private function getTable( array $stats ):
string {
323 $table = $this->table;
328 $lb = $this->linkBatchFactory->newLinkBatch();
329 foreach ( MessageGroups::getAllGroups() as $group ) {
331 $lb->addObj( $group->getTitle() );
334 $lb->setCaller( __METHOD__ )->execute();
336 $structure = MessageGroups::getGroupStructure();
338 if ( $this->getConfig()->
get(
'TranslateWorkflowStates' ) ) {
339 $this->states = $this->groupReviewStore->getWorkflowStatesForLanguage(
341 array_keys( $structure )
344 $this->table->addExtraColumn( $this->msg(
'translate-stats-workflow' ) );
347 foreach ( $structure as $item ) {
348 $out .= $this->makeGroupGroup( $item, $stats );
352 $table->setMainColumnHeader( $this->msg(
'translate-ls-column-group' ) );
354 $out .= Html::closeElement(
'tbody' );
356 $out .= Html::openElement(
'tfoot' );
358 $this->msg(
'translate-languagestats-overall' ),
361 $out .= Html::closeElement(
'tfoot' );
363 $out .= Html::closeElement(
'table' );
367 $this->nothing =
true;
383 private function makeGroupGroup( $item, array $cache, ?
MessageGroup $parent =
null,
int $depth = 0 ):
string {
384 if ( !is_array( $item ) ) {
385 return $this->makeGroupRow( $item, $cache, $parent, $depth );
390 $top = array_shift( $item );
391 $out .= $this->makeGroupRow( $top, $cache, $parent, $depth );
394 foreach ( $item as $subgroup ) {
395 $out .= $this->makeGroupGroup( $subgroup, $cache, $top, $depth + 1 );
405 private function makeGroupRow(
411 $groupId = $group->
getId();
413 if ( $this->table->isExcluded( $group, $this->target ) ) {
417 $stats = $cache[$groupId];
423 if ( $this->noComplete && $fuzzy === 0 && $translated === $total ) {
426 if ( $this->noEmpty && $translated === 0 && $fuzzy === 0 ) {
430 if ( $total ===
null ) {
431 $this->incomplete =
true;
436 !isset( $this->statsCounted[$groupId] )
438 $this->totals = MessageGroupStats::multiAdd( $this->totals, $stats );
439 $this->statsCounted[$groupId] =
true;
444 $params[] = $this->states[$groupId] ??
'';
445 $params[] = md5( $groupId );
446 $params[] = $this->getLanguage()->getCode();
447 $params[] = md5( $this->target );
448 $params[] = $parent ? $parent->getId() :
'!';
451 return $this->cache->getWithSetCallback(
452 $this->cache->makeKey( __METHOD__ .
'-v3', implode(
'-', $params ) ),
453 $this->cache::TTL_DAY,
454 function () use ( $translated, $total, $groupId, $group, $parent, $stats, $depth ) {
457 if ( $translated === $total ) {
458 $extra = [
'action' =>
'proofread' ];
462 $rowParams[
'data-groupid'] = $groupId;
463 $rowParams[
'class'] = get_class( $group );
465 $rowParams[
'data-parentgroup'] = $parent->getId();
468 $rowParams[
'data-depth'] = $depth;
472 Html::openElement(
'tr', $rowParams ) .
477 $this->table->makeGroupLink( $group, $this->target, $extra )
478 ) . $this->table->makeNumberColumns( $stats ) .
479 $this->getWorkflowStateCell( $groupId ) .
481 Html::closeElement(
'tr' ) .