00001 <?php
00015 class TranslatablePage {
00019 protected $title = null;
00020
00024 protected $text = null;
00025
00031 protected $revision = null;
00032
00037 protected $source = null;
00038
00042 protected $init = false;
00043
00047 protected $displayTitle = 'Page display title';
00048
00049 protected $cachedParse;
00050
00054 protected function __construct( Title $title ) {
00055 $this->title = $title;
00056 }
00057
00058
00059
00070 public static function newFromText( Title $title, $text ) {
00071 $obj = new self( $title );
00072 $obj->text = $text;
00073 $obj->source = 'text';
00074
00075 return $obj;
00076 }
00077
00088 public static function newFromRevision( Title $title, $revision ) {
00089 $rev = Revision::newFromTitle( $title, $revision );
00090 if ( $rev === null ) {
00091 throw new MWException( 'Revision is null' );
00092 }
00093
00094 $obj = new self( $title );
00095 $obj->source = 'revision';
00096 $obj->revision = $revision;
00097
00098 return $obj;
00099 }
00100
00108 public static function newFromTitle( Title $title ) {
00109 $obj = new self( $title );
00110 $obj->source = 'title';
00111
00112 return $obj;
00113 }
00114
00115
00116
00121 public function getTitle() {
00122 return $this->title;
00123 }
00124
00130 public function getText() {
00131 if ( $this->init === false ) {
00132 switch ( $this->source ) {
00133 case 'text':
00134 break;
00135 case 'title':
00136 $this->revision = $this->getMarkedTag();
00137
00138 case 'revision':
00139 $rev = Revision::newFromTitle( $this->getTitle(), $this->revision );
00140 $this->text = $rev->getText();
00141 break;
00142 }
00143 }
00144
00145 if ( !is_string( $this->text ) ) {
00146 throw new MWException( 'We have no text' );
00147 }
00148
00149 $this->init = true;
00150
00151 return $this->text;
00152 }
00153
00158 public function getRevision() {
00159 return $this->revision;
00160 }
00161
00166 public function setRevision( $revision ) {
00167 $this->revision = $revision;
00168 $this->source = 'revision';
00169 $this->init = false;
00170 }
00171
00172
00173
00180 public function getSourceLanguageCode() {
00181 return $this->getTitle()->getPageLanguage()->getCode();
00182 }
00183
00188 public function getMessageGroupId() {
00189 return self::getMessageGroupIdFromTitle( $this->getTitle() );
00190 }
00191
00197 public static function getMessageGroupIdFromTitle( Title $title ) {
00198 return 'page-' . $title->getPrefixedText();
00199 }
00200
00206 public function getMessageGroup() {
00207 return MessageGroups::getGroup( $this->getMessageGroupId() );
00208 }
00209
00215 public function getPageDisplayTitle( $code ) {
00216 $section = str_replace( ' ', '_', $this->displayTitle );
00217 $page = $this->getTitle()->getPrefixedDBKey();
00218
00219 return $this->getMessageGroup()->getMessage( "$page/$section", $code );
00220 }
00221
00228 public function getParse() {
00229 if ( isset( $this->cachedParse ) ) {
00230 return $this->cachedParse;
00231 }
00232
00233 $text = $this->getText();
00234
00235 $nowiki = array();
00236 $text = self::armourNowiki( $nowiki, $text );
00237
00238 $sections = array();
00239
00240
00241 $displaytitle = new TPSection;
00242 $displaytitle->id = $this->displayTitle;
00243 $displaytitle->text = $this->getTitle()->getPrefixedText();
00244 $sections[TranslateUtils::getPlaceholder()] = $displaytitle;
00245
00246 $tagPlaceHolders = array();
00247
00248 while ( true ) {
00249 $re = '~(<translate>)\s*(.*?)(</translate>)~s';
00250 $matches = array();
00251 $ok = preg_match_all( $re, $text, $matches, PREG_OFFSET_CAPTURE );
00252
00253 if ( $ok === 0 ) {
00254 break;
00255 }
00256
00257
00258 $ph = TranslateUtils::getPlaceholder();
00259 $start = $matches[0][0][1];
00260 $len = strlen( $matches[0][0][0] );
00261 $end = $start + $len;
00262 $text = self::index_replace( $text, $ph, $start, $end );
00263
00264
00265
00266 $contents = $matches[0][0][0];
00267 $start = $matches[2][0][1] - $matches[0][0][1];
00268 $len = strlen( $matches[2][0][0] );
00269 $end = $start + $len;
00270
00271 $sectiontext = substr( $contents, $start, $len );
00272
00273 if ( strpos( $sectiontext, '<translate>' ) !== false ) {
00274 throw new TPException( array( 'pt-parse-nested', $sectiontext ) );
00275 }
00276
00277 $sectiontext = self::unArmourNowiki( $nowiki, $sectiontext );
00278
00279 $ret = $this->sectionise( $sections, $sectiontext );
00280
00281 $tagPlaceHolders[$ph] =
00282 self::index_replace( $contents, $ret, $start, $end );
00283 }
00284
00285 $prettyTemplate = $text;
00286 foreach ( $tagPlaceHolders as $ph => $value ) {
00287 $prettyTemplate = str_replace( $ph, '[...]', $prettyTemplate );
00288 }
00289
00290 if ( strpos( $text, '<translate>' ) !== false ) {
00291 throw new TPException( array( 'pt-parse-open', $prettyTemplate ) );
00292 } elseif ( strpos( $text, '</translate>' ) !== false ) {
00293 throw new TPException( array( 'pt-parse-close', $prettyTemplate ) );
00294 }
00295
00296 foreach ( $tagPlaceHolders as $ph => $value ) {
00297 $text = str_replace( $ph, $value, $text );
00298 }
00299
00300 if ( count( $sections ) === 1 ) {
00301
00302 $sections = array();
00303 }
00304
00305 $text = self::unArmourNowiki( $nowiki, $text );
00306
00307 $parse = new TPParse( $this->getTitle() );
00308 $parse->template = $text;
00309 $parse->sections = $sections;
00310
00311
00312 $this->cachedParse = $parse;
00313
00314 return $parse;
00315 }
00316
00317
00318
00324 public static function armourNowiki( &$holders, $text ) {
00325 $re = '~(<nowiki>)(.*?)(</nowiki>)~s';
00326
00327 while ( preg_match( $re, $text, $matches ) ) {
00328 $ph = TranslateUtils::getPlaceholder();
00329 $text = str_replace( $matches[0], $ph, $text );
00330 $holders[$ph] = $matches[0];
00331 }
00332
00333 return $text;
00334 }
00335
00341 public static function unArmourNowiki( $holders, $text ) {
00342 foreach ( $holders as $ph => $value ) {
00343 $text = str_replace( $ph, $value, $text );
00344 }
00345
00346 return $text;
00347 }
00348
00356 protected static function index_replace( $string, $rep, $start, $end ) {
00357 return substr( $string, 0, $start ) . $rep . substr( $string, $end );
00358 }
00359
00368 protected function sectionise( &$sections, $text ) {
00369 $flags = PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE;
00370 $parts = preg_split( '~(\s*\n\n\s*|\s*$)~', $text, -1, $flags );
00371
00372 $template = '';
00373 foreach ( $parts as $_ ) {
00374 if ( trim( $_ ) === '' ) {
00375 $template .= $_;
00376 } else {
00377 $ph = TranslateUtils::getPlaceholder();
00378 $sections[$ph] = $this->shakeSection( $_ );
00379 $template .= $ph;
00380 }
00381 }
00382
00383 return $template;
00384 }
00385
00398 protected function shakeSection( $content ) {
00399 $re = '~<!--T:(.*?)-->~';
00400 $matches = array();
00401 $count = preg_match_all( $re, $content, $matches, PREG_SET_ORDER );
00402
00403 if ( $count > 1 ) {
00404 throw new TPException( array( 'pt-shake-multiple', $content ) );
00405 }
00406
00407 $section = new TPSection;
00408 if ( $count === 1 ) {
00409 foreach ( $matches as $match ) {
00410 list( , $id ) = $match;
00411 $section->id = $id;
00412
00413
00414
00415 $rer1 = '~^<!--T:(.*?)-->\n~';
00416 $rer2 = '~\s*<!--T:(.*?)-->$~m';
00417 $content = preg_replace( $rer1, '', $content );
00418 $content = preg_replace( $rer2, '', $content );
00419
00420 if ( preg_match( $re, $content ) === 1 ) {
00421 throw new TPException( array( 'pt-shake-position', $content ) );
00422 } elseif ( trim( $content ) === '' ) {
00423 throw new TPException( array( 'pt-shake-empty', $id ) );
00424 }
00425 }
00426 } else {
00427
00428 $section->id = -1;
00429 }
00430
00431 $section->text = $content;
00432
00433 return $section;
00434 }
00435
00436
00437
00438 protected static $tagCache = array();
00439
00446 public function addMarkedTag( $revision, $value = null ) {
00447 $this->addTag( 'tp:mark', $revision, $value );
00448 MessageGroups::clearCache();
00449 }
00450
00456 public function addReadyTag( $revision ) {
00457 $this->addTag( 'tp:tag', $revision );
00458 }
00459
00466 protected function addTag( $tag, $revision, $value = null ) {
00467 $dbw = wfGetDB( DB_MASTER );
00468
00469 $aid = $this->getTitle()->getArticleID();
00470
00471 if ( is_object( $revision ) ) {
00472 throw new MWException( 'Got object, excepted id' );
00473 }
00474
00475 $conds = array(
00476 'rt_page' => $aid,
00477 'rt_type' => RevTag::getType( $tag ),
00478 'rt_revision' => $revision
00479 );
00480 $dbw->delete( 'revtag', $conds, __METHOD__ );
00481
00482 if ( $value !== null ) {
00483 $conds['rt_value'] = serialize( implode( '|', $value ) );
00484 }
00485
00486 $dbw->insert( 'revtag', $conds, __METHOD__ );
00487
00488 self::$tagCache[$aid][$tag] = $revision;
00489 }
00490
00495 public function getMarkedTag() {
00496 return $this->getTag( 'tp:mark' );
00497 }
00498
00503 public function getReadyTag() {
00504 return $this->getTag( 'tp:tag' );
00505 }
00506
00511 public function unmarkTranslatablePage() {
00512 $aid = $this->getTitle()->getArticleID();
00513
00514 $dbw = wfGetDB( DB_MASTER );
00515 $conds = array(
00516 'rt_page' => $aid,
00517 'rt_type' => array(
00518 RevTag::getType( 'tp:mark' ),
00519 RevTag::getType( 'tp:tag' ),
00520 ),
00521 );
00522
00523 $dbw->delete( 'revtag', $conds, __METHOD__ );
00524 $dbw->delete( 'translate_sections', array( 'trs_page' => $aid ), __METHOD__ );
00525 unset( self::$tagCache[$aid] );
00526 }
00527
00533 protected function getTag( $tag, $dbt = DB_SLAVE ) {
00534 if ( !$this->getTitle()->exists() ) {
00535 return false;
00536 }
00537
00538 $aid = $this->getTitle()->getArticleID();
00539
00540 if ( isset( self::$tagCache[$aid][$tag] ) ) {
00541 return self::$tagCache[$aid][$tag];
00542 }
00543
00544 $db = wfGetDB( $dbt );
00545
00546 $conds = array(
00547 'rt_page' => $aid,
00548 'rt_type' => RevTag::getType( $tag ),
00549 );
00550
00551 $options = array( 'ORDER BY' => 'rt_revision DESC' );
00552
00553
00554 $tagRevision = $db->selectField( 'revtag', 'rt_revision', $conds, __METHOD__, $options );
00555 if ( $tagRevision !== false ) {
00556 return self::$tagCache[$aid][$tag] = intval( $tagRevision );
00557 } else {
00558 return self::$tagCache[$aid][$tag] = false;
00559 }
00560 }
00561
00567 public function getTranslationUrl( $code = false ) {
00568 $params = array(
00569 'group' => $this->getMessageGroupId(),
00570 'action' => 'page',
00571 'filter' => '',
00572 'language' => $code,
00573 );
00574
00575 $translate = SpecialPage::getTitleFor( 'Translate' );
00576
00577 return $translate->getLocalURL( $params );
00578 }
00579
00580 public function getMarkedRevs() {
00581
00582 $db = wfGetDB( DB_MASTER );
00583
00584 $fields = array( 'rt_revision', 'rt_value' );
00585 $conds = array(
00586 'rt_page' => $this->getTitle()->getArticleID(),
00587 'rt_type' => RevTag::getType( 'tp:mark' ),
00588 );
00589 $options = array( 'ORDER BY' => 'rt_revision DESC' );
00590
00591 return $db->select( 'revtag', $fields, $conds, __METHOD__, $options );
00592 }
00593
00598 public function getTranslationPages() {
00599
00600 $dbr = wfGetDB( DB_MASTER );
00601 $prefix = $this->getTitle()->getDBkey() . '/';
00602 $likePattern = $dbr->buildLike( $prefix, $dbr->anyString() );
00603 $res = $dbr->select(
00604 'page',
00605 array( 'page_namespace', 'page_title' ),
00606 array(
00607 'page_namespace' => $this->getTitle()->getNamespace(),
00608 "page_title $likePattern"
00609 ),
00610 __METHOD__
00611 );
00612
00613 $titles = TitleArray::newFromResult( $res );
00614 $filtered = array();
00615
00616
00617 $codes = Language::getLanguageNames( false );
00618 $prefix = $this->getTitle()->getText();
00620 foreach ( $titles as $title ) {
00621 list( $name, $code ) = TranslateUtils::figureMessage( $title->getText() );
00622 if ( !isset( $codes[$code] ) || $name !== $prefix ) {
00623 continue;
00624 }
00625 $filtered[] = $title;
00626 }
00627
00628 return $filtered;
00629 }
00630
00636 protected function getSections() {
00637 $dbw = wfGetDB( DB_MASTER );
00638 $conds = array( 'trs_page' => $this->getTitle()->getArticleID() );
00639 $res = $dbw->select( 'translate_sections', 'trs_key', $conds, __METHOD__ );
00640
00641 $sections = array();
00642 foreach ( $res as $row ) {
00643 $sections[] = $row->trs_key;
00644 }
00645
00646 return $sections;
00647 }
00648
00656 public function getTranslationUnitPages( $set = 'active', $code = false ) {
00657 $dbw = wfGetDB( DB_MASTER );
00658 $base = $this->getTitle()->getPrefixedDBKey();
00659
00660 $baseLength = strlen( $base ) + 1;
00661
00662 if ( $code !== false ) {
00663 $like = $dbw->buildLike( "$base/", $dbw->anyString(), "/$code" );
00664 } else {
00665 $like = $dbw->buildLike( "$base/", $dbw->anyString() );
00666 }
00667
00668 $fields = array( 'page_namespace', 'page_title' );
00669 $conds = array(
00670 'page_namespace' => NS_TRANSLATIONS,
00671 'page_title ' . $like
00672 );
00673 $res = $dbw->select( 'page', $fields, $conds, __METHOD__ );
00674
00675
00676
00677
00678
00679 $sections = array_flip( $this->getSections() );
00680 $units = array();
00681 foreach ( $res as $row ) {
00682 $title = Title::newFromRow( $row );
00683
00684
00685
00686 $handle = new MessageHandle( $title );
00687 $key = substr( $handle->getKey(), $baseLength );
00688 if ( strpos( $key, '/' ) !== false ) {
00689
00690 continue;
00691 }
00692
00693
00694 if ( $set === 'active' && !isset( $sections[$key] ) ) {
00695 continue;
00696 }
00697
00698
00699 $units[] = $title;
00700 }
00701
00702 return $units;
00703 }
00704
00709 public function getTranslationPercentages() {
00710
00711 $group = $this->getMessageGroup();
00712 if ( !$group instanceof WikiPageMessageGroup ) {
00713 return array();
00714 }
00715
00716 $titles = $this->getTranslationPages();
00717 $temp = MessageGroupStats::forGroup( $this->getMessageGroupId() );
00718 $stats = array();
00719
00720 foreach ( $titles as $t ) {
00721 $handle = new MessageHandle( $t );
00722 $code = $handle->getCode();
00723
00724
00725
00726 $stats[$code] = 0.00;
00727 if ( isset( $temp[$code] ) && $temp[$code][MessageGroupStats::TOTAL] > 0 ) {
00728 $total = $temp[$code][MessageGroupStats::TOTAL];
00729 $translated = $temp[$code][MessageGroupStats::TRANSLATED];
00730 $percentage = $translated / $total;
00731 $stats[$code] = sprintf( '%.2f', $percentage );
00732 }
00733 }
00734
00735
00736 $stats[$this->getSourceLanguageCode()] = 1.00;
00737
00738 return $stats;
00739 }
00740
00741 public function getTransRev( $suffix ) {
00742 $title = Title::makeTitle( NS_TRANSLATIONS, $suffix );
00743
00744
00745 $db = wfGetDB( DB_MASTER );
00746 $fields = 'rt_value';
00747 $conds = array(
00748 'rt_page' => $title->getArticleID(),
00749 'rt_type' => RevTag::getType( 'tp:transver' ),
00750 );
00751 $options = array( 'ORDER BY' => 'rt_revision DESC' );
00752
00753 return $db->selectField( 'revtag', $fields, $conds, __METHOD__, $options );
00754 }
00755
00760 public static function isTranslationPage( Title $title ) {
00761 $handle = new MessageHandle( $title );
00762 $key = $handle->getKey();
00763 $code = $handle->getCode();
00764
00765 if ( $key === '' || $code === '' ) {
00766 return false;
00767 }
00768
00769 $codes = Language::getLanguageNames( false );
00770 global $wgTranslateDocumentationLanguageCode;
00771 unset( $codes[$wgTranslateDocumentationLanguageCode] );
00772
00773 if ( !isset( $codes[$code] ) ) {
00774 return false;
00775 }
00776
00777 $newtitle = self::changeTitleText( $title, $key );
00778
00779 if ( !$newtitle ) {
00780 return false;
00781 }
00782
00783 $page = TranslatablePage::newFromTitle( $newtitle );
00784
00785 if ( $page->getMarkedTag() === false ) {
00786 return false;
00787 }
00788
00789 return $page;
00790 }
00791
00792 protected static function changeTitleText( Title $title, $text ) {
00793 return Title::makeTitleSafe( $title->getNamespace(), $text );
00794 }
00795
00800 public static function isSourcePage( Title $title ) {
00801 static $cache = null;
00802
00803 $cacheObj = wfGetCache( CACHE_ANYTHING );
00804 $cacheKey = wfMemcKey( 'pagetranslation', 'sourcepages' );
00805
00806 if ( $cache === null ) {
00807 $cache = $cacheObj->get( $cacheKey );
00808 }
00809 if ( !is_array( $cache ) ) {
00810 $cache = self::getTranslatablePages();
00811 $cacheObj->set( $cacheKey, $cache, 60 * 5 );
00812 }
00813
00814 return in_array( $title->getArticleID(), $cache );
00815 }
00816
00820 public static function getTranslatablePages() {
00821
00822 $dbr = wfGetDB( DB_MASTER );
00823
00824 $tables = array( 'revtag', 'page' );
00825 $fields = 'rt_page';
00826 $conds = array(
00827 'rt_page = page_id',
00828 'rt_revision = page_latest',
00829 'rt_type' => array( RevTag::getType( 'tp:mark' ), RevTag::getType( 'tp:tag' ) ),
00830 );
00831 $options = array( 'GROUP BY' => 'rt_page' );
00832
00833 $res = $dbr->select( $tables, $fields, $conds, __METHOD__, $options );
00834 $results = array();
00835 foreach ( $res as $row ) {
00836 $results[] = $row->rt_page;
00837 }
00838
00839 return $results;
00840 }
00841 }