Index: trunk/extensions/Translate/specials/SpecialTranslationStats.php |
— | — | @@ -37,6 +37,7 @@ |
38 | 38 | $opts->add( 'height', 400 ); |
39 | 39 | $opts->add( 'group', '' ); |
40 | 40 | $opts->add( 'uselang', '' ); |
| 41 | + $opts->add( 'start', '' ); |
41 | 42 | $opts->fetchValuesFromRequest( $wgRequest ); |
42 | 43 | |
43 | 44 | $pars = explode( ';', $par ); |
— | — | @@ -55,6 +56,9 @@ |
56 | 57 | $opts->validateIntBounds( 'days', 1, 10000 ); |
57 | 58 | $opts->validateIntBounds( 'width', 200, 1000 ); |
58 | 59 | $opts->validateIntBounds( 'height', 200, 1000 ); |
| 60 | + if ( $opts['start'] !== '' ) { |
| 61 | + $opts['start'] = strval( wfTimestamp( TS_MW, $opts['start'] ) ); |
| 62 | + } |
59 | 63 | |
60 | 64 | $validScales = array( 'months', 'weeks', 'days', 'hours' ); |
61 | 65 | if ( !in_array( $opts['scale'], $validScales ) ) { |
— | — | @@ -126,6 +130,7 @@ |
127 | 131 | $this->eInput( 'width', $opts ) . |
128 | 132 | $this->eInput( 'height', $opts ) . |
129 | 133 | '<tr><td colspan="2"><hr /></td></tr>' . |
| 134 | + $this->eInput( 'start', $opts, 12 ) . |
130 | 135 | $this->eInput( 'days', $opts ) . |
131 | 136 | $this->eRadio( 'scale', $opts, array( 'months', 'weeks', 'days', 'hours' ) ) . |
132 | 137 | $this->eRadio( 'count', $opts, array( 'edits', 'users', 'registrations' ) ) . |
— | — | @@ -182,12 +187,12 @@ |
183 | 188 | * @param $opts FormOptions |
184 | 189 | * @return \string Html. |
185 | 190 | */ |
186 | | - protected function eInput( $name, FormOptions $opts ) { |
| 191 | + protected function eInput( $name, FormOptions $opts, $width = 4 ) { |
187 | 192 | $value = $opts[$name]; |
188 | 193 | |
189 | 194 | return |
190 | 195 | '<tr><td>' . $this->eLabel( $name ) . '</td><td>' . |
191 | | - Xml::input( $name, 4, $value, array( 'id' => $name ) ) . |
| 196 | + Xml::input( $name, $width, $value, array( 'id' => $name ) ) . |
192 | 197 | '</td></tr>' . "\n"; |
193 | 198 | } |
194 | 199 | |
— | — | @@ -354,37 +359,33 @@ |
355 | 360 | $so = new TranslatePerLanguageStats( $opts ); |
356 | 361 | } |
357 | 362 | |
| 363 | + $fixedStart = $opts->getValue( 'start' ) !== ''; |
| 364 | + |
358 | 365 | $now = time(); |
| 366 | + $period = 3600 * 24 * $opts->getValue( 'days' ); |
359 | 367 | |
360 | | - /* Ensure that the first item in the graph has full data even |
361 | | - * if it doesn't align with the given 'days' boundary */ |
362 | | - $cutoff = $now - ( 3600 * 24 * $opts->getValue( 'days' ) ); |
363 | | - if ( $opts['scale'] === 'hours' ) { |
364 | | - $cutoff -= ( $cutoff % 3600 ); |
365 | | - } elseif ( $opts['scale'] === 'days' ) { |
366 | | - $cutoff -= ( $cutoff % 86400 ); |
367 | | - } elseif ( $opts['scale'] === 'weeks' ) { |
368 | | - /* Here we assume that week starts on monday, which does not |
369 | | - * always hold true. Go backwards day by day until we are on monday */ |
370 | | - while ( date( 'D', $cutoff ) !== "Mon" ) { |
371 | | - $cutoff -= 86400; |
372 | | - } |
373 | | - $cutoff -= ( $cutoff % 86400 ); |
374 | | - } elseif ( $opts['scale'] === 'months' ) { |
375 | | - // Go backwards day by day until we are on the first day of the month |
376 | | - while ( date( 'j', $cutoff ) !== "1" ) { |
377 | | - $cutoff -= 86400; |
378 | | - } |
379 | | - $cutoff -= ( $cutoff % 86400 ); |
| 368 | + if ( $fixedStart ) { |
| 369 | + $cutoff = wfTimestamp( TS_UNIX, $opts->getValue( 'start' ) ); |
| 370 | + } else { |
| 371 | + $cutoff = $now - $period; |
380 | 372 | } |
| 373 | + $cutoff = self::roundTimestampToCutoff( $opts['scale'], $cutoff, 'earlier' ); |
381 | 374 | |
| 375 | + $start = $cutoff; |
| 376 | + |
| 377 | + if ( $fixedStart ) { |
| 378 | + $end = self::roundTimestampToCutoff( $opts['scale'], $start + $period, 'later' ) -1; |
| 379 | + } else { |
| 380 | + $end = null; |
| 381 | + } |
| 382 | + |
382 | 383 | $tables = array(); |
383 | 384 | $fields = array(); |
384 | 385 | $conds = array(); |
385 | 386 | $type = __METHOD__; |
386 | 387 | $options = array(); |
387 | 388 | |
388 | | - $so->preQuery( $tables, $fields, $conds, $type, $options, $cutoff ); |
| 389 | + $so->preQuery( $tables, $fields, $conds, $type, $options, $start, $end ); |
389 | 390 | $res = $dbr->select( $tables, $fields, $conds, $type, $options ); |
390 | 391 | wfDebug( __METHOD__ . "-queryend\n" ); |
391 | 392 | |
— | — | @@ -399,7 +400,8 @@ |
400 | 401 | |
401 | 402 | $data = array(); |
402 | 403 | // Allow 10 seconds in the future for processing time |
403 | | - while ( $cutoff <= $now + 10 ) { |
| 404 | + $lastValue = $end !== null ? $end : $now + 10; |
| 405 | + while ( $cutoff <= $lastValue ) { |
404 | 406 | $date = $wgLang->sprintfDate( $dateFormat, wfTimestamp( TS_MW, $cutoff ) ); |
405 | 407 | $cutoff += $increment; |
406 | 408 | $data[$date] = $defaults; |
— | — | @@ -450,14 +452,59 @@ |
451 | 453 | } |
452 | 454 | } |
453 | 455 | |
| 456 | + if ( $end == null ) { |
| 457 | + $last = array_splice( $data, -1, 1 ); |
| 458 | + // Indicator that the last value is not full |
| 459 | + $data[key( $last ) . '*'] = current( $last ); |
| 460 | + } |
454 | 461 | |
455 | | - $last = array_splice( $data, -1, 1 ); |
456 | | - $data[key( $last ) . '*'] = current( $last ); |
457 | | - |
458 | 462 | return array( $labels, $data ); |
459 | 463 | } |
460 | 464 | |
461 | 465 | /** |
| 466 | + * Gets the closest earlieast timestamp that corresponds to start of a |
| 467 | + * period in given scale, like, midnight, monday or first day of the month. |
| 468 | + * @param $scale string One of hours, days, weeks, months |
| 469 | + * @param $cutoff int Timestamp in unix format. |
| 470 | + * @param $direction string One of earlier, later |
| 471 | + */ |
| 472 | + protected static function roundTimestampToCutoff( $scale, $cutoff, $direction = 'earlier' ) { |
| 473 | + $dir = $direction === 'earlier' ? -1 : 1; |
| 474 | + |
| 475 | + /* Ensure that the first item in the graph has full data even |
| 476 | + * if it doesn't align with the given 'days' boundary */ |
| 477 | + if ( $scale === 'hours' ) { |
| 478 | + $cutoff += self::roundingAddition( $cutoff, 3600, $dir ); |
| 479 | + } elseif ( $scale === 'days' ) { |
| 480 | + $cutoff += self::roundingAddition( $cutoff, 86400, $dir ); |
| 481 | + } elseif ( $scale === 'weeks' ) { |
| 482 | + /* Here we assume that week starts on monday, which does not |
| 483 | + * always hold true. Go Xwards day by day until we are on monday */ |
| 484 | + while ( date( 'D', $cutoff ) !== "Mon" ) { |
| 485 | + $cutoff += $dir*86400; |
| 486 | + } |
| 487 | + // Round to nearest day |
| 488 | + $cutoff -= ( $cutoff % 86400 ); |
| 489 | + } elseif ( $scale === 'months' ) { |
| 490 | + // Go Xwards/ day by day until we are on the first day of the month |
| 491 | + while ( date( 'j', $cutoff ) !== "1" ) { |
| 492 | + $cutoff += $dir*86400; |
| 493 | + } |
| 494 | + // Round to nearest day |
| 495 | + $cutoff -= ( $cutoff % 86400 ); |
| 496 | + } |
| 497 | + return $cutoff; |
| 498 | + } |
| 499 | + |
| 500 | + protected static function roundingAddition( $ts, $amount, $dir ) { |
| 501 | + if ( $direction === -1 ) { |
| 502 | + return -1 * ( $ts % $amount ); |
| 503 | + } else { |
| 504 | + return $amount - ( $ts % $amount ); |
| 505 | + } |
| 506 | + } |
| 507 | + |
| 508 | + /** |
462 | 509 | * Adds raw image data of the graph to the output. |
463 | 510 | * @param $opts FormOptions |
464 | 511 | */ |
— | — | @@ -601,9 +648,10 @@ |
602 | 649 | * @param $conds \array Empty array. Append select conditions. |
603 | 650 | * @param $type \string Append graph type (used to identify queries). |
604 | 651 | * @param $options \array Empty array. Append extra query options. |
605 | | - * @param $cutoff \string Precalculated cutoff timestamp in unix format. |
| 652 | + * @param $start \string Precalculated start cutoff timestamp |
| 653 | + * @param $end \string Precalculated end cutoff timestamp |
606 | 654 | */ |
607 | | - public function preQuery( &$tables, &$fields, &$conds, &$type, &$options, $cutoff ); |
| 655 | + public function preQuery( &$tables, &$fields, &$conds, &$type, &$options, $start, $end ); |
608 | 656 | |
609 | 657 | /** |
610 | 658 | * Return the indexes which this result contributes to. |
— | — | @@ -667,6 +715,18 @@ |
668 | 716 | |
669 | 717 | return $dateFormat; |
670 | 718 | } |
| 719 | + |
| 720 | + protected static function makeTimeCondition( $field, $start, $end ) { |
| 721 | + $db = wfGetDB( DB_SLAVE ); |
| 722 | + $conds = array(); |
| 723 | + if ( $start !== null ) { |
| 724 | + $conds[] = "$field >= '{$db->timestamp( $start )}'"; |
| 725 | + } |
| 726 | + if ( $end !== null ) { |
| 727 | + $conds[] = "$field <= '{$db->timestamp( $end )}'"; |
| 728 | + } |
| 729 | + return $conds; |
| 730 | + } |
671 | 731 | } |
672 | 732 | |
673 | 733 | /** |
— | — | @@ -683,7 +743,7 @@ |
684 | 744 | $opts->validateIntBounds( 'days', 1, 200 ); |
685 | 745 | } |
686 | 746 | |
687 | | - public function preQuery( &$tables, &$fields, &$conds, &$type, &$options, $cutoff ) { |
| 747 | + public function preQuery( &$tables, &$fields, &$conds, &$type, &$options, $start, $end ) { |
688 | 748 | global $wgTranslateMessageNamespaces; |
689 | 749 | |
690 | 750 | $db = wfGetDB( DB_SLAVE ); |
— | — | @@ -692,11 +752,13 @@ |
693 | 753 | $fields = array( 'rc_timestamp' ); |
694 | 754 | |
695 | 755 | $conds = array( |
696 | | - "rc_timestamp >= '{$db->timestamp( $cutoff )}'", |
697 | 756 | 'rc_namespace' => $wgTranslateMessageNamespaces, |
698 | 757 | 'rc_bot' => 0 |
699 | 758 | ); |
700 | 759 | |
| 760 | + $timeConds = self::makeTimeCondition( 'rc_timestamp', $start, $end ); |
| 761 | + $conds = array_merge( $conds, $timeConds ); |
| 762 | + |
701 | 763 | $options = array( 'ORDER BY' => 'rc_timestamp' ); |
702 | 764 | |
703 | 765 | $this->groups = array_filter( array_map( 'trim', explode( ',', $this->opts['group'] ) ) ); |
— | — | @@ -863,11 +925,11 @@ |
864 | 926 | * @ingroup Stats |
865 | 927 | */ |
866 | 928 | class TranslateRegistrationStats extends TranslationStatsBase { |
867 | | - public function preQuery( &$tables, &$fields, &$conds, &$type, &$options, $cutoff ) { |
| 929 | + public function preQuery( &$tables, &$fields, &$conds, &$type, &$options, $start, $end ) { |
868 | 930 | $db = wfGetDB( DB_SLAVE ); |
869 | 931 | $tables = 'user'; |
870 | 932 | $fields = 'user_registration'; |
871 | | - $conds = array( "user_registration >= '{$db->timestamp( $cutoff )}'" ); |
| 933 | + $conds = self::makeTimeCondition( 'user_registration', $start, $end ); |
872 | 934 | $type .= '-registration'; |
873 | 935 | $options = array(); |
874 | 936 | } |