00001 <?php
00023 class SpecialLanguageStats extends IncludableSpecialPage {
00027 protected $table;
00028
00032 protected $targetValueName = array( 'code', 'language' );
00033
00037 protected $totals;
00038
00044 protected $timelimit = 8;
00045
00050 protected $nothing = false;
00051
00056 protected $incomplete = false;
00057
00062 protected $noComplete = true;
00063
00068 protected $noEmpty = false;
00069
00073 protected $target;
00074
00079 protected $purge;
00080
00088 protected $statsCounted = array();
00089
00093 protected $states;
00094
00095 public function __construct() {
00096 parent::__construct( 'LanguageStats' );
00097
00098 $this->target = $this->getLanguage()->getCode();
00099 $this->totals = MessageGroupStats::getEmptyStats();
00100 }
00101
00102 function execute( $par ) {
00103 $request = $this->getRequest();
00104
00105 $this->purge = $request->getVal( 'action' ) === 'purge';
00106 $this->table = new StatsTable();
00107
00108 $this->setHeaders();
00109 $this->outputHeader();
00110
00111 $out = $this->getOutput();
00112
00113 $out->addModules( 'ext.translate.special.languagestats' );
00114 $out->addModuleStyles( 'ext.translate.messagetable' );
00115
00116 $params = explode( '/', $par );
00117
00118 if ( isset( $params[0] ) && trim( $params[0] ) ) {
00119 $this->target = $params[0];
00120 }
00121
00122 if ( isset( $params[1] ) ) {
00123 $this->noComplete = (bool)$params[1];
00124 }
00125
00126 if ( isset( $params[2] ) ) {
00127 $this->noEmpty = (bool)$params[2];
00128 }
00129
00130
00131 $submitted = !$this->including() && $request->getVal( 'x' ) === 'D';
00132
00133
00134 foreach ( $this->targetValueName as $key ) {
00135 $this->target = $request->getVal( $key, $this->target );
00136 }
00137 $this->noComplete = $request->getBool(
00138 'suppresscomplete',
00139 $this->noComplete && !$submitted
00140 );
00141 $this->noEmpty = $request->getBool( 'suppressempty', $this->noEmpty && !$submitted );
00142
00143 if ( !$this->including() ) {
00144 TranslateUtils::addSpecialHelpLink(
00145 $out,
00146 'Help:Extension:Translate/Statistics_and_reporting'
00147 );
00148 $out->addHTML( $this->getForm() );
00149 }
00150
00151 if ( $this->isValidValue( $this->target ) ) {
00152 $this->outputIntroduction();
00153 $output = $this->getTable();
00154 if ( $this->incomplete ) {
00155 $out->wrapWikiMsg(
00156 "<div class='error'>$1</div>",
00157 'translate-langstats-incomplete'
00158 );
00159 }
00160 if ( $this->nothing ) {
00161 $out->wrapWikiMsg( "<div class='error'>$1</div>", 'translate-mgs-nothing' );
00162 }
00163 $out->addHTML( $output );
00164 } elseif ( $submitted ) {
00165 $this->invalidTarget();
00166 }
00167 }
00168
00174 protected function isValidValue( $value ) {
00175 $langs = Language::getLanguageNames( false );
00176
00177 return isset( $langs[$value] );
00178 }
00179
00181 protected function invalidTarget() {
00182 $this->getOutput()->wrapWikiMsg(
00183 "<div class='error'>$1</div>",
00184 'translate-page-no-such-language'
00185 );
00186 }
00187
00193 protected function getForm() {
00194 global $wgScript;
00195
00196 $out = Html::openElement( 'div' );
00197 $out .= Html::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) );
00198 $out .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() );
00199 $out .= Html::hidden( 'x', 'D' );
00200 $out .= Html::openElement( 'fieldset' );
00201 $out .= Html::element(
00202 'legend',
00203 array(),
00204 $this->msg( 'translate-language-code' )->text()
00205 );
00206 $out .= Html::openElement( 'table' );
00207
00208 $out .= Html::openElement( 'tr' );
00209 $out .= Html::openElement( 'td', array( 'class' => 'mw-label' ) );
00210 $out .= Xml::label(
00211 $this->msg( 'translate-language-code-field-name' )->text(),
00212 'language'
00213 );
00214 $out .= Html::closeElement( 'td' );
00215 $out .= Html::openElement( 'td', array( 'class' => 'mw-input' ) );
00216 $out .= Xml::input( 'language', 10, $this->target, array( 'id' => 'language' ) );
00217 $out .= Html::closeElement( 'td' );
00218 $out .= Html::closeElement( 'tr' );
00219
00220 $out .= Html::openElement( 'tr' );
00221 $out .= Html::openElement( 'td', array( 'colspan' => 2 ) );
00222 $out .= Xml::checkLabel(
00223 $this->msg( 'translate-suppress-complete' )->text(),
00224 'suppresscomplete',
00225 'suppresscomplete',
00226 $this->noComplete
00227 );
00228 $out .= Html::closeElement( 'td' );
00229 $out .= Html::closeElement( 'tr' );
00230
00231 $out .= Html::openElement( 'tr' );
00232 $out .= Html::openElement( 'td', array( 'colspan' => 2 ) );
00233 $out .= Xml::checkLabel(
00234 $this->msg( 'translate-ls-noempty' )->text(),
00235 'suppressempty',
00236 'suppressempty',
00237 $this->noEmpty
00238 );
00239 $out .= Html::closeElement( 'td' );
00240 $out .= Html::closeElement( 'tr' );
00241
00242 $out .= Html::openElement( 'tr' );
00243 $out .= Html::openElement( 'td', array( 'class' => 'mw-input', 'colspan' => 2 ) );
00244 $out .= Xml::submitButton( $this->msg( 'translate-ls-submit' )->text() );
00245 $out .= Html::closeElement( 'td' );
00246 $out .= Html::closeElement( 'tr' );
00247
00248 $out .= Html::closeElement( 'table' );
00249 $out .= Html::closeElement( 'fieldset' );
00250
00251
00252
00253 $val = $this->getRequest()->getVal( 'group' );
00254 if ( $val !== null ) {
00255 $out .= Html::hidden( 'group', $val );
00256 }
00257 $out .= Html::closeElement( 'form' );
00258 $out .= Html::closeElement( 'div' );
00259
00260 return $out;
00261 }
00262
00266 protected function outputIntroduction() {
00267 $languageName = TranslateUtils::getLanguageName(
00268 $this->target,
00269 $this->getLanguage()->getCode()
00270 );
00271 $task = $this->getUser()->isAllowed( 'translate-messagereview' ) ?
00272 'acceptqueue' :
00273 'reviewall';
00274 $rcInLangLink = Linker::link(
00275 SpecialPage::getTitleFor( 'Translate', '!recent' ),
00276 $this->msg( 'languagestats-recenttranslations' )->escaped(),
00277 array(),
00278 array(
00279 'task' => $task,
00280 'language' => $this->target
00281 )
00282 );
00283
00284 $out = $this->msg( 'languagestats-stats-for', $languageName )->rawParams( $rcInLangLink )
00285 ->parseAsBlock();
00286 $this->getOutput()->addHTML( $out );
00287 }
00288
00292 function addWorkflowStatesColumn() {
00293 global $wgTranslateWorkflowStates;
00294
00295 if ( $wgTranslateWorkflowStates ) {
00296 $this->states = $this->getWorkflowStates();
00297
00298
00299 $this->table->addExtraColumn( $this->msg( 'translate-stats-workflow' ) );
00300 }
00301
00302 return;
00303 }
00304
00305 protected function getWorkflowStateValue( $target ) {
00306 return isset( $this->states[$target] ) ? $this->states[$target] : '';
00307 }
00308
00315 protected function getWorkflowStateCell( $target, $state ) {
00316
00317 if ( !isset( $this->states ) ) {
00318 return '';
00319 }
00320
00321 if ( $state === '' ) {
00322 return "\n\t\t" . $this->table->element( '', '', -1 );
00323 }
00324
00325 if ( $this instanceof SpecialMessageGroupStats ) {
00326
00327 $group = MessageGroups::getGroup( $this->target );
00328 $stateConfig = $group->getMessageGroupStates()->getStates();
00329 } else {
00330
00331 $group = MessageGroups::getGroup( $target );
00332 $stateConfig = $group->getMessageGroupStates()->getStates();
00333 }
00334
00335 $sortValue = -1;
00336 $stateColor = '';
00337 if ( isset( $stateConfig[$state] ) ) {
00338 $sortIndex = array_flip( array_keys( $stateConfig ) );
00339 $sortValue = $sortIndex[$state] + 1;
00340
00341 if ( is_string( $stateConfig[$state] ) ) {
00342
00343 $stateColor = $stateConfig[$state];
00344 } elseif ( isset( $stateConfig[$state]['color'] ) ) {
00345 $stateColor = $stateConfig[$state]['color'];
00346 }
00347 }
00348
00349 $stateMessage = $this->msg( "translate-workflow-state-$state" );
00350 $stateText = $stateMessage->isBlank() ? $state : $stateMessage->text();
00351
00352 return "\n\t\t" . $this->table->element(
00353 $stateText,
00354 $stateColor,
00355 $sortValue
00356 );
00357 }
00358
00363 function getTable() {
00364 $table = $this->table;
00365
00366 $this->addWorkflowStatesColumn();
00367 $out = '';
00368
00369 if ( $this->purge ) {
00370 MessageGroupStats::clearLanguage( $this->target );
00371 }
00372
00373 MessageGroupStats::setTimeLimit( $this->timelimit );
00374 $cache = MessageGroupStats::forLanguage( $this->target );
00375
00376 $structure = MessageGroups::getGroupStructure();
00377 foreach ( $structure as $item ) {
00378 $out .= $this->makeGroupGroup( $item, $cache );
00379 }
00380
00381 if ( $out ) {
00382 $table->setMainColumnHeader( $this->msg( 'translate-ls-column-group' ) );
00383 $out = $table->createHeader() . "\n" . $out;
00384 $out .= Html::closeElement( 'tbody' );
00385
00386 $out .= Html::openElement( 'tfoot' );
00387 $out .= $table->makeTotalRow(
00388 $this->msg( 'translate-languagestats-overall' ),
00389 $this->totals
00390 );
00391 $out .= Html::closeElement( 'tfoot' );
00392
00393 $out .= Html::closeElement( 'table' );
00394
00395 return $out;
00396 } else {
00397 $this->nothing = true;
00398
00399 return '';
00400 }
00401
00407 }
00408
00419 protected function makeGroupGroup( $item, array $cache, MessageGroup $parent = null ) {
00420 if ( !is_array( $item ) ) {
00421 return $this->makeGroupRow( $item, $cache, $parent );
00422 }
00423
00424
00425 $out = '';
00426 $top = array_shift( $item );
00427 $out .= $this->makeGroupRow( $top, $cache, $parent );
00428
00429
00430 foreach ( $item as $subgroup ) {
00431 $out .= $this->makeGroupGroup( $subgroup, $cache, $top );
00432 }
00433
00434 return $out;
00435 }
00436
00445 protected function makeGroupRow( MessageGroup $group, array $cache,
00446 MessageGroup $parent = null
00447 ) {
00448 $groupId = $group->getId();
00449
00450 if ( $this->table->isBlacklisted( $groupId, $this->target ) !== null ) {
00451 return '';
00452 }
00453
00454 $stats = $cache[$groupId];
00455 $total = $stats[MessageGroupStats::TOTAL];
00456 $translated = $stats[MessageGroupStats::TRANSLATED];
00457 $fuzzy = $stats[MessageGroupStats::FUZZY];
00458
00459
00460 if ( $this->noComplete && $fuzzy === 0 && $translated === $total ) {
00461 return '';
00462 }
00463 if ( $this->noEmpty && $translated === 0 && $fuzzy === 0 ) {
00464 return '';
00465 }
00466
00467
00468 if ( !$group instanceof AggregateMessageGroup ) {
00469 if ( !isset( $this->statsCounted[$groupId] ) ) {
00470 $this->totals = MessageGroupStats::multiAdd( $this->totals, $stats );
00471 $this->statsCounted[$groupId] = true;
00472 }
00473 }
00474
00475 $state = $this->getWorkflowStateValue( $groupId );
00476
00477 $params = $stats;
00478 $params[] = $state;
00479 $params[] = $groupId;
00480 $params[] = $this->getLanguage()->getCode();
00481 $params[] = $this->target;
00482 $cachekey = wfMemcKey( __METHOD__, implode( '-', $params ) );
00483 $cacheval = wfGetCache( CACHE_ANYTHING )->get( $cachekey );
00484 if ( !$this->purge && is_string( $cacheval ) ) {
00485 return $cacheval;
00486 }
00487
00488 $extra = array();
00489 if ( $total === null ) {
00490 $this->incomplete = true;
00491 } elseif ( $translated === $total ) {
00492 $extra = array( 'task' => 'reviewall' );
00493 }
00494
00495 $rowParams = array();
00496 $rowParams['data-groupid'] = $groupId;
00497 $rowParams['class'] = get_class( $group );
00498 if ( $parent ) {
00499 $rowParams['data-parentgroup'] = $parent->getId();
00500 }
00501
00502 $out = "\t" . Html::openElement( 'tr', $rowParams );
00503 $out .= "\n\t\t" . Html::rawElement( 'td', array(),
00504 $this->table->makeGroupLink( $group, $this->target, $extra ) );
00505 $out .= $this->table->makeNumberColumns( $stats );
00506 $out .= $this->getWorkflowStateCell( $groupId, $state );
00507 $out .= "\n\t" . Html::closeElement( 'tr' ) . "\n";
00508
00509 wfGetCache( CACHE_ANYTHING )->set( $cachekey, $out, 3600 * 24 );
00510
00511 return $out;
00512 }
00513
00514 protected function getWorkflowStates( $field = 'tgr_group', $filter = 'tgr_lang' ) {
00515 $db = wfGetDB( DB_SLAVE );
00516 $res = $db->select(
00517 'translate_groupreviews',
00518 array( 'tgr_state', $field ),
00519 array( $filter => $this->target ),
00520 __METHOD__
00521 );
00522
00523 $states = array();
00524 foreach ( $res as $row ) {
00525 $states[$row->$field] = $row->tgr_state;
00526 }
00527
00528 return $states;
00529 }
00530 }