30 private $targetValueName = [
'group' ];
37 private $nothing =
false;
42 private $incomplete =
false;
47 private $noComplete =
true;
52 private $noEmpty =
false;
63 private $progressStatsTableFactory;
67 private $numberOfShownLanguages;
69 private $jobQueueGroup;
73 public function __construct(
75 JobQueueGroup $jobQueueGroup
77 parent::__construct(
'MessageGroupStats' );
78 $this->progressStatsTableFactory = $progressStatsTableFactory;
79 $this->jobQueueGroup = $jobQueueGroup;
80 $this->totals = MessageGroupStats::getEmptyStats();
83 public function getDescription() {
84 return $this->msg(
'translate-mgs-pagename' )->text();
87 public function isIncludable() {
91 protected function getGroupName() {
95 public function execute( $par ) {
96 $request = $this->getRequest();
98 $this->purge = $request->getVal(
'action' ) ===
'purge';
99 if ( $this->purge && !$request->wasPosted() ) {
100 LanguageStatsSpecialPage::showPurgeForm( $this->getContext() );
104 $this->table = $this->progressStatsTableFactory->newFromContext( $this->getContext() );
107 $this->outputHeader();
109 $out = $this->getOutput();
111 $out->addModules(
'ext.translate.special.languagestats' );
112 $out->addModuleStyles(
'ext.translate.statstable' );
114 $params = $par ? explode(
'/', $par ) : [];
116 if ( isset( $params[0] ) && trim( $params[0] ) ) {
117 $this->target = $params[0];
120 if ( isset( $params[1] ) ) {
121 $this->noComplete = (bool)$params[1];
124 if ( isset( $params[2] ) ) {
125 $this->noEmpty = (bool)$params[2];
129 $submitted = !$this->including() && $request->getVal(
'x' ) ===
'D';
132 foreach ( $this->targetValueName as $key ) {
133 $this->target = $request->getVal( $key, $this->target );
135 $this->noComplete = $request->getBool(
137 $this->noComplete && !$submitted
139 $this->noEmpty = $request->getBool(
'suppressempty', $this->noEmpty && !$submitted );
141 if ( !$this->including() ) {
142 $out->addHelpLink(
'Help:Extension:Translate/Statistics_and_reporting' );
146 if ( $this->isValidValue( $this->target ) ) {
147 $this->outputIntroduction();
149 $stats = $this->loadStatistics( $this->target, MessageGroupStats::FLAG_CACHE_ONLY );
150 $output = $this->getTable( $stats );
151 if ( $this->incomplete ) {
153 "<div class='error'>$1</div>",
154 'translate-langstats-incomplete'
158 if ( $this->incomplete || $this->purge ) {
159 DeferredUpdates::addCallableUpdate(
function () {
170 $jobParams = $this->getCacheRebuildJobParameters( $this->target );
171 $jobParams[
'purge' ] = $this->purge;
172 $job = MessageGroupStatsRebuildJob::newJob( $jobParams );
173 $this->jobQueueGroup->push( $job );
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();
192 private function loadStatistics(
string $target,
int $flags = 0 ): array {
193 return MessageGroupStats::forGroup( $target, $flags );
196 private function getCacheRebuildJobParameters(
string $target ): array {
197 return [
'groupid' => $target ];
200 private function isValidValue( ?
string $value ):
bool {
201 if ( $value ===
null ) {
205 $group = MessageGroups::getGroup( $value );
207 if ( MessageGroups::isDynamic( $group ) ) {
215 $this->target = $group->getId();
222 private function invalidTarget():
void {
223 $this->getOutput()->wrapWikiMsg(
224 "<div class='error'>$1</div>",
225 [
'translate-mgs-invalid-group', $this->target ]
229 private function outputIntroduction():
void {
230 $priorityLangs = TranslateMetadata::get( $this->target,
'prioritylangs' );
231 if ( $priorityLangs ) {
232 $hasPriorityForce = TranslateMetadata::get( $this->target,
'priorityforce' ) ===
'on';
233 if ( $hasPriorityForce ) {
234 $this->getOutput()->addWikiMsg(
'tpt-priority-languages-force', $priorityLangs );
236 $this->getOutput()->addWikiMsg(
'tpt-priority-languages', $priorityLangs );
242 private function addWorkflowStatesColumn():
void {
243 global $wgTranslateWorkflowStates;
245 if ( $wgTranslateWorkflowStates ) {
246 $this->states = $this->getWorkflowStates();
249 $this->table->addExtraColumn( $this->msg(
'translate-stats-workflow' ) );
254 private function getWorkflowStateCell(
string $language ):
string {
256 if ( !isset( $this->states ) ) {
260 return $this->table->makeWorkflowStateCell(
261 $this->states[$language] ??
null,
262 MessageGroups::getGroup( $this->target ),
267 private function addForm():
void {
273 'label' => $this->msg(
'translate-mgs-group' )->text(),
274 'options' => $this->getGroupOptions(),
275 'default' => $this->target
277 'nocomplete-check' => [
279 'name' =>
'suppresscomplete',
280 'id' =>
'suppresscomplete',
281 'label' => $this->msg(
'translate-mgs-nocomplete' )->text(),
282 'default' => $this->noComplete,
286 'name' =>
'suppressempty',
287 'id' =>
'suppressempty',
288 'label' => $this->msg(
'translate-mgs-noempty' )->text(),
289 'default' => $this->noEmpty,
293 $htmlForm = HTMLForm::factory(
'ooui', $formDescriptor, $this->getContext() );
298 $val = $this->getRequest()->getVal(
'language' );
299 if ( $val !==
null ) {
300 $htmlForm->addHiddenField(
'language', $val );
304 ->addHiddenField(
'x',
'D' )
306 ->setSubmitTextMsg(
'translate-mgs-submit' )
307 ->setWrapperLegendMsg(
'translate-mgs-fieldset' )
309 ->displayForm(
false );
312 private function getTable( array $stats ):
string {
313 $table = $this->table;
315 $this->addWorkflowStatesColumn();
318 $this->numberOfShownLanguages = 0;
319 $languages = array_keys(
320 TranslateUtils::getLanguageNames( $this->getLanguage()->getCode() )
323 $this->filterPriorityLangs( $languages, $this->target, $stats );
324 foreach ( $languages as $code ) {
325 if ( $table->isExcluded( $this->target, $code ) ) {
328 $out .= $this->makeRow( $code, $stats );
332 $table->setMainColumnHeader( $this->msg(
'translate-mgs-column-language' ) );
333 $out = $table->createHeader() .
"\n" . $out;
334 $out .= Html::closeElement(
'tbody' );
336 $out .= Html::openElement(
'tfoot' );
337 $out .= $table->makeTotalRow(
338 $this->msg(
'translate-mgs-totals' )
339 ->numParams( $this->numberOfShownLanguages ),
342 $out .= Html::closeElement(
'tfoot' );
344 $out .= Html::closeElement(
'table' );
348 $this->nothing =
true;
359 private function filterPriorityLangs( array &$languages,
string $group, array $cache ):
void {
360 $filterLangs = TranslateMetadata::get( $group,
'prioritylangs' );
361 if ( $filterLangs ===
false || strlen( $filterLangs ) === 0 ) {
365 $filter = array_flip( explode(
',', $filterLangs ) );
366 foreach ( $languages as $id => $code ) {
367 if ( isset( $filter[$code] ) ) {
370 $translated = $cache[$code][1];
371 if ( $translated === 0 ) {
372 unset( $languages[$id] );
377 private function makeRow(
string $code, array $cache ):
string {
378 $stats = $cache[$code];
379 $total = $stats[MessageGroupStats::TOTAL];
380 $translated = $stats[MessageGroupStats::TRANSLATED];
381 $fuzzy = $stats[MessageGroupStats::FUZZY];
383 if ( $total ===
null ) {
384 $this->incomplete =
true;
387 if ( $this->noComplete && $fuzzy === 0 && $translated === $total ) {
391 if ( $this->noEmpty && $translated === 0 && $fuzzy === 0 ) {
396 if ( $this->noEmpty && ( $translated / $total ) < 0.02 ) {
400 if ( $translated === $total ) {
401 $extra = [
'action' =>
'proofread' ];
406 $this->numberOfShownLanguages += 1;
407 $this->totals = MessageGroupStats::multiAdd( $this->totals, $stats );
410 if ( $this->numberOfShownLanguages % 2 === 0 ) {
411 $rowParams[
'class' ] =
'tux-statstable-even';
414 $out =
"\t" . Html::openElement(
'tr', $rowParams );
415 $out .=
"\n\t\t" . $this->getMainColumnCell( $code, $extra );
416 $out .= $this->table->makeNumberColumns( $stats );
417 $out .= $this->getWorkflowStateCell( $code );
419 $out .=
"\n\t" . Html::closeElement(
'tr' ) .
"\n";
424 private function getMainColumnCell(
string $code, array $params ):
string {
425 if ( !isset( $this->names ) ) {
426 $this->names = TranslateUtils::getLanguageNames( $this->getLanguage()->getCode() );
427 $this->translate = SpecialPage::getTitleFor(
'Translate' );
430 $queryParameters = $params + [
431 'group' => $this->target,
435 if ( isset( $this->names[$code] ) ) {
436 $text =
"$code: {$this->names[$code]}";
440 $link = $this->getLinkRenderer()->makeKnownLink(
447 return Html::rawElement(
'td', [], $link );
450 private function getWorkflowStates(): array {
451 $db = wfGetDB( DB_REPLICA );
453 'translate_groupreviews',
454 [
'tgr_state',
'tgr_lang' ],
455 [
'tgr_group' => $this->target ],
460 foreach ( $res as $row ) {
461 $states[$row->tgr_lang] = $row->tgr_state;
468 private function getGroupOptions(): array {
470 $groups = MessageGroups::getAllGroups();
472 foreach ( $groups as $id => $class ) {
473 if ( MessageGroups::getGroup( $id )->exists() ) {
474 $options[$class->getLabel()] = $id;