00001 <?php
00022 class SpecialTranslationStats extends IncludableSpecialPage {
00023 public function __construct() {
00024 parent::__construct( 'TranslationStats' );
00025 }
00026
00028 protected static $graphs = array(
00029 'edits' => 'TranslatePerLanguageStats',
00030 'users' => 'TranslatePerLanguageStats',
00031 'registrations' => 'TranslateRegistrationStats',
00032 'reviews' => 'ReviewPerLanguageStats',
00033 'reviewers' => 'ReviewPerLanguageStats',
00034 );
00035
00040 public function getGraphTypes() {
00041 return array_keys( self::$graphs );
00042 }
00043
00049 public function getGraphClass( $type ) {
00050 return self::$graphs[$type];
00051 }
00052
00053 public function execute( $par ) {
00054 $this->getOutput()->addModules( 'ext.translate.special.translationstats' );
00055
00056 $opts = new FormOptions();
00057 $opts->add( 'graphit', false );
00058 $opts->add( 'preview', false );
00059 $opts->add( 'language', '' );
00060 $opts->add( 'count', 'edits' );
00061 $opts->add( 'scale', 'days' );
00062 $opts->add( 'days', 30 );
00063 $opts->add( 'width', 600 );
00064 $opts->add( 'height', 400 );
00065 $opts->add( 'group', '' );
00066 $opts->add( 'uselang', '' );
00067 $opts->add( 'start', '' );
00068 $opts->fetchValuesFromRequest( $this->getRequest() );
00069
00070 $pars = explode( ';', $par );
00071
00072 foreach ( $pars as $item ) {
00073 if ( strpos( $item, '=' ) === false ) {
00074 continue;
00075 }
00076
00077 list( $key, $value ) = array_map( 'trim', explode( '=', $item, 2 ) );
00078 if ( isset( $opts[$key] ) ) {
00079 $opts[$key] = $value;
00080 }
00081 }
00082
00083 $opts->validateIntBounds( 'days', 1, 10000 );
00084 $opts->validateIntBounds( 'width', 200, 1000 );
00085 $opts->validateIntBounds( 'height', 200, 1000 );
00086
00087 if ( $opts['start'] !== '' ) {
00088 $opts['start'] = strval( wfTimestamp( TS_MW, $opts['start'] ) );
00089 }
00090
00091 $validScales = array( 'months', 'weeks', 'days', 'hours' );
00092 if ( !in_array( $opts['scale'], $validScales ) ) {
00093 $opts['scale'] = 'days';
00094 }
00095
00096 if ( $opts['scale'] === 'hours' ) {
00097 $opts->validateIntBounds( 'days', 1, 4 );
00098 }
00099
00100 $validCounts = $this->getGraphTypes();
00101 if ( !in_array( $opts['count'], $validCounts ) ) {
00102 $opts['count'] = 'edits';
00103 }
00104
00105 foreach ( array( 'group', 'language' ) as $t ) {
00106 $values = array_map( 'trim', explode( ',', $opts[$t] ) );
00107 $values = array_splice( $values, 0, 4 );
00108 if ( $t === 'group' ) {
00109
00110 $values = preg_replace( '~^page_~', 'page-', $values );
00111 }
00112 $opts[$t] = implode( ',', $values );
00113 }
00114
00115 if ( $this->including() ) {
00116 $this->getOutput()->addHTML( $this->image( $opts ) );
00117 } elseif ( $opts['graphit'] ) {
00118
00119 if ( !class_exists( 'PHPlot' ) ) {
00120 header( "HTTP/1.0 500 Multi fail" );
00121 echo "PHPlot not found";
00122 }
00123
00124 if ( !$this->getRequest()->getBool( 'debug' ) ) {
00125 $this->getOutput()->disable();
00126 header( 'Content-Type: image/png' );
00127 header( 'Cache-Control: private, max-age=3600' );
00128 header( 'Expires: ' . wfTimestamp( TS_RFC2822, time() + 3600 ) );
00129 }
00130 $this->draw( $opts );
00131 } else {
00132 $this->form( $opts );
00133 }
00134 }
00135
00140 protected function form( FormOptions $opts ) {
00141 global $wgScript;
00142
00143 $this->setHeaders();
00144 $out = $this->getOutput();
00145 TranslateUtils::addSpecialHelpLink( $out, 'Help:Extension:Translate/Statistics_and_reporting' );
00146 $out->addWikiMsg( 'translate-statsf-intro' );
00147
00148 $out->addHTML(
00149 Xml::fieldset( $this->msg( 'translate-statsf-options' )->text() ) .
00150 Html::openElement( 'form', array( 'action' => $wgScript ) ) .
00151 Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) .
00152 Html::hidden( 'preview', 1 ) .
00153 '<table>'
00154 );
00155
00156 $submit = Xml::submitButton( $this->msg( 'translate-statsf-submit' )->text() );
00157
00158 $out->addHTML(
00159 $this->eInput( 'width', $opts ) .
00160 $this->eInput( 'height', $opts ) .
00161 '<tr><td colspan="2"><hr /></td></tr>' .
00162 $this->eInput( 'start', $opts, 16 ) .
00163 $this->eInput( 'days', $opts ) .
00164 $this->eRadio( 'scale', $opts, array( 'months', 'weeks', 'days', 'hours' ) ) .
00165 $this->eRadio( 'count', $opts, $this->getGraphTypes() ) .
00166 '<tr><td colspan="2"><hr /></td></tr>' .
00167 $this->eLanguage( 'language', $opts ) .
00168 $this->eGroup( 'group', $opts ) .
00169 '<tr><td colspan="2"><hr /></td></tr>' .
00170 '<tr><td colspan="2">' . $submit . '</td></tr>'
00171 );
00172
00173 $out->addHTML(
00174 '</table>' .
00175 '</form>' .
00176 '</fieldset>'
00177 );
00178
00179 if ( !$opts['preview'] ) {
00180 return;
00181 }
00182
00183 $spiParams = '';
00184 foreach ( $opts->getChangedValues() as $key => $v ) {
00185 if ( $key === 'preview' ) {
00186 continue;
00187 }
00188
00189 if ( $spiParams !== '' ) {
00190 $spiParams .= ';';
00191 }
00192
00193 $spiParams .= wfEscapeWikiText( "$key=$v" );
00194 }
00195
00196 if ( $spiParams !== '' ) {
00197 $spiParams = '/' . $spiParams;
00198 }
00199
00200 $titleText = $this->getTitle()->getPrefixedText();
00201
00202 $out->addHTML(
00203 Html::element( 'hr' ) .
00204 Html::element( 'pre', array(), "{{{$titleText}{$spiParams}}}" )
00205 );
00206
00207 $out->addHTML(
00208 Html::element( 'hr' ) .
00209 Html::rawElement(
00210 'div',
00211 array( 'style' => 'margin: 1em auto; text-align: center;' ),
00212 $this->image( $opts )
00213 )
00214 );
00215 }
00216
00224 protected function eInput( $name, FormOptions $opts, $width = 4 ) {
00225 $value = $opts[$name];
00226
00227 return
00228 '<tr><td>' . $this->eLabel( $name ) . '</td><td>' .
00229 Xml::input( $name, $width, $value, array( 'id' => $name ) ) .
00230 '</td></tr>' . "\n";
00231 }
00232
00238 protected function eLabel( $name ) {
00239
00240
00241
00242
00243 $label = 'translate-statsf-' . $name;
00244 $label = $this->msg( $label )->escaped();
00245
00246 return Xml::tags( 'label', array( 'for' => $name ), $label );
00247 }
00248
00256 protected function eRadio( $name, FormOptions $opts, array $alts ) {
00257
00258
00259 $label = 'translate-statsf-' . $name;
00260 $label = $this->msg( $label )->escaped();
00261 $s = '<tr><td>' . $label . '</td><td>';
00262
00263 $options = array();
00264 foreach ( $alts as $alt ) {
00265 $id = "$name-$alt";
00266 $radio = Xml::radio( $name, $alt, $alt === $opts[$name],
00267 array( 'id' => $id ) ) . ' ';
00268 $options[] = $radio . ' ' . $this->eLabel( $id );
00269 }
00270
00271 $s .= implode( ' ', $options );
00272 $s .= '</td></tr>' . "\n";
00273
00274 return $s;
00275 }
00276
00283 protected function eLanguage( $name, FormOptions $opts ) {
00284 $value = $opts[$name];
00285
00286 $select = $this->languageSelector();
00287 $select->setTargetId( 'language' );
00288
00289 return
00290 '<tr><td>' . $this->eLabel( $name ) . '</td><td>' .
00291 $select->getHtmlAndPrepareJs() . '<br />' .
00292 Xml::input( $name, 20, $value, array( 'id' => $name ) ) .
00293 '</td></tr>' . "\n";
00294 }
00295
00300 protected function languageSelector() {
00301 if ( is_callable( array( 'LanguageNames', 'getNames' ) ) ) {
00302 $languages = LanguageNames::getNames( $this->getLanguage()->getCode(),
00303 LanguageNames::FALLBACK_NORMAL,
00304 LanguageNames::LIST_MW_AND_CLDR
00305 );
00306 } else {
00307 $languages = Language::getLanguageNames( false );
00308 }
00309
00310 ksort( $languages );
00311
00312 $selector = new XmlSelect( 'mw-language-selector', 'mw-language-selector' );
00313 foreach ( $languages as $code => $name ) {
00314 $selector->addOption( "$code - $name", $code );
00315 }
00316
00317 $jsSelect = new JsSelectToInput( $selector );
00318 $jsSelect->setSourceId( 'mw-language-selector' );
00319
00320 return $jsSelect;
00321 }
00322
00329 protected function eGroup( $name, FormOptions $opts ) {
00330 $value = $opts[$name];
00331
00332 $select = $this->groupSelector();
00333 $select->setTargetId( 'group' );
00334
00335 return
00336 '<tr><td>' . $this->eLabel( $name ) . '</td><td>' .
00337 $select->getHtmlAndPrepareJs() . '<br />' .
00338 Xml::input( $name, 20, $value, array( 'id' => $name ) ) .
00339 '</td></tr>' . "\n";
00340 }
00341
00346 protected function groupSelector() {
00347 $groups = MessageGroups::singleton()->getGroups();
00351 foreach ( $groups as $key => $group ) {
00352 if ( !$group->exists() ) {
00353 unset( $groups[$key] );
00354 continue;
00355 }
00356 }
00357
00358 ksort( $groups );
00359
00360 $selector = new XmlSelect( 'mw-group-selector', 'mw-group-selector' );
00364 foreach ( $groups as $code => $name ) {
00365 $selector->addOption( $name->getLabel(), $code );
00366 }
00367
00368 $jsSelect = new JsSelectToInput( $selector );
00369 $jsSelect->setSourceId( 'mw-group-selector' );
00370
00371 return $jsSelect;
00372 }
00373
00379 protected function image( FormOptions $opts ) {
00380 $title = $this->getTitle();
00381 $cgiparams = wfArrayToCgi( array( 'graphit' => true ), $opts->getAllValues() );
00382 $href = $title->getLocalUrl( $cgiparams );
00383
00384 return Xml::element( 'img',
00385 array(
00386 'src' => $href,
00387 'width' => $opts['width'],
00388 'height' => $opts['height'],
00389 )
00390 );
00391 }
00392
00398 protected function getData( FormOptions $opts ) {
00399 $dbr = wfGetDB( DB_SLAVE );
00400
00401 $class = $this->getGraphClass( $opts['count'] );
00402 $so = new $class( $opts );
00403
00404 $fixedStart = $opts->getValue( 'start' ) !== '';
00405
00406 $now = time();
00407 $period = 3600 * 24 * $opts->getValue( 'days' );
00408
00409 if ( $fixedStart ) {
00410 $cutoff = wfTimestamp( TS_UNIX, $opts->getValue( 'start' ) );
00411 } else {
00412 $cutoff = $now - $period;
00413 }
00414 $cutoff = self::roundTimestampToCutoff( $opts['scale'], $cutoff, 'earlier' );
00415
00416 $start = $cutoff;
00417
00418 if ( $fixedStart ) {
00419 $end = self::roundTimestampToCutoff( $opts['scale'], $start + $period, 'later' ) - 1;
00420 } else {
00421 $end = null;
00422 }
00423
00424 $tables = array();
00425 $fields = array();
00426 $conds = array();
00427 $type = __METHOD__;
00428 $options = array();
00429
00430 $so->preQuery( $tables, $fields, $conds, $type, $options, $start, $end );
00431 $res = $dbr->select( $tables, $fields, $conds, $type, $options );
00432 wfDebug( __METHOD__ . "-queryend\n" );
00433
00434
00435 $dateFormat = $so->getDateFormat();
00436 $increment = self::getIncrement( $opts['scale'] );
00437
00438 $labels = $so->labels();
00439 $keys = array_keys( $labels );
00440 $values = array_pad( array(), count( $labels ), 0 );
00441 $defaults = array_combine( $keys, $values );
00442
00443 $data = array();
00444
00445 $lastValue = $end !== null ? $end : $now + 10;
00446 $lang = $this->getLanguage();
00447 while ( $cutoff <= $lastValue ) {
00448 $date = $lang->sprintfDate( $dateFormat, wfTimestamp( TS_MW, $cutoff ) );
00449 $cutoff += $increment;
00450 $data[$date] = $defaults;
00451 }
00452
00453
00454 $labelToIndex = array_flip( $labels );
00455
00456 foreach ( $res as $row ) {
00457 $indexLabels = $so->indexOf( $row );
00458 if ( $indexLabels === false ) {
00459 continue;
00460 }
00461
00462 foreach ( (array)$indexLabels as $i ) {
00463 if ( !isset( $labelToIndex[$i] ) ) {
00464 continue;
00465 }
00466 $date = $lang->sprintfDate( $dateFormat, $so->getTimestamp( $row ) );
00467
00468 if ( !isset( $data[$date] ) ) {
00469 continue;
00470 }
00471
00472 $data[$date][$labelToIndex[$i]]++;
00473 }
00474 }
00475
00476
00477 if ( count( $labels ) === 1 && $labels[0] === 'all' ) {
00478 $labels = array();
00479 }
00480
00481 foreach ( $labels as &$label ) {
00482 if ( strpos( $label, '@' ) === false ) {
00483 continue;
00484 }
00485 list( $groupId, $code ) = explode( '@', $label, 2 );
00486 if ( $code && $groupId ) {
00487 $code = TranslateUtils::getLanguageName( $code, $lang->getCode() ) . " ($code)";
00488 $group = MessageGroups::getGroup( $groupId );
00489 $group = $group ? $group->getLabel() : $groupId;
00490 $label = "$group @ $code";
00491 } elseif ( $code ) {
00492 $label = TranslateUtils::getLanguageName( $code, $lang->getCode() ) . " ($code)";
00493 } elseif ( $groupId ) {
00494 $group = MessageGroups::getGroup( $groupId );
00495 $label = $group ? $group->getLabel() : $groupId;
00496 }
00497 }
00498
00499 if ( $end == null ) {
00500 $last = array_splice( $data, -1, 1 );
00501
00502 $data[key( $last ) . '*'] = current( $last );
00503 }
00504
00505 return array( $labels, $data );
00506 }
00507
00516 protected static function roundTimestampToCutoff( $scale, $cutoff, $direction = 'earlier' ) {
00517 $dir = $direction === 'earlier' ? -1 : 1;
00518
00519
00520
00521 if ( $scale === 'hours' ) {
00522 $cutoff += self::roundingAddition( $cutoff, 3600, $dir );
00523 } elseif ( $scale === 'days' ) {
00524 $cutoff += self::roundingAddition( $cutoff, 86400, $dir );
00525 } elseif ( $scale === 'weeks' ) {
00526
00527
00528 while ( date( 'D', $cutoff ) !== "Mon" ) {
00529 $cutoff += $dir * 86400;
00530 }
00531
00532 $cutoff -= ( $cutoff % 86400 );
00533 } elseif ( $scale === 'months' ) {
00534
00535 while ( date( 'j', $cutoff ) !== "1" ) {
00536 $cutoff += $dir * 86400;
00537 }
00538
00539 $cutoff -= ( $cutoff % 86400 );
00540 }
00541
00542 return $cutoff;
00543 }
00544
00551 protected static function roundingAddition( $ts, $amount, $dir ) {
00552 if ( $dir === -1 ) {
00553 return -1 * ( $ts % $amount );
00554 } else {
00555 return $amount - ( $ts % $amount );
00556 }
00557 }
00558
00563 public function draw( FormOptions $opts ) {
00564 global $wgTranslatePHPlotFont;
00565
00566 $width = $opts->getValue( 'width' );
00567 $height = $opts->getValue( 'height' );
00568
00569 $plot = new PHPlot( $width, $height );
00570
00571 list( $legend, $resData ) = $this->getData( $opts );
00572 $count = count( $resData );
00573 $skip = intval( $count / ( $width / 60 ) - 1 );
00574 $i = $count;
00575 $data = array();
00576
00577 foreach ( $resData as $date => $edits ) {
00578 if ( $skip > 0 ) {
00579 if ( ( $count - $i ) % $skip !== 0 ) {
00580 $date = '';
00581 }
00582 }
00583
00584 if ( strpos( $date, ';' ) !== false ) {
00585 list( , $date ) = explode( ';', $date, 2 );
00586 }
00587
00588 array_unshift( $edits, $date );
00589 $data[] = $edits;
00590 $i--;
00591 }
00592
00593 $font = FCFontFinder::findFile( $this->getLanguage()->getCode() );
00594
00595 if ( $font ) {
00596 $plot->SetDefaultTTFont( $font );
00597 } else {
00598 $plot->SetDefaultTTFont( $wgTranslatePHPlotFont );
00599 }
00600 $plot->SetDataValues( $data );
00601
00602 if ( $legend !== null ) {
00603 $plot->SetLegend( $legend );
00604 }
00605
00606 $numberFont = FCFontFinder::findFile( 'en' );
00607
00608 $plot->setFont( 'x_label', $numberFont, 8 );
00609 $plot->setFont( 'y_label', $numberFont, 8 );
00610
00611
00612
00613 $yTitle = $this->msg( 'translate-stats-' . $opts['count'] )->escaped();
00614
00615
00616 $plot->SetYTitle( $yTitle );
00617 $plot->SetXTickLabelPos( 'none' );
00618 $plot->SetXTickPos( 'none' );
00619 $plot->SetXLabelAngle( 45 );
00620
00621 $max = max( array_map( 'max', $resData ) );
00622 $max = self::roundToSignificant( $max, 1 );
00623 $max = round( $max, intval( -log( $max, 10 ) ) );
00624
00625 $yTick = 10;
00626 while ( $max / $yTick > $height / 20 ) {
00627 $yTick *= 2;
00628 }
00629
00630
00631 $yTick = min( $max, $yTick );
00632 $yTick = self::roundToSignificant( $yTick );
00633 $plot->SetYTickIncrement( $yTick );
00634 $plot->SetPlotAreaWorld( null, 0, null, $max );
00635
00636 $plot->SetTransparentColor( 'white' );
00637 $plot->SetBackgroundColor( 'white' );
00638
00639
00640 $plot->DrawGraph();
00641 }
00642
00655 public static function roundToSignificant( $number, $significant = 1 ) {
00656 $log = (int)log( $number, 10 );
00657 $nonSignificant = max( 0, $log - $significant + 1 );
00658 $factor = pow( 10, $nonSignificant );
00659
00660 return intval( ceil( $number / $factor ) * $factor );
00661 }
00662
00671 public static function getIncrement( $scale ) {
00672 $increment = 3600 * 24;
00673 if ( $scale === 'months' ) {
00674
00675
00676 $increment = 3600 * 24 * 15;
00677 } elseif ( $scale === 'weeks' ) {
00678 $increment = 3600 * 24 * 7;
00679 } elseif ( $scale === 'hours' ) {
00680 $increment = 3600;
00681 }
00682
00683 return $increment;
00684 }
00685 }
00686
00692 interface TranslationStatsInterface {
00698 public function __construct( FormOptions $opts );
00699
00710 public function preQuery( &$tables, &$fields, &$conds, &$type, &$options, $start, $end );
00711
00717 public function indexOf( $row );
00718
00725 public function labels();
00726
00732 public function getTimestamp( $row );
00733
00739 public function getDateFormat();
00740 }
00741
00746 abstract class TranslationStatsBase implements TranslationStatsInterface {
00750 protected $opts;
00751
00752 public function __construct( FormOptions $opts ) {
00753 $this->opts = $opts;
00754 }
00755
00756 public function indexOf( $row ) {
00757 return array( 'all' );
00758 }
00759
00760 public function labels() {
00761 return array( 'all' );
00762 }
00763
00764 public function getDateFormat() {
00765 $dateFormat = 'Y-m-d';
00766 if ( $this->opts['scale'] === 'months' ) {
00767 $dateFormat = 'Y-m';
00768 } elseif ( $this->opts['scale'] === 'weeks' ) {
00769 $dateFormat = 'Y-\WW';
00770 } elseif ( $this->opts['scale'] === 'hours' ) {
00771 $dateFormat .= ';H';
00772 }
00773
00774 return $dateFormat;
00775 }
00776
00777 protected static function makeTimeCondition( $field, $start, $end ) {
00778 $db = wfGetDB( DB_SLAVE );
00779
00780 $conds = array();
00781 if ( $start !== null ) {
00782 $conds[] = "$field >= '{$db->timestamp( $start )}'";
00783 }
00784 if ( $end !== null ) {
00785 $conds[] = "$field <= '{$db->timestamp( $end )}'";
00786 }
00787
00788 return $conds;
00789 }
00790
00792 protected static function namespacesFromGroups( $groupIds ) {
00793 $namespaces = array();
00794 foreach ( $groupIds as $id ) {
00795 $group = MessageGroups::getGroup( $id );
00796 if ( $group ) {
00797 $namespace = $group->getNamespace();
00798 $namespaces[$namespace] = true;
00799 }
00800 }
00801
00802 return array_keys( $namespaces );
00803 }
00804 }
00805
00810 class TranslatePerLanguageStats extends TranslationStatsBase {
00812 protected $usercache;
00813
00814 protected $codes, $groups;
00815
00816 public function __construct( FormOptions $opts ) {
00817 parent::__construct( $opts );
00818
00819 $opts->validateIntBounds( 'days', 1, 200 );
00820 }
00821
00822 public function preQuery( &$tables, &$fields, &$conds, &$type, &$options, $start, $end ) {
00823 global $wgTranslateMessageNamespaces;
00824
00825 $db = wfGetDB( DB_SLAVE );
00826
00827 $tables = array( 'recentchanges' );
00828 $fields = array( 'rc_timestamp' );
00829
00830 $conds = array(
00831 'rc_namespace' => $wgTranslateMessageNamespaces,
00832 'rc_bot' => 0,
00833 'rc_type != ' . RC_LOG,
00834 );
00835
00836 $timeConds = self::makeTimeCondition( 'rc_timestamp', $start, $end );
00837 $conds = array_merge( $conds, $timeConds );
00838
00839 $options = array( 'ORDER BY' => 'rc_timestamp' );
00840
00841 $this->groups = array_filter( array_map( 'trim', explode( ',', $this->opts['group'] ) ) );
00842 $this->codes = array_filter( array_map( 'trim', explode( ',', $this->opts['language'] ) ) );
00843
00844 $namespaces = self::namespacesFromGroups( $this->groups );
00845 if ( count( $namespaces ) ) {
00846 $conds['rc_namespace'] = $namespaces;
00847 }
00848
00849 $languages = array();
00850 foreach ( $this->codes as $code ) {
00851 $languages[] = 'rc_title ' . $db->buildLike( $db->anyString(), "/$code" );
00852 }
00853 if ( count( $languages ) ) {
00854 $conds[] = $db->makeList( $languages, LIST_OR );
00855 }
00856
00857 $fields[] = 'rc_title';
00858
00859 if ( $this->groups ) {
00860 $fields[] = 'rc_namespace';
00861 }
00862
00863 if ( $this->opts['count'] === 'users' ) {
00864 $fields[] = 'rc_user_text';
00865 }
00866
00867 $type .= '-perlang';
00868 }
00869
00870 public function indexOf( $row ) {
00871
00872 if ( $this->opts['count'] === 'users' ) {
00873 $date = $this->formatTimestamp( $row->rc_timestamp );
00874
00875 if ( isset( $this->usercache[$date][$row->rc_user_text] ) ) {
00876 return -1;
00877 } else {
00878 $this->usercache[$date][$row->rc_user_text] = 1;
00879 }
00880 }
00881
00882
00883 if ( strpos( $row->rc_title, '/' ) === false ) {
00884 return false;
00885 }
00886
00887
00888 if ( !$this->groups && !$this->codes ) {
00889 return 'all';
00890 }
00891
00892
00893 list( $key, $code ) = TranslateUtils::figureMessage( $row->rc_title );
00894
00895 $groups = array();
00896 $codes = array();
00897
00898 if ( $this->groups ) {
00899
00900
00901
00902
00903 $groups = TranslateUtils::messageKeyToGroups( $row->rc_namespace, $key );
00904 $groups = array_intersect( $this->groups, $groups );
00905 }
00906
00907 if ( $this->codes ) {
00908 $codes = array( $code );
00909 }
00910
00911 return $this->combineTwoArrays( $groups, $codes );
00912 }
00913
00914 public function labels() {
00915 return $this->combineTwoArrays( $this->groups, $this->codes );
00916 }
00917
00918 public function getTimestamp( $row ) {
00919 return $row->rc_timestamp;
00920 }
00921
00929 protected function makeLabel( $group, $code ) {
00930 if ( $group || $code ) {
00931 return "$group@$code";
00932 } else {
00933 return 'all';
00934 }
00935 }
00936
00944 protected function combineTwoArrays( $groups, $codes ) {
00945 if ( !count( $groups ) ) {
00946 $groups[] = false;
00947 }
00948
00949 if ( !count( $codes ) ) {
00950 $codes[] = false;
00951 }
00952
00953 $items = array();
00954 foreach ( $groups as $group ) {
00955 foreach ( $codes as $code ) {
00956 $items[] = $this->makeLabel( $group, $code );
00957 }
00958 }
00959
00960 return $items;
00961 }
00962
00969 protected function formatTimestamp( $timestamp ) {
00970 global $wgContLang;
00971
00972 switch ( $this->opts['scale'] ) {
00973 case 'hours' :
00974 $cut = 4;
00975 break;
00976 case 'days' :
00977 $cut = 6;
00978 break;
00979 case 'months':
00980 $cut = 8;
00981 break;
00982 default :
00983 return $wgContLang->sprintfDate( $this->getDateFormat(), $timestamp );
00984 }
00985
00986 return substr( $timestamp, 0, -$cut );
00987 }
00988 }
00989
00994 class TranslateRegistrationStats extends TranslationStatsBase {
00995 public function preQuery( &$tables, &$fields, &$conds, &$type, &$options, $start, $end ) {
00996 $tables = 'user';
00997 $fields = 'user_registration';
00998 $conds = self::makeTimeCondition( 'user_registration', $start, $end );
00999 $type .= '-registration';
01000 $options = array();
01001 }
01002
01003 public function getTimestamp( $row ) {
01004 return $row->user_registration;
01005 }
01006 }
01007
01013 class ReviewPerLanguageStats extends TranslatePerLanguageStats {
01014 public function preQuery( &$tables, &$fields, &$conds, &$type, &$options, $start, $end ) {
01015 global $wgTranslateMessageNamespaces;
01016
01017 $db = wfGetDB( DB_SLAVE );
01018
01019 $tables = array( 'logging' );
01020 $fields = array( 'log_timestamp' );
01021
01022 $conds = array(
01023 'log_namespace' => $wgTranslateMessageNamespaces,
01024 'log_action' => 'message',
01025 );
01026
01027 $timeConds = self::makeTimeCondition( 'log_timestamp', $start, $end );
01028 $conds = array_merge( $conds, $timeConds );
01029
01030 $options = array( 'ORDER BY' => 'log_timestamp' );
01031
01032 $this->groups = array_filter( array_map( 'trim', explode( ',', $this->opts['group'] ) ) );
01033 $this->codes = array_filter( array_map( 'trim', explode( ',', $this->opts['language'] ) ) );
01034
01035 $namespaces = self::namespacesFromGroups( $this->groups );
01036 if ( count( $namespaces ) ) {
01037 $conds['log_namespace'] = $namespaces;
01038 }
01039
01040 $languages = array();
01041 foreach ( $this->codes as $code ) {
01042 $languages[] = 'log_title ' . $db->buildLike( $db->anyString(), "/$code" );
01043 }
01044 if ( count( $languages ) ) {
01045 $conds[] = $db->makeList( $languages, LIST_OR );
01046 }
01047
01048 $fields[] = 'log_title';
01049
01050 if ( $this->groups ) {
01051 $fields[] = 'log_namespace';
01052 }
01053
01054 if ( $this->opts['count'] === 'reviewers' ) {
01055 $fields[] = 'log_user_text';
01056 }
01057
01058 $type .= '-reviews';
01059 }
01060
01061 public function indexOf( $row ) {
01062
01063 if ( $this->opts['count'] === 'reviewers' ) {
01064 $date = $this->formatTimestamp( $row->log_timestamp );
01065
01066 if ( isset( $this->usercache[$date][$row->log_user_text] ) ) {
01067 return -1;
01068 } else {
01069 $this->usercache[$date][$row->log_user_text] = 1;
01070 }
01071 }
01072
01073
01074 if ( strpos( $row->log_title, '/' ) === false ) {
01075 return false;
01076 }
01077
01078
01079 if ( !$this->groups && !$this->codes ) {
01080 return 'all';
01081 }
01082
01083
01084 list( $key, $code ) = TranslateUtils::figureMessage( $row->log_title );
01085
01086 $groups = array();
01087 $codes = array();
01088
01089 if ( $this->groups ) {
01090
01091
01092 $groups = TranslateUtils::messageKeyToGroups( $row->log_namespace, $key );
01093 $groups = array_intersect( $this->groups, $groups );
01094 }
01095
01096 if ( $this->codes ) {
01097 $codes = array( $code );
01098 }
01099
01100 return $this->combineTwoArrays( $groups, $codes );
01101 }
01102
01103 public function labels() {
01104 return $this->combineTwoArrays( $this->groups, $this->codes );
01105 }
01106
01107 public function getTimestamp( $row ) {
01108 return $row->log_timestamp;
01109 }
01110 }