SpecialTranslationStats.php

Go to the documentation of this file.
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                 // BC for old syntax which replaced _ to | which was not allowed
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 ) . // Should fit yyyymmddhhmmss
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         // Give grep a chance to find the usages:
00240         // translate-statsf-width, translate-statsf-height, translate-statsf-start,
00241         // translate-statsf-days, translate-statsf-scale, translate-statsf-count,
00242         // translate-statsf-language, translate-statsf-group
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         // Give grep a chance to find the usages:
00258         // translate-statsf-scale, translate-statsf-count
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         // Start processing the data
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         // Allow 10 seconds in the future for processing time
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         // Processing
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                 // Ignore values outside range
00468                 if ( !isset( $data[$date] ) ) {
00469                     continue;
00470                 }
00471 
00472                 $data[$date][$labelToIndex[$i]]++;
00473             }
00474         }
00475 
00476         // Don't display dummy label
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             // Indicator that the last value is not full
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         /* Ensure that the first item in the graph has full data even
00520         * if it doesn't align with the given 'days' boundary */
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             /* Here we assume that week starts on monday, which does not
00527             * always hold true. Go Xwards day by day until we are on monday */
00528             while ( date( 'D', $cutoff ) !== "Mon" ) {
00529                 $cutoff += $dir * 86400;
00530             }
00531             // Round to nearest day
00532             $cutoff -= ( $cutoff % 86400 );
00533         } elseif ( $scale === 'months' ) {
00534             // Go Xwards/ day by day until we are on the first day of the month
00535             while ( date( 'j', $cutoff ) !== "1" ) {
00536                 $cutoff += $dir * 86400;
00537             }
00538             // Round to nearest day
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         // Define the object
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         // Give grep a chance to find the usages:
00611         // translate-stats-edits, translate-stats-users, translate-stats-registrations,
00612         // translate-stats-reviews, translate-stats-reviewers
00613         $yTitle = $this->msg( 'translate-stats-' . $opts['count'] )->escaped();
00614 
00615         // Turn off X axis ticks and labels because they get in the way:
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         // If we have very small case, ensure that there is at least one tick
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         // Draw it
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             /* We use increment to fill up the values. Use number small enough
00675              * to ensure we hit each month */
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         // This query is slow... ensure a lower limit.
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         // We need to check that there is only one user per day.
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         // Do not consider language-less pages.
00883         if ( strpos( $row->rc_title, '/' ) === false ) {
00884             return false;
00885         }
00886 
00887         // No filters, just one key to track.
00888         if ( !$this->groups && !$this->codes ) {
00889             return 'all';
00890         }
00891 
00892         // The key-building needs to be in sync with ::labels().
00893         list( $key, $code ) = TranslateUtils::figureMessage( $row->rc_title );
00894 
00895         $groups = array();
00896         $codes = array();
00897 
00898         if ( $this->groups ) {
00899             /*
00900              * Get list of keys that the message belongs to, and filter
00901              * out those which are not requested.
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         // We need to check that there is only one user per day.
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         // Do not consider language-less pages.
01074         if ( strpos( $row->log_title, '/' ) === false ) {
01075             return false;
01076         }
01077 
01078         // No filters, just one key to track.
01079         if ( !$this->groups && !$this->codes ) {
01080             return 'all';
01081         }
01082 
01083         // The key-building needs to be in sync with ::labels().
01084         list( $key, $code ) = TranslateUtils::figureMessage( $row->log_title );
01085 
01086         $groups = array();
01087         $codes = array();
01088 
01089         if ( $this->groups ) {
01090             /* Get list of keys that the message belongs to, and filter
01091              * out those which are not requested. */
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 }
Generated on Tue Oct 29 00:00:24 2013 for MediaWiki Translate Extension by  doxygen 1.6.3