00001 <?php
00019 class MessageGroupStats {
00021 const TABLE = 'translate_groupstats';
00022
00023 const TOTAL = 0;
00024 const TRANSLATED = 1;
00025 const FUZZY = 2;
00026 const PROOFREAD = 3;
00027
00029 protected static $timeStart = null;
00031 protected static $limit = null;
00032
00039 public static function setTimeLimit( $limit ) {
00040 self::$timeStart = microtime( true );
00041 self::$limit = $limit;
00042 }
00043
00050 public static function getEmptyStats() {
00051 return array( 0, 0, 0, 0 );
00052 }
00053
00060 protected static function getUnknownStats() {
00061 return array( null, null, null, null );
00062 }
00063
00070 public static function forItem( $id, $code ) {
00071 $res = self::selectRowsIdLang( $id, $code );
00072 $stats = self::extractResults( $res );
00073
00074
00075
00076
00077 $group = MessageGroups::getGroup( $id );
00078 if ( MessageGroups::isDynamic( $group ) ) {
00079 $stats[$id][$code] = self::getUnknownStats();
00080 }
00081
00082 if ( !isset( $stats[$id][$code] ) ) {
00083 $stats[$id][$code] = self::forItemInternal( $stats, $group, $code );
00084 }
00085
00086 return $stats[$id][$code];
00087 }
00088
00094 public static function forLanguage( $code ) {
00095 $stats = self::forLanguageInternal( $code );
00096 $flattened = array();
00097 foreach ( $stats as $group => $languages ) {
00098 $flattened[$group] = $languages[$code];
00099 }
00100
00101 return $flattened;
00102 }
00103
00109 public static function forGroup( $id ) {
00110 $group = MessageGroups::getGroup( $id );
00111 if ( $group === null ) {
00112 return array();
00113 }
00114 $stats = self::forGroupInternal( $group );
00115
00116 return $stats[$id];
00117 }
00118
00125 public static function forEverything() {
00126 $groups = MessageGroups::singleton()->getGroups();
00127 $stats = array();
00128 foreach ( $groups as $g ) {
00129 $stats = self::forGroupInternal( $g, $stats );
00130 }
00131
00132 return $stats;
00133 }
00134
00141 public static function clear( MessageHandle $handle ) {
00142 $dbw = wfGetDB( DB_MASTER );
00143 $conds = array(
00144 'tgs_group' => $handle->getGroupIds(),
00145 'tgs_lang' => $handle->getCode(),
00146 );
00147
00148 $dbw->delete( self::TABLE, $conds, __METHOD__ );
00149 wfDebugLog( 'messagegroupstats', "Cleared " . serialize( $conds ) );
00150
00151
00152 return true;
00153 }
00154
00155 public static function clearGroup( $id ) {
00156 if ( !count( $id ) ) {
00157 return;
00158 }
00159 $dbw = wfGetDB( DB_MASTER );
00160 $conds = array( 'tgs_group' => $id );
00161 $dbw->delete( self::TABLE, $conds, __METHOD__ );
00162 wfDebugLog( 'messagegroupstats', "Cleared " . serialize( $conds ) );
00163 }
00164
00165 public static function clearLanguage( $code ) {
00166 if ( !count( $code ) ) {
00167 return;
00168 }
00169 $dbw = wfGetDB( DB_MASTER );
00170 $conds = array( 'tgs_lang' => $code );
00171 $dbw->delete( self::TABLE, $conds, __METHOD__ );
00172 wfDebugLog( 'messagegroupstats', "Cleared " . serialize( $conds ) );
00173 }
00174
00178 public static function clearAll() {
00179 $dbw = wfGetDB( DB_MASTER );
00180 $dbw->delete( self::TABLE, '*' );
00181 wfDebugLog( 'messagegroupstats', "Cleared everything :(" );
00182 }
00183
00184 protected static function extractResults( $res, $stats = array() ) {
00185 foreach ( $res as $row ) {
00186 $stats[$row->tgs_group][$row->tgs_lang] = self::extractNumbers( $row );
00187 }
00188
00189 return $stats;
00190 }
00191
00192 public static function update( MessageHandle $handle, $changes = array() ) {
00193 $dbw = wfGetDB( DB_MASTER );
00194 $conds = array(
00195 'tgs_group' => $handle->getGroupIds(),
00196 'tgs_lang' => $handle->getCode(),
00197 );
00198
00199 $values = array();
00200 foreach ( array( 'total', 'translated', 'fuzzy', 'proofread' ) as $type ) {
00201 if ( isset( $changes[$type] ) ) {
00202 $values[] = "tgs_$type=tgs_$type" .
00203 self::stringifyNumber( $changes[$type] );
00204 }
00205 }
00206
00207 $dbw->update( self::TABLE, $values, $conds, __METHOD__ );
00208 }
00209
00215 protected static function extractNumbers( $row ) {
00216 return array(
00217 self::TOTAL => (int)$row->tgs_total,
00218 self::TRANSLATED => (int)$row->tgs_translated,
00219 self::FUZZY => (int)$row->tgs_fuzzy,
00220 self::PROOFREAD => (int)$row->tgs_proofread,
00221 );
00222 }
00223
00229 protected static function forLanguageInternal( $code, $stats = array() ) {
00230 $res = self::selectRowsIdLang( null, $code );
00231 $stats = self::extractResults( $res, $stats );
00232
00233 $groups = MessageGroups::singleton()->getGroups();
00234 foreach ( $groups as $id => $group ) {
00235 if ( isset( $stats[$id][$code] ) ) {
00236 continue;
00237 }
00238 $stats[$id][$code] = self::forItemInternal( $stats, $group, $code );
00239 }
00240
00241 return $stats;
00242 }
00243
00248 protected static function expandAggregates( AggregateMessageGroup $agg ) {
00249 $flattened = array();
00250
00252 foreach ( $agg->getGroups() as $group ) {
00253 if ( $group instanceof AggregateMessageGroup ) {
00254 $flattened += self::expandAggregates( $group );
00255 } else {
00256 $flattened[$group->getId()] = $group;
00257 }
00258 }
00259
00260 return $flattened;
00261 }
00262
00268 protected static function forGroupInternal( $group, $stats = array() ) {
00269 $id = $group->getId();
00270 $res = self::selectRowsIdLang( $id, null );
00271 $stats = self::extractResults( $res, $stats );
00272
00273 # Go over each language filling missing entries
00274 $languages = array_keys( Language::getLanguageNames( false ) );
00275
00276 sort( $languages );
00277 foreach ( $languages as $code ) {
00278 if ( isset( $stats[$id][$code] ) ) {
00279 continue;
00280 }
00281 $stats[$id][$code] = self::forItemInternal( $stats, $group, $code );
00282 }
00283
00284
00285 foreach ( array_keys( $stats ) as $key ) {
00286 ksort( $stats[$key] );
00287 }
00288
00289 return $stats;
00290 }
00291
00292 protected static function selectRowsIdLang( $ids = null, $codes = null ) {
00293 $conds = array();
00294 if ( $ids !== null ) {
00295 $conds['tgs_group'] = $ids;
00296 }
00297
00298 if ( $codes !== null ) {
00299 $conds['tgs_lang'] = $codes;
00300 }
00301
00302 $dbr = wfGetDB( DB_MASTER );
00303 $res = $dbr->select( self::TABLE, '*', $conds, __METHOD__ );
00304
00305 return $res;
00306 }
00307
00308 protected static function forItemInternal( &$stats, $group, $code ) {
00309 $id = $group->getId();
00310
00311 if ( self::$timeStart !== null && ( microtime( true ) - self::$timeStart ) > self::$limit ) {
00312 return $stats[$id][$code] = self::getUnknownStats();
00313 }
00314
00315 if ( $group instanceof AggregateMessageGroup ) {
00316 $aggregates = self::getEmptyStats();
00317
00318 $expanded = self::expandAggregates( $group );
00319 if ( $expanded === array() ) {
00320 return $aggregates;
00321 }
00322 $res = self::selectRowsIdLang( array_keys( $expanded ), $code );
00323 $stats = self::extractResults( $res, $stats );
00324
00325 foreach ( $expanded as $sid => $subgroup ) {
00326 # Discouraged groups may belong to another group, usually if there
00327 # is an aggregate group for all translatable pages. In that case
00328 # calculate and store the statistics, but don't count them as part of
00329 # the aggregate group, so that the numbers in Special:LanguageStats
00330 # add up. The statistics for discouraged groups can still be viewed
00331 # through Special:MessageGroupStats.
00332 if ( !isset( $stats[$sid][$code] ) ) {
00333 $stats[$sid][$code] = self::forItemInternal( $stats, $subgroup, $code );
00334 }
00335
00336 $include = wfRunHooks( 'Translate:MessageGroupStats:isIncluded', array( $sid, $code ) );
00337 if ( $include ) {
00338 $aggregates = self::multiAdd( $aggregates, $stats[$sid][$code] );
00339 }
00340 }
00341 $stats[$id][$code] = $aggregates;
00342 } else {
00343 $aggregates = self::calculateGroup( $group, $code );
00344 }
00345
00346
00347 if ( $aggregates[self::TOTAL] === null ) {
00348 return $aggregates;
00349 }
00350
00351 $data = array(
00352 'tgs_group' => $id,
00353 'tgs_lang' => $code,
00354 'tgs_total' => $aggregates[self::TOTAL],
00355 'tgs_translated' => $aggregates[self::TRANSLATED],
00356 'tgs_fuzzy' => $aggregates[self::FUZZY],
00357 'tgs_proofread' => $aggregates[self::PROOFREAD],
00358 );
00359
00360 $dbw = wfGetDB( DB_MASTER );
00361 $dbw->insert(
00362 self::TABLE,
00363 $data,
00364 __METHOD__,
00365 array( 'IGNORE' )
00366 );
00367
00368 return $aggregates;
00369 }
00370
00371 public static function multiAdd( &$a, $b ) {
00372 if ( $a[0] === null || $b[0] === null ) {
00373 return array_fill( 0, count( $a ), null );
00374 }
00375 foreach ( $a as $i => &$v ) {
00376 $v += $b[$i];
00377 }
00378
00379 return $a;
00380 }
00381
00387 protected static function calculateGroup( $group, $code ) {
00388 global $wgTranslateDocumentationLanguageCode;
00389 # Calculate if missing and store in the db
00390 $collection = $group->initCollection( $code );
00391 $collection->setReviewMode( true );
00392
00393 if ( $code === $wgTranslateDocumentationLanguageCode ) {
00394 $ffs = $group->getFFS();
00395 if ( $ffs instanceof GettextFFS ) {
00396 $template = $ffs->read( 'en' );
00397 $infile = array();
00398 foreach ( $template['TEMPLATE'] as $key => $data ) {
00399 if ( isset( $data['comments']['.'] ) ) {
00400 $infile[$key] = '1';
00401 }
00402 }
00403 $collection->setInFile( $infile );
00404 }
00405 }
00406
00407 $collection->filter( 'ignored' );
00408 $collection->filter( 'optional' );
00409
00410 $total = count( $collection );
00411
00412
00413 $collection->filter( 'fuzzy' );
00414 $fuzzy = $total - count( $collection );
00415
00416
00417 $collection->filter( 'hastranslation', false );
00418 $translated = count( $collection );
00419
00420
00421
00422 $collection->filter( 'reviewer', false );
00423 $proofread = count( $collection );
00424
00425 return array(
00426 self::TOTAL => $total,
00427 self::TRANSLATED => $translated,
00428 self::FUZZY => $fuzzy,
00429 self::PROOFREAD => $proofread,
00430 );
00431 }
00432
00438 protected static function stringifyNumber( $number ) {
00439 $number = intval( $number );
00440
00441 return $number < 0 ? "$number" : "+$number";
00442 }
00443 }