00001 <?php
00019 class MessageCollection implements ArrayAccess, Iterator, Countable {
00023 public $code;
00024
00028 protected $definitions = null;
00029
00033 protected $infile = array();
00034
00035
00036
00040 protected $keys = array();
00041
00045 protected $messages = array();
00046
00050 protected $reverseMap;
00051
00052
00053
00055 protected $dbInfo = null;
00056
00058 protected $dbData = null;
00059
00061 protected $dbReviewData = array();
00062
00067 protected $tags = array();
00068
00072 protected $properties = array();
00073
00077 protected $authors = array();
00078
00083 public function __construct( $code ) {
00084 $this->code = $code;
00085 }
00086
00093 public static function newFromDefinitions( MessageDefinitions $definitions, $code ) {
00094 $collection = new self( $code );
00095 $collection->definitions = $definitions;
00096 $collection->resetForNewLanguage( $code );
00097
00098 return $collection;
00099 }
00100
00106 public static function newEmpty( $code ) {
00107 }
00108
00112 public function getLanguage() {
00113 return $this->code;
00114 }
00115
00116
00117
00124 public function setInfile( array $messages ) {
00125 $this->infile = $messages;
00126 }
00127
00133 public function setTags( $type, array $keys ) {
00134 $this->tags[$type] = $keys;
00135 }
00136
00141 public function keys() {
00142 return $this->keys;
00143 }
00144
00150 public function getTitles() {
00151 return array_values( $this->keys );
00152 }
00153
00159 public function getMessageKeys() {
00160 return array_keys( $this->keys );
00161 }
00162
00168 public function getTags( $type ) {
00169 return isset( $this->tags[$type] ) ? $this->tags[$type] : array();
00170 }
00171
00178 public function getAuthors() {
00179 $this->loadTranslations();
00180
00181 $authors = array_flip( $this->authors );
00182
00183 foreach ( $this->messages as $m ) {
00184
00188 $author = $m->getProperty( 'last-translator-text' );
00189
00190 if ( $author === null ) {
00191 continue;
00192 }
00193
00194 if ( !isset( $authors[$author] ) ) {
00195 $authors[$author] = 1;
00196 } else {
00197 $authors[$author]++;
00198 }
00199 }
00200
00201 # arsort( $authors, SORT_NUMERIC );
00202 ksort( $authors );
00203 $fuzzyBot = FuzzyBot::getName();
00204 foreach ( $authors as $author => $edits ) {
00205 if ( $author !== $fuzzyBot ) {
00206 $filteredAuthors[] = $author;
00207 }
00208 }
00209
00210 return isset( $filteredAuthors ) ? $filteredAuthors : array();
00211 }
00212
00219 public function addCollectionAuthors( $authors, $mode = 'append' ) {
00220 switch ( $mode ) {
00221 case 'append':
00222 $authors = array_merge( $this->authors, $authors );
00223 break;
00224 case 'set':
00225 break;
00226 default:
00227 throw new MWException( "Invalid mode $mode" );
00228 }
00229
00230 $this->authors = array_unique( $authors );
00231 }
00232
00236 public function setReviewMode( $value = true ) {
00237 }
00238
00239
00240
00247 public function loadTranslations( $dbtype = DB_SLAVE ) {
00248 $this->loadData( $this->keys, $dbtype );
00249 $this->loadInfo( $this->keys, $dbtype );
00250 $this->loadReviewInfo( $this->keys, $dbtype );
00251 $this->initMessages();
00252 }
00253
00259 public function resetForNewLanguage( $code ) {
00260 $this->code = $code;
00261 $this->keys = $this->fixKeys();
00262 $this->dbInfo = null;
00263 $this->dbData = null;
00264 $this->dbReviewData = array();
00265 $this->messages = null;
00266 $this->infile = array();
00267 $this->authors = array();
00268
00269 unset( $this->tags['fuzzy'] );
00270 $this->reverseMap = null;
00271 $this->getReverseMap();
00272 }
00273
00281 public function slice( $offset, $limit ) {
00282 $indexes = array_keys( $this->keys );
00283
00284 if ( $offset === '' ) {
00285 $offset = 0;
00286 }
00287
00288
00289 if ( !ctype_digit( strval( $offset ) ) ) {
00290 $count = 0;
00291 foreach ( array_keys( $this->keys ) as $index ) {
00292 if ( $index === $offset ) {
00293 break;
00294 }
00295 $count++;
00296 }
00297
00298 $offset = $count;
00299 }
00300
00301
00302 $backwardsOffset = $forwardsOffset = false;
00303
00304
00305
00306
00307
00308
00309
00310
00311 if ( $offset > 0 ) {
00312 $backwardsOffset = strval( max( 0, $offset - $limit ) );
00313 }
00314
00315
00316
00317
00318
00319
00320
00321
00322
00323
00324
00325 if ( isset( $indexes[$offset + $limit] ) ) {
00326 $forwardsOffset = $indexes[$offset + $limit];
00327 }
00328
00329 $this->keys = array_slice( $this->keys, $offset, $limit, true );
00330
00331 return array( $backwardsOffset, $forwardsOffset, $offset );
00332 }
00333
00357 public function filter( $type, $condition = true, $value = null ) {
00358 if ( !in_array( $type, self::getAvailableFilters(), true ) ) {
00359 throw new MWException( "Unknown filter $type" );
00360 }
00361 $this->applyFilter( $type, $condition, $value );
00362 }
00363
00367 public static function getAvailableFilters() {
00368 return array(
00369 'fuzzy',
00370 'optional',
00371 'ignored',
00372 'hastranslation',
00373 'changed',
00374 'translated',
00375 'reviewer',
00376 'last-translator',
00377 );
00378 }
00379
00388 protected function applyFilter( $filter, $condition, $value ) {
00389 $keys = $this->keys;
00390 if ( $filter === 'fuzzy' ) {
00391 $keys = $this->filterFuzzy( $keys, $condition );
00392 } elseif ( $filter === 'hastranslation' ) {
00393 $keys = $this->filterHastranslation( $keys, $condition );
00394 } elseif ( $filter === 'translated' ) {
00395 $fuzzy = $this->filterFuzzy( $keys, false );
00396 $hastranslation = $this->filterHastranslation( $keys, false );
00397
00398 $translated = $this->filterOnCondition( $hastranslation, $fuzzy );
00399 $keys = $this->filterOnCondition( $keys, $translated, $condition );
00400 } elseif ( $filter === 'changed' ) {
00401 $keys = $this->filterChanged( $keys, $condition );
00402 } elseif ( $filter === 'reviewer' ) {
00403 $keys = $this->filterReviewer( $keys, $condition, $value );
00404 } elseif ( $filter === 'last-translator' ) {
00405 $keys = $this->filterLastTranslator( $keys, $condition, $value );
00406 } else {
00407
00408 if ( !isset( $this->tags[$filter] ) ) {
00409 if ( $filter !== 'optional' && $filter !== 'ignored' ) {
00410 throw new MWException( "No tagged messages for custom filter $filter" );
00411 }
00412 $keys = $this->filterOnCondition( $keys, array(), $condition );
00413 } else {
00414 $taggedKeys = array_flip( $this->tags[$filter] );
00415 $keys = $this->filterOnCondition( $keys, $taggedKeys, $condition );
00416 }
00417 }
00418
00419 $this->keys = $keys;
00420 }
00421
00437 protected function filterOnCondition( array $keys, array $condKeys, $condition = true ) {
00438 if ( $condition === true ) {
00439
00440 foreach ( array_keys( $condKeys ) as $key ) {
00441 unset( $keys[$key] );
00442 }
00443 } else {
00444
00445 foreach ( array_keys( $keys ) as $key ) {
00446 if ( !isset( $condKeys[$key] ) ) {
00447 unset( $keys[$key] );
00448 }
00449 }
00450 }
00451
00452 return $keys;
00453 }
00454
00462 protected function filterFuzzy( array $keys, $condition ) {
00463 $this->loadInfo( $keys );
00464
00465 $origKeys = array();
00466 if ( $condition === false ) {
00467 $origKeys = $keys;
00468 }
00469
00470 foreach ( $this->dbInfo as $row ) {
00471 if ( $row->rt_type !== null ) {
00472 unset( $keys[$this->rowToKey( $row )] );
00473 }
00474 }
00475
00476 if ( $condition === false ) {
00477 $keys = array_diff( $origKeys, $keys );
00478 }
00479
00480 return $keys;
00481 }
00482
00490 protected function filterHastranslation( array $keys, $condition ) {
00491 $this->loadInfo( $keys );
00492
00493 $origKeys = array();
00494 if ( $condition === false ) {
00495 $origKeys = $keys;
00496 }
00497
00498 foreach ( $this->dbInfo as $row ) {
00499 unset( $keys[$this->rowToKey( $row )] );
00500 }
00501
00502
00503 foreach ( array_keys( $this->infile ) as $inf ) {
00504 unset( $keys[$inf] );
00505 }
00506
00507
00508 if ( $condition === false ) {
00509 $keys = array_diff( $origKeys, $keys );
00510 }
00511
00512 return $keys;
00513 }
00514
00523 protected function filterChanged( array $keys, $condition ) {
00524 $this->loadData( $keys );
00525
00526 $origKeys = array();
00527 if ( $condition === false ) {
00528 $origKeys = $keys;
00529 }
00530
00531 foreach ( $this->dbData as $row ) {
00532 $mkey = $this->rowToKey( $row );
00533 if ( !isset( $this->infile[$mkey] ) ) {
00534 continue;
00535 }
00536
00537 $text = Revision::getRevisionText( $row );
00538 if ( $this->infile[$mkey] === $text ) {
00539
00540 unset( $keys[$mkey] );
00541 }
00542 }
00543
00544
00545 if ( $condition === false ) {
00546 $keys = $this->filterOnCondition( $keys, $origKeys, false );
00547 }
00548
00549 return $keys;
00550 }
00551
00560 protected function filterReviewer( array $keys, $condition, $user ) {
00561 $this->loadReviewInfo( $keys );
00562 $origKeys = $keys;
00563
00564
00565
00566 $userId = intval( $user );
00567 foreach ( $this->dbReviewData as $row ) {
00568 if ( $user === null || intval( $row->trr_user ) === $userId ) {
00569 unset( $keys[$this->rowToKey( $row )] );
00570 }
00571 }
00572
00573 if ( $condition === false ) {
00574 $keys = array_diff( $origKeys, $keys );
00575 }
00576
00577 return $keys;
00578 }
00579
00587 protected function filterLastTranslator( array $keys, $condition, $user ) {
00588 $this->loadData( $keys );
00589 $origKeys = $keys;
00590
00591 $user = intval( $user );
00592 foreach ( $this->dbData as $row ) {
00593 if ( intval( $row->rev_user ) === $user ) {
00594 unset( $keys[$this->rowToKey( $row )] );
00595 }
00596 }
00597
00598 if ( $condition === false ) {
00599 $keys = array_diff( $origKeys, $keys );
00600 }
00601
00602 return $keys;
00603 }
00604
00609 protected function fixKeys() {
00610 $newkeys = array();
00611
00612 $pages = $this->definitions->getPages();
00613 $code = $this->code;
00614
00615 foreach ( $pages as $key => $page ) {
00616 list ( $namespace, $pagename ) = $page;
00617 $title = Title::makeTitleSafe( $namespace, "$pagename/$code" );
00618 if ( !$title ) {
00619 wfWarn( "Invalid title $namespace:$pagename/$code" );
00620 continue;
00621 }
00622 $newkeys[$key] = $title;
00623 }
00624
00625 return $newkeys;
00626 }
00627
00633 protected function loadInfo( array $keys, $dbtype = DB_SLAVE ) {
00634 if ( $this->dbInfo !== null ) {
00635 return;
00636 }
00637
00638 $this->dbInfo = array();
00639
00640 if ( !count( $keys ) ) {
00641 return;
00642 }
00643
00644 $dbr = wfGetDB( $dbtype );
00645 $tables = array( 'page', 'revtag' );
00646 $fields = array( 'page_namespace', 'page_title', 'rt_type' );
00647 $conds = $this->getTitleConds( $dbr );
00648 $joins = array( 'revtag' =>
00649 array(
00650 'LEFT JOIN',
00651 array( 'page_id=rt_page', 'page_latest=rt_revision', 'rt_type' => RevTag::getType( 'fuzzy' ) )
00652 )
00653 );
00654
00655 $this->dbInfo = $dbr->select( $tables, $fields, $conds, __METHOD__, array(), $joins );
00656 }
00657
00663 protected function loadReviewInfo( array $keys, $dbtype = DB_SLAVE ) {
00664 if ( $this->dbReviewData !== array() ) {
00665 return;
00666 }
00667
00668 $this->dbReviewData = array();
00669
00670 if ( !count( $keys ) ) {
00671 return;
00672 }
00673
00674 $dbr = wfGetDB( $dbtype );
00675 $tables = array( 'page', 'translate_reviews' );
00676 $fields = array( 'page_namespace', 'page_title', 'trr_user' );
00677 $conds = $this->getTitleConds( $dbr );
00678 $joins = array( 'translate_reviews' =>
00679 array(
00680 'JOIN',
00681 array( 'page_id=trr_page', 'page_latest=trr_revision' )
00682 )
00683 );
00684
00685 $this->dbReviewData = $dbr->select( $tables, $fields, $conds, __METHOD__, array(), $joins );
00686 }
00687
00693 protected function loadData( array $keys, $dbtype = DB_SLAVE ) {
00694 if ( $this->dbData !== null ) {
00695 return;
00696 }
00697
00698 $this->dbData = array();
00699
00700 if ( !count( $keys ) ) {
00701 return;
00702 }
00703
00704 $dbr = wfGetDB( $dbtype );
00705
00706 $tables = array( 'page', 'revision', 'text' );
00707 $fields = array(
00708 'page_namespace',
00709 'page_title',
00710 'page_latest',
00711 'rev_user',
00712 'rev_user_text',
00713 'old_flags',
00714 'old_text'
00715 );
00716 $conds = array(
00717 'page_latest = rev_id',
00718 'old_id = rev_text_id',
00719 );
00720 $conds[] = $this->getTitleConds( $dbr );
00721
00722 $res = $dbr->select( $tables, $fields, $conds, __METHOD__ );
00723
00724 $this->dbData = $res;
00725 }
00726
00733 protected function getTitleConds( $db ) {
00734
00735 $byNamespace = array();
00736 foreach ( $this->getTitles() as $title ) {
00737 $namespace = $title->getNamespace();
00738 $pagename = $title->getDBKey();
00739 $byNamespace[$namespace][] = $pagename;
00740 }
00741
00742 $conds = array();
00743 foreach ( $byNamespace as $namespaces => $pagenames ) {
00744 $cond = array(
00745 'page_namespace' => $namespaces,
00746 'page_title' => $pagenames,
00747 );
00748
00749 $conds[] = $db->makeList( $cond, LIST_AND );
00750 }
00751
00752 return $db->makeList( $conds, LIST_OR );
00753 }
00754
00761 protected function rowToKey( $row ) {
00762 $map = $this->getReverseMap();
00763 if ( isset( $map[$row->page_namespace][$row->page_title] ) ) {
00764 return $map[$row->page_namespace][$row->page_title];
00765 } else {
00766 wfWarn( "Got unknown title from the database: {$row->page_namespace}:{$row->page_title}" );
00767
00768 return null;
00769 }
00770 }
00771
00776 public function getReverseMap() {
00777 if ( isset( $this->reverseMap ) ) {
00778 return $this->reverseMap;
00779 }
00780
00781 $map = array();
00785 foreach ( $this->keys as $mkey => $title ) {
00786 $map[$title->getNamespace()][$title->getDBKey()] = $mkey;
00787 }
00788
00789 return $this->reverseMap = $map;
00790 }
00791
00796 public function initMessages() {
00797 if ( $this->messages !== null ) {
00798 return;
00799 }
00800
00801 $messages = array();
00802 $definitions = $this->definitions->getDefinitions();
00803 foreach ( array_keys( $this->keys ) as $mkey ) {
00804 $messages[$mkey] = new ThinMessage( $mkey, $definitions[$mkey] );
00805 }
00806
00807
00808 if ( $this->dbData !== null ) {
00809 foreach ( $this->dbData as $row ) {
00810 $mkey = $this->rowToKey( $row );
00811 if ( !isset( $messages[$mkey] ) ) {
00812 continue;
00813 }
00814 $messages[$mkey]->setRow( $row );
00815 $messages[$mkey]->setProperty( 'revision', $row->page_latest );
00816 }
00817 }
00818
00819 if ( $this->dbInfo !== null ) {
00820 $fuzzy = array();
00821 foreach ( $this->dbInfo as $row ) {
00822 if ( $row->rt_type !== null ) {
00823 $fuzzy[] = $this->rowToKey( $row );
00824 }
00825 }
00826
00827 $this->setTags( 'fuzzy', $fuzzy );
00828 }
00829
00830
00831 foreach ( $this->tags as $type => $keys ) {
00832 foreach ( $keys as $mkey ) {
00833 if ( isset( $messages[$mkey] ) ) {
00834 $messages[$mkey]->addTag( $type );
00835 }
00836 }
00837 }
00838
00839
00840 foreach ( $this->properties as $type => $keys ) {
00841 foreach ( $keys as $mkey => $value ) {
00842 if ( isset( $messages[$mkey] ) ) {
00843 $messages[$mkey]->setProperty( $type, $value );
00844 }
00845 }
00846 }
00847
00848
00849 foreach ( $this->infile as $mkey => $value ) {
00850 if ( isset( $messages[$mkey] ) ) {
00851 $messages[$mkey]->setInfile( $value );
00852 }
00853 }
00854
00855 foreach ( $this->dbReviewData as $row ) {
00856 $mkey = $this->rowToKey( $row );
00857 if ( !isset( $messages[$mkey] ) ) {
00858 continue;
00859 }
00860 $messages[$mkey]->appendProperty( 'reviewers', $row->trr_user );
00861 }
00862
00863
00864 foreach ( $messages as $obj ) {
00865 if ( $obj->hasTag( 'fuzzy' ) ) {
00866 $obj->setProperty( 'status', 'fuzzy' );
00867 } elseif ( is_array( $obj->getProperty( 'reviewers' ) ) ) {
00868 $obj->setProperty( 'status', 'proofread' );
00869 } elseif ( $obj->translation() !== null ) {
00870 $obj->setProperty( 'status', 'translated' );
00871 } else {
00872 $obj->setProperty( 'status', 'untranslated' );
00873 }
00874 }
00875
00876 $this->messages = $messages;
00877 }
00878
00884 public function offsetExists( $offset ) {
00885 return isset( $this->keys[$offset] );
00886 }
00887
00892 public function offsetGet( $offset ) {
00893 return $this->messages[$offset];
00894 }
00895
00900 public function offsetSet( $offset, $value ) {
00901 $this->messages[$offset] = $value;
00902 }
00903
00907 public function offsetUnset( $offset ) {
00908 unset( $this->keys[$offset] );
00909 }
00910
00918 public function __get( $name ) {
00919 throw new MWException( __METHOD__ . ": Trying to access unknown property $name" );
00920 }
00921
00922 public function __set( $name, $value ) {
00923 throw new MWException( __METHOD__ . ": Trying to modify unknown property $name" );
00924 }
00925
00931 public function rewind() {
00932 reset( $this->keys );
00933 }
00934
00935 public function current() {
00936 if ( !count( $this->keys ) ) {
00937 return false;
00938 }
00939
00940 return $this->messages[key( $this->keys )];
00941 }
00942
00943 public function key() {
00944 return key( $this->keys );
00945 }
00946
00947 public function next() {
00948 return next( $this->keys );
00949 }
00950
00951 public function valid() {
00952 return isset( $this->messages[key( $this->keys )] );
00953 }
00954
00955 public function count() {
00956 return count( $this->keys() );
00957 }
00959 }
00960
00966 class MessageDefinitions {
00967 protected $namespace;
00968 protected $messages;
00969
00970 public function __construct( array $messages, $namespace = false ) {
00971 $this->namespace = $namespace;
00972 $this->messages = $messages;
00973 }
00974
00975 public function getDefinitions() {
00976 return $this->messages;
00977 }
00978
00982 public function getPages() {
00983 $namespace = $this->namespace;
00984 $pages = array();
00985 foreach ( array_keys( $this->messages ) as $key ) {
00986 if ( $namespace === false ) {
00987
00988 $pages[$key] = explode( ':', $key, 2 );
00989 } else {
00990 $pages[$key] = array( $namespace, $key );
00991 }
00992 }
00993
00994 return $pages;
00995 }
00996 }