r50377 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r50376‎ | r50377 | r50378 >
Date:18:20, 9 May 2009
Author:nikerabbit
Status:ok
Tags:
Comment:
Page translation feature 0.9
Modified paths:
  • /trunk/extensions/Translate/MessageGroups.php (modified) (history)
  • /trunk/extensions/Translate/PageTranslation.i18n.php (added) (history)
  • /trunk/extensions/Translate/Translate.alias.php (modified) (history)
  • /trunk/extensions/Translate/Translate.css (modified) (history)
  • /trunk/extensions/Translate/Translate.i18n.php (modified) (history)
  • /trunk/extensions/Translate/Translate.php (modified) (history)
  • /trunk/extensions/Translate/TranslateUtils.php (modified) (history)
  • /trunk/extensions/Translate/_autoload.php (modified) (history)
  • /trunk/extensions/Translate/revtags.sql (added) (history)
  • /trunk/extensions/Translate/scripts/cli.inc (modified) (history)
  • /trunk/extensions/Translate/tag/FuzzyJob.php (deleted) (history)
  • /trunk/extensions/Translate/tag/Hooks.php (deleted) (history)
  • /trunk/extensions/Translate/tag/PageTranslationHooks.php (added) (history)
  • /trunk/extensions/Translate/tag/SpecialPageTranslation.php (added) (history)
  • /trunk/extensions/Translate/tag/TPParse.php (added) (history)
  • /trunk/extensions/Translate/tag/TPSection.php (added) (history)
  • /trunk/extensions/Translate/tag/Tag.php (deleted) (history)
  • /trunk/extensions/Translate/tag/TranslatablePage.php (added) (history)
  • /trunk/extensions/Translate/tag/Utils.php (deleted) (history)
  • /trunk/extensions/Translate/translate.sql (added) (history)
  • /trunk/extensions/Translate/utils/MessageIndex.php (added) (history)

Diff [purge]

Index: trunk/extensions/Translate/Translate.alias.php
@@ -17,6 +17,7 @@
1818 'TranslationStats' => array( 'TranslationStats' ),
1919 'Translations' => array( 'Translations' ),
2020 'LanguageStats' => array( 'LanguageStats' ),
 21+ 'PageTranslation' => array( 'PageTranslation' ),
2122 );
2223
2324 /** Afrikaans (Afrikaans) */
Index: trunk/extensions/Translate/Translate.css
@@ -89,4 +89,23 @@
9090 font-family: monospace;
9191 background-color: #F3F3FF;
9292 border: 1px solid black;
 93+}
 94+
 95+.mw-pt-languages table {
 96+ border: 1px solid rgb(170, 170, 170);
 97+ background: rgb(246, 249, 237) none repeat scroll 0% 0%;
 98+ border-collapse: collapse;
 99+ line-height: 1.2;
 100+ width: 100%;
 101+}
 102+
 103+.mw-pt-languages-label {
 104+ border-right: 1px solid rgb(170, 170, 170);
 105+ padding: 0.5em;
 106+ background: rgb(238, 243, 226) none repeat scroll 0% 0%;
 107+ width: 200px;
 108+}
 109+
 110+.mw-pt-languages-list {
 111+ padding: 0.5em;
93112 }
\ No newline at end of file
Index: trunk/extensions/Translate/scripts/cli.inc
@@ -12,6 +12,7 @@
1313
1414 $dir = dirname( __FILE__ ); $IP = "$dir/../../..";
1515 @include("$dir/../../CorePath.php"); // Allow override
 16+define( 'TRANSLATE_CLI', 1 );
1617 require_once( "$IP/maintenance/commandLine.inc" );
1718
1819 function STDOUT( $str, $channel = null, $force = false ) {
Index: trunk/extensions/Translate/PageTranslation.i18n.php
@@ -0,0 +1,53 @@
 2+<?php
 3+/**
 4+ * Translations of Page Translation feature of Translate extension.
 5+ *
 6+ * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
 7+ */
 8+
 9+$messages = array();
 10+
 11+/** English
 12+ * @author Nike
 13+ */
 14+$messages['en'] = array(
 15+ 'pagetranslation' => 'Page translation',
 16+ 'right-pagetranslate' => 'Mark versions of pages for translation',
 17+ 'tpt-section' => 'Section:',
 18+ 'tpt-section-new' => 'New section:',
 19+
 20+ 'tpt-diff-old' => 'Previous text',
 21+ 'tpt-diff-new' => 'New text',
 22+ 'tpt-submit' => 'Mark this version for translation',
 23+
 24+ # Specific page on the special page
 25+ 'tpt-badtitle' => 'Page name given ($1) is not a valid title',
 26+ 'tpt-oldrevision' => '$2 is not the latest version of the page $1. Only latest versions can be marked up for translating.',
 27+ 'tpt-notsuitable' => 'Page $1 is not suitable for translation. Please make sure it has <nowiki><translate></nowiki>-tags and has a valid syntax.',
 28+ 'tpt-saveok' => '$1 has been marked up for translation with $2 translatable sections. The page can now be <span class="plainlinks">[$3 translated]</span>.',
 29+ 'tpt-badsect' => '"$1" is not a valid name for section $2.',
 30+ 'tpt-deletedsections' => 'The following sections wil no longer be used',
 31+ 'tpt-showpage-intro' => 'Below are listed new, existing and deleted sections. Before marking this version for translation, please check that the changes to sections are minimised to avoid unnecessary work for translations.',
 32+ 'tpt-mark-summary' => 'Marked this version for translation',
 33+ 'tpt-edit-failed' => 'Could not update the page: $1',
 34+ 'tpt-insert-failed' => 'Could not add sections to the database.',
 35+ 'tpt-already-marked' => 'The latest version of this page is already marked for translation.',
 36+
 37+ # Page list on the special page
 38+ 'tpt-old-pages' => 'Some version of these pages have been marked for translation.',
 39+ 'tpt-new-pages' => 'These pages contain text with translation tags, but no version of these pages are currently marked for translation.',
 40+ 'tpt-rev-latest' => 'latest version',
 41+ 'tpt-rev-old' => 'version $1',
 42+ 'tpt-rev-mark-new' => 'mark this version for translation',
 43+ 'tpt-translate-this' => 'translate this page',
 44+
 45+ # Source and translation page headers
 46+ 'translate-tag-translate-link-desc' => 'Translate this page',
 47+ 'translate-tag-markthis' => 'Mark this page for translation',
 48+ 'translate-tag-legend' => 'Legend:',
 49+ 'translate-tag-legend-fallback' => 'Translation in other language',
 50+ 'translate-tag-legend-fuzzy' => 'Outdated translation',
 51+
 52+ 'tpt-target-page' => 'This page cannot be updated manually. This page is a translation of page [[$1]] and the translation can be updated using [$2 the translation tool].',
 53+ 'tpt-unknown-page' => 'This namespace is reserved for content page translations. The page you are trying to edit does not seem to correspond any page marked for translation.'
 54+);
\ No newline at end of file
Property changes on: trunk/extensions/Translate/PageTranslation.i18n.php
___________________________________________________________________
Added: svn:eol-style
155 + native
Index: trunk/extensions/Translate/Translate.i18n.php
@@ -158,11 +158,6 @@
159159 'translate-tag-category' => 'Translatable pages',
160160 'translate-tag-page-desc' => 'Translation of the wiki page [[:$1]].',
161161
162 - 'translate-tag-translate-link-desc' => 'Translate this page',
163 - 'translate-tag-legend' => 'Legend:',
164 - 'translate-tag-legend-fallback' => 'Translation in other language',
165 - 'translate-tag-legend-fuzzy' => 'Outdated translation',
166 -
167162 'translate-tag-fuzzy-comment' => 'Definition changed by [[User:$1|$1]] in revision $2.',
168163 'translate-tag-fuzzy-reason' => 'Definition changed by "$1" with comment "$3" in $2.',
169164
Index: trunk/extensions/Translate/TranslateUtils.php
@@ -44,7 +44,6 @@
4545 */
4646 public static function fillContents( MessageCollection $messages,
4747 array $namespaces ) {
48 - wfMemIn( __METHOD__ ); wfProfileIn( __METHOD__ );
4948
5049 $titles = array();
5150 foreach ( $messages->keys() as $key ) {
@@ -64,7 +63,6 @@
6564 }
6665 }
6766
68 - wfProfileOut( __METHOD__ ); wfMemOut( __METHOD__ );
6967 }
7068
7169 public static function getMessageContent( $key, $language,
@@ -82,7 +80,6 @@
8381 * @param $namespace Mixed: the number of the namespace to look in for.
8482 */
8583 public static function getContents( $titles, $namespace ) {
86 - wfMemIn( __METHOD__ ); wfProfileIn( __METHOD__ );
8784 $dbr = wfGetDB( DB_SLAVE );
8885 $rows = $dbr->select( array( 'page', 'revision', 'text' ),
8986 array( 'page_title', 'old_text', 'old_flags', 'rev_user_text' ),
@@ -106,11 +103,9 @@
107104 $rows->free();
108105
109106 return $titles;
110 - wfProfileOut( __METHOD__ ); wfMemOut( __METHOD__ );
111107 }
112108
113109 public static function translationChanges( $hours = 24 ) {
114 - wfMemIn( __METHOD__ );
115110 global $wgTranslateMessageNamespaces;
116111
117112 $dbr = wfGetDB( DB_SLAVE );
@@ -137,7 +132,6 @@
138133 $rows[] = $row;
139134 }
140135 $dbr->freeResult( $res );
141 - wfMemOut( __METHOD__ );
142136 return $rows;
143137 }
144138
@@ -256,7 +250,6 @@
257251 }
258252
259253 public static function getLanguageName( $code, $native = false, $language = 'en' ) {
260 - wfMemIn( __METHOD__ );
261254 if ( !$native && is_callable( array( 'LanguageNames', 'getNames' ) ) ) {
262255 $languages = LanguageNames::getNames( $language ,
263256 LanguageNames::FALLBACK_NORMAL,
@@ -279,12 +272,10 @@
280273 break;
281274 }
282275 $code = implode( '-', $parts );
283 - wfMemOut( __METHOD__ );
284276 return isset( $languages[$code] ) ? $languages[$code] . $suffix : false;
285277 }
286278
287279 public static function languageSelector( $language, $selectedId ) {
288 - wfMemIn( __METHOD__ );
289280 global $wgLang;
290281 if ( is_callable( array( 'LanguageNames', 'getNames' ) ) ) {
291282 $languages = LanguageNames::getNames( $language,
@@ -301,7 +292,6 @@
302293 foreach ( $languages as $code => $name ) {
303294 $selector->addOption( "$code - $name", $code );
304295 }
305 - wfMemOut( __METHOD__ );
306296 return $selector->getHTML();
307297 }
308298
@@ -316,7 +306,6 @@
317307 }
318308
319309 public static function messageIndex() {
320 - wfMemIn( __METHOD__ );
321310 $keyToGroup = array();
322311 if ( file_exists( TRANSLATE_INDEXFILE ) ) {
323312 $keyToGroup = unserialize( file_get_contents( TRANSLATE_INDEXFILE ) );
@@ -324,7 +313,6 @@
325314 wfDebug( __METHOD__ . ": Message index missing." );
326315 }
327316
328 - wfMemOut( __METHOD__ );
329317 return $keyToGroup;
330318 }
331319
Index: trunk/extensions/Translate/revtags.sql
@@ -0,0 +1,27 @@
 2+-- These tables could go into core someday
 3+
 4+-- Revision tag type list
 5+CREATE TABLE /*$wgDBprefix*/revtag_type (
 6+ rtt_id int not null primary key auto_increment,
 7+ rtt_name varbinary(60) not null
 8+) /*$wgDBTableOptions*/;
 9+CREATE UNIQUE INDEX /*i*/rtt_name ON /*$wgDBprefix*/revtag_type (rtt_name);
 10+
 11+-- Revision tags
 12+CREATE TABLE /*$wgDBprefix*/revtag (
 13+ -- Link to revtag_type.rtt_id
 14+ rt_type int not null,
 15+
 16+ -- Link to page.page_id
 17+ rt_page int not null,
 18+
 19+ -- Link to revision.rev_id
 20+ rt_revision int not null,
 21+
 22+ rt_value blob null
 23+) /*$wgDBTableOptions*/;
 24+-- Index for finding all revisions in a page with a given tag
 25+CREATE INDEX /*i*/rt_type_page_revision ON /*$wgDBprefix*/revtag
 26+(rt_type, rt_page, rt_revision);
 27+-- Index for finding the tags on a given revision
 28+CREATE INDEX /*i*/rt_revision_type ON /*$wgDBprefix*/revtag (rt_revision, rt_type);
\ No newline at end of file
Property changes on: trunk/extensions/Translate/revtags.sql
___________________________________________________________________
Added: svn:eol-style
129 + native
Index: trunk/extensions/Translate/MessageGroups.php
@@ -593,12 +593,28 @@
594594 throw new MWException( 'Invalid title' );
595595 }
596596 $this->title = $title;
597 - $this->namespaces = array( $title->getNamespace(), $title->getNamespace() + 1 );
 597+ $this->namespaces = array( NS_TRANSLATIONS, NS_TRANSLATIONS_TALK );
598598
599599 }
600600
601601 public function getDefinitions() {
602 - return TranslateTag::parseSectionDefinitions( $this->title, $this->namespaces );
 602+ $dbr = wfGetDB( DB_SLAVE );
 603+ $tables = 'translate_sections';
 604+ $vars = array( 'trs_key', 'trs_text' );
 605+ $conds = array( 'trs_page' => $this->title->getArticleId() );
 606+ $res = $dbr->select( $tables, $vars, $conds, __METHOD__ );
 607+
 608+ $defs = array();
 609+ $prefix = $this->title->getPrefixedText() . '/';
 610+ foreach ( $res as $r ) {
 611+ $defs[$r->trs_key] = $r->trs_text;
 612+ }
 613+ // Some hacks to get nice order for the messages
 614+ ksort( $defs );
 615+ $new_defs = array();
 616+ foreach ( $defs as $k => $v ) $new_defs[$prefix.$k] = $v;
 617+
 618+ return $new_defs;
603619 }
604620
605621 public function load( $code ) {
@@ -638,20 +654,26 @@
639655 $a->addAll();
640656 }
641657
642 - global $wgTranslateCategory, $wgTranslateCC;
643 -/* wfLoadExtensionMessages( 'Translate' );
644 - $cat = Category::newFromName( wfMsgForContent( 'translate-tag-category' ) );
645 - $titles = $cat->getMembers();
646 - foreach ( $titles as $t ) {
647 - $title = $t->getPrefixedText();
648 - $id = "page|$title";
649 - $wgTranslateCC[$id] = new WikiPageMessageGroup( $id, $title );
650 - $wgTranslateCC[$id]->setLabel( $title );
651 - $wgTranslateCC[$id]->setDescription( wfMsgNoTrans( 'translate-tag-page-desc', $title ) );
 658+ global $wgTranslateCC;
652659
653 - }*/
 660+ global $wgEnablePageTranslation;
 661+ if ( $wgEnablePageTranslation ) {
 662+ $dbr = wfGetDB( DB_SLAVE );
 663+
 664+ $tables = array( 'page', 'revtag', 'revtag_type' );
 665+ $vars = array( 'page_id', 'page_namespace', 'page_title', 'rt_revision' );
 666+ $conds = array( 'page_id=rt_page', 'rtt_id=rt_type', 'rtt_name' => 'tp:mark' );
 667+ $options = array( 'GROUP BY' => 'page_id' );
 668+ $res = $dbr->select( $tables, $vars, $conds, __METHOD__ );
 669+ foreach ( $res as $r ) {
 670+ $title = Title::makeTitle( $r->page_namespace, $r->page_title )->getPrefixedText();
 671+ $id = "page|$title";
 672+ $wgTranslateCC[$id] = new WikiPageMessageGroup( $id, $title );
 673+ $wgTranslateCC[$id]->setLabel( $title );
 674+ $wgTranslateCC[$id]->setDescription( wfMsgNoTrans( 'translate-tag-page-desc', $title ) );
 675+ }
 676+ }
654677
655 - global $wgTranslateCC;
656678 wfRunHooks( 'TranslatePostInitGroups', array( &$wgTranslateCC ) );
657679 $loaded = true;
658680 }
Index: trunk/extensions/Translate/tag/Utils.php
@@ -1,180 +0,0 @@
2 -<?php
3 -
4 -class TranslateTagUtils {
5 - const T_SOURCE = 1;
6 - const T_TRANSLATION = 2;
7 - const T_ANY = 3;
8 -
9 - const STATUSEXPIRY = 600; // in seconds
10 -
11 - public static function isTagPage( Title $title, $type = self::T_ANY ) {
12 - list( $key, $code ) = self::keyAndCode( $title );
13 -
14 - if ( $type === self::T_SOURCE && $code !== false ) return false;
15 - if ( $type === self::T_TRANSLATION && $code === false ) return false;
16 -
17 -
18 - static $members = null;
19 - if ( $members === null ) {
20 - wfLoadExtensionMessages( 'Translate' );
21 - $cat = Category::newFromName( wfMsgForContent( 'translate-tag-category' ) );
22 - $members = $cat->getMembers();
23 - }
24 -
25 - foreach ( $members as $member ) {
26 - if ( $member->getNamespace() === $title->getNamespace() ) {
27 - if ( $member->getDBkey() === $key ) return true;
28 - }
29 - }
30 - return false;
31 - }
32 -
33 - public static function keyAndCode( Title $title ) {
34 - $dbkey = $title->getDBkey();
35 - $code = false;
36 -
37 - $pos = strrpos( $dbkey, '/' );
38 - if ( $pos !== false ) {
39 - $code = substr( $dbkey, $pos + 1 );
40 - $key = substr( $dbkey, 0, $pos );
41 - if ( self::isValidCode( $code ) ) {
42 - return array( $key, $code );
43 - }
44 - }
45 -
46 - return array( $dbkey, false );
47 - }
48 -
49 - public static function isValidCode( $code ) {
50 - $codes = Language::getLanguageNames();
51 - return isset( $codes[$code] );
52 - }
53 -
54 - /**
55 - * Returns a database resource which can be looped and text can be fetched via
56 - * Revision::getRevisionText( $row ).
57 - */
58 - public static function getPageContent( $namespace, $pages ) {
59 - $dbr = wfGetDB( DB_SLAVE );
60 - $rows = $dbr->select( array( 'page', 'revision', 'text' ),
61 - array( 'page_title', 'old_text', 'old_flags' ),
62 - array(
63 - 'page_is_redirect' => 0,
64 - 'page_namespace' => $namespace,
65 - 'page_latest=rev_id',
66 - 'rev_text_id=old_id',
67 - 'page_title' => $pages,
68 - ),
69 - __METHOD__
70 - );
71 -
72 - return $rows;
73 - }
74 -
75 - public static function getTagPageSource( Title $title ) {
76 - $text = null;
77 - $res = self::getPageContent( $title->getNamespace(), $title->getDBkey() );
78 -
79 - foreach ( $res as $_ ) {
80 - $text = Revision::getRevisionText( $_ );
81 - break;
82 - }
83 -
84 - return $text;
85 - }
86 -
87 - public static function codefyTitle( Title $title, $code ) {
88 - global $wgContLang;
89 - $namespace = $title->getNamespace();
90 - $dbkey = $title->getDBkey();
91 - if ( $code !== $wgContLang->getCode() ) $dbkey .= "/$code";
92 - return Title::makeTitle( $namespace, "$dbkey" );
93 - }
94 -
95 - public static function deCodefy( Title $title ) {
96 - // Don't remove trail if source page
97 - $type = self::T_SOURCE;
98 - if ( self::isTagPage( $title, $type ) ) return $title;
99 -
100 - list( , $code ) = self::keyAndCode( $title );
101 - $namespace = $title->getNamespace();
102 - $dbkey = $title->getDBkey();
103 - $suflen = strlen( $code );
104 - if ( $suflen ) $dbkey = substr( $dbkey, 0, - ( $suflen + 1 ) );
105 - return Title::makeTitle( $namespace, $dbkey );
106 - }
107 -
108 - public static function getTranslationPercent( $pages, $code, $namespace ) {
109 - $pages = self::mapAppend( $pages, "/$code" );
110 - $n = count( $pages );
111 - $sectionSize = 1 / $n;
112 - $total = 0;
113 -
114 - $res = self::getPageContent( $namespace, $pages );
115 - foreach ( $res as $_ ) {
116 - $text = Revision::getRevisionText( $_ );
117 - if ( strpos( $text, TRANSLATE_FUZZY ) !== false ) {
118 - $total += $sectionSize / 2;
119 - } else {
120 - $total += $sectionSize;
121 - }
122 - }
123 -
124 - return $total;
125 - }
126 -
127 - public static function getPercentages( Title $title ) {
128 - // Check if relevant at all
129 - if ( !TranslateTagUtils::isTagPage( $title ) ) return false;
130 -
131 - // Normalise title, just to be sure
132 - $title = self::deCodefy( $title );
133 -
134 - // Check the memory cache, as this is very slow to calculate
135 - global $wgMemc;
136 - $memcKey = wfMemcKey( 'translate', 'status', $title->getPrefixedText() );
137 - $cache = $wgMemc->get( $memcKey );
138 - if ( is_array( $cache ) ) return $cache;
139 -
140 - // Fetch the available translation pages from database
141 - $dbr = wfGetDB( DB_SLAVE );
142 - $likePattern = $dbr->escapeLike( $title->getDBkey() ) . '/%%';
143 - $res = $dbr->select(
144 - 'page',
145 - array( 'page_namespace', 'page_title' ),
146 - array(
147 - 'page_namespace' => $title->getNamespace(),
148 - "page_title LIKE '$likePattern'"
149 - ), __METHOD__ );
150 -
151 - $titles = TitleArray::newFromResult( $res );
152 -
153 - // Calculate percentages for the available translations
154 - $tag = TranslateTag::newFromTitle( $title );
155 - $pages = $tag->getSectionPages();
156 - $namespace = $tag->getNamespace( $title );
157 -
158 -
159 - $temp = array();
160 -
161 -
162 - $type = TranslateTagUtils::T_TRANSLATION;
163 - foreach ( $titles as $_ ) {
164 - if ( !TranslateTagUtils::isTagPage( $_, $type ) ) continue;
165 - list( , $code ) = TranslateTagUtils::keyAndcode( $_ );
166 - $percent = TranslateTagUtils::getTranslationPercent( $pages, $code, $namespace );
167 - $temp[$code] = $percent;
168 - }
169 - $temp['en'] = '1';
170 -
171 - // Ideally there would be some kind of purging here
172 - $wgMemc->set( $memcKey, $temp, self::STATUSEXPIRY );
173 - return $temp;
174 - }
175 -
176 - public static function mapAppend( array $array, $suffix ) {
177 - $function = create_function( '$_', "return \$_ . '$suffix';" );
178 - return array_map( $function, $array );
179 - }
180 -
181 -}
Index: trunk/extensions/Translate/tag/FuzzyJob.php
@@ -1,93 +0,0 @@
2 -<?php
3 -
4 -/**
5 - * Job for making translation fuzzy when the definition changes.
6 - *
7 - * @addtogroup Extensions
8 - *
9 - * @author Niklas Laxström
10 - * @copyright Copyright © 2008, Niklas Laxström
11 - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
12 - */
13 -/*
14 - * Based on Tim Starling's DoubleRedirectJob.php
15 - */
16 -class FuzzyJob extends Job {
17 - static $user;
18 -
19 - public static function fuzzyPages( $reason, $comment, Title $prefix ) {
20 - $dbr = wfGetDB( DB_SLAVE );
21 - $likePattern = $dbr->escapeLike( $prefix->getDBkey() ) . '/%%';
22 -
23 - $res = $dbr->select(
24 - 'page',
25 - array( 'page_namespace', 'page_title' ),
26 - array(
27 - 'page_namespace' => $prefix->getNamespace(),
28 - "page_title LIKE '$likePattern'"
29 - ), __METHOD__ );
30 - // Nothind to update
31 - if ( !$res->numRows() ) return;
32 -
33 - $jobs = array();
34 - foreach ( $res as $row ) {
35 - $title = Title::makeTitle( $row->page_namespace, $row->page_title );
36 - // Should not happen, but who knows
37 - if ( !$title ) continue;
38 -
39 - $jobs[] = new self( $title, array(
40 - 'reason' => $reason,
41 - 'comment' => $comment ) );
42 - }
43 - Job::batchInsert( $jobs );
44 - }
45 -
46 - function __construct( $title, $params = false, $id = 0 ) {
47 - parent::__construct( __CLASS__, $title, $params, $id );
48 - $this->reason = $params['reason'];
49 - $this->comment = $params['comment'];
50 - $this->removeDuplicates = false;
51 - }
52 -
53 - function run() {
54 - $targetRev = Revision::newFromTitle( $this->title );
55 - if ( !$targetRev ) {
56 - wfDebug( __METHOD__ . ": target page deleted, ignoring\n" );
57 - return true;
58 - }
59 - $text = $targetRev->getText();
60 -
61 - # Add fuzzy tag if there isn't already one
62 - if ( strpos( $text, TRANSLATE_FUZZY ) === false ) {
63 - $text = TRANSLATE_FUZZY . $text;
64 - }
65 -
66 - # Add a comment to help translators to identify what has changed
67 - $text .= "\n" . $this->reason;
68 -
69 - # Save it
70 - global $wgUser;
71 - $oldUser = $wgUser;
72 - $wgUser = $this->getUser();
73 - $article = new Article( $this->title );
74 - $article->doEdit( $text, $this->comment, EDIT_UPDATE | EDIT_FORCE_BOT );
75 - $wgUser = $oldUser;
76 -
77 - return true;
78 - }
79 -
80 - /**
81 - * Get a user object for doing edits, from a request-lifetime cache
82 - */
83 - function getUser() {
84 - if ( !self::$user ) {
85 - global $wgTranslateFuzzyBotName;
86 - self::$user = User::newFromName( $wgTranslateFuzzyBotName, false );
87 - if ( !self::$user->isLoggedIn() ) {
88 - self::$user->addToDatabase();
89 - }
90 - }
91 - return self::$user;
92 - }
93 -}
94 -
Index: trunk/extensions/Translate/tag/Hooks.php
@@ -1,224 +0,0 @@
2 -<?php
3 -
4 -class TranslateTagHooks {
5 -
6 - public static function disableEdit( $title, $user, $action, &$errors ) {
7 - if ( $action !== 'edit' ) return true;
8 -
9 - $type = TranslateTagUtils::T_TRANSLATION;
10 - if ( TranslateTagUtils::isTagPage( $title, $type ) ) {
11 - wfLoadExtensionMessages( 'Translate' );
12 -
13 - $sourceTitle = Title::makeTitle(
14 - $title->getNamespace(),
15 - // TODO: breaks if the page being translated is a subpage
16 - $title->getBaseText()
17 - );
18 -
19 - list( , $code ) = TranslateTagUtils::keyAndcode( $title );
20 - $par = array(
21 - 'group' => 'page|' . $sourceTitle->getPrefixedText(),
22 - 'language' => $code,
23 - 'task' => 'view',
24 - );
25 - $translate = SpecialPage::getTitleFor( 'Translate' );
26 -
27 - $errors[] = array( 'translate-tag-noedit',
28 - $sourceTitle->getFullURL( 'action=edit' ),
29 - $translate->getFullUrl( wfArrayToCgi( $par ) )
30 - );
31 - return false;
32 - }
33 -
34 - return true;
35 - }
36 -
37 - public static function renderTagPage( $parser, &$text, $state ) {
38 - $title = $parser->getTitle();
39 - list( , $code ) = TranslateTagUtils::keyAndcode( $title );
40 -
41 - if ( strpos( $text, '</translate>' ) !== false ) {
42 - $tag = TranslateTag::getInstance();
43 - $text = $tag->renderPage( $text, $title );
44 -
45 - wfLoadExtensionMessages( 'Translate' );
46 - $cat = wfMsgForContent( 'translate-tag-category' );
47 - $text .= " [[Category:$cat]]";
48 - $text = $tag->getHeader( $title ) . $text;
49 - } elseif ( strpos( $text, '<translate />' ) !== false ) {
50 - $text = str_replace( '<translate />', '', $text );
51 -
52 - list( , $code ) = TranslateTagUtils::keyAndCode( $title );
53 - $title = TranslateTagUtils::deCodefy( $title );
54 -
55 - $tag = TranslateTag::getInstance();
56 - $text = $tag->renderPage( $text, $title );
57 - $text = $tag->getHeader( $title, $code ) . $text;
58 -
59 - $lobj = Language::factory( $code );
60 - $parser->mOptions->setTargetLanguage( $lobj );
61 - }
62 -
63 - return true;
64 - }
65 -
66 - public static function injectCss( $outputpage, $text ) {
67 - TranslateUtils::injectCSS();
68 - return true;
69 - }
70 -
71 - public static function onSave( $article, $user, $text, $summary, $minor,
72 - $_, $_, $flags, $revision ) {
73 - global $wgTranslateFuzzyBotName;
74 -
75 - // Do not trigger renders for bots
76 - // It is more efficient to update
77 - if ( $user->getName() === $wgTranslateFuzzyBotName ) return true;
78 -
79 - $flags |= EDIT_SUPPRESS_RC; // No point listing them twice
80 - $flags &= ~EDIT_NEW & ~EDIT_UPDATE; // We don't know
81 -
82 - $title = $article->getTitle();
83 - self::updateTranslationChange( $title, $flags, $summary );
84 - self::updateSourceChange( $title, $flags, $summary );
85 -
86 - return true;
87 -
88 - }
89 -
90 - public static function updateTranslationChange( Title $translation, $flags, $summary ) {
91 - list( $key, $code ) = TranslateTagUtils::keyAndcode( $translation );
92 -
93 - // Figure out group
94 - $namespace = $translation->getNamespace();
95 - $groupKey = TranslateUtils::messageKeyToGroup( $namespace, $key );
96 - $group = MessageGroups::getGroup( $groupKey );
97 - if ( !$group instanceof WikiPageMessageGroup ) return;
98 -
99 - $source = $group->title;
100 - $target = TranslateTagUtils::codefyTitle( $group->title, $code );
101 -
102 - global $wgUser;
103 - $user = $wgUser->getName();
104 - RenderJob::renderPage( $source, $target, $user, $summary, $flags, true );
105 - }
106 -
107 - public static function updateSourceChange( Title $source, $flags, $summary ) {
108 - global $wgUser;
109 - $user = $wgUser->getName();
110 -
111 - $type = TranslateTagUtils::T_SOURCE;
112 -
113 - if ( !TranslateTagUtils::isTagPage( $source, $type ) ) return true;
114 -
115 - $dbr = wfGetDB( DB_SLAVE );
116 - $likePattern = $dbr->escapeLike( $source->getDBkey() ) . '/%%';
117 - $res = $dbr->select(
118 - 'page',
119 - array( 'page_namespace', 'page_title' ),
120 - array(
121 - 'page_namespace' => $source->getNamespace(),
122 - "page_title LIKE '$likePattern'"
123 - ), __METHOD__ );
124 -
125 - $titles = TitleArray::newFromResult( $res );
126 - $type = TranslateTagUtils::T_TRANSLATION;
127 - foreach ( $titles as $target ) {
128 - if ( !TranslateTagUtils::isTagPage( $target, $type ) ) continue;
129 - RenderJob::renderPage( $source, $target, $user, $summary, $flags );
130 - }
131 - }
132 -
133 - public static function addSidebar( $out, $tpl ) {
134 - $title = TranslateTagUtils::deCodefy( $out->mTitle );
135 - $status = TranslateTagUtils::getPercentages( $title );
136 - if ( !$status ) return true;
137 -
138 - global $wgLang;
139 -
140 - // Sort by translation percentage
141 - arsort( $status, SORT_NUMERIC );
142 -
143 - foreach ( $status as $code => $percent ) {
144 - $name = TranslateUtils::getLanguageName( $code, false, $wgLang->getCode() );
145 - $percent = $wgLang->formatNum( round( 100 * $percent ) );
146 - $label = "$name ($percent%)";
147 -
148 - $_title = TranslateTagUtils::codefyTitle( $title, $code );
149 -
150 - $items[] = array(
151 - 'text' => $label,
152 - 'href' => $_title->getFullURL(),
153 - 'id' => 'foo',
154 - );
155 - }
156 -
157 - $sidebar = $out->buildSidebar();
158 - $sidebar['TRANSLATIONS'] = $items;
159 -
160 - $tpl->set( 'sidebar', $sidebar );
161 -
162 - return true;
163 - }
164 -
165 - public static function languages( $data, $params, $parser ) {
166 - $title = TranslateTagUtils::deCodefy( $parser->getTitle() );
167 - $status = TranslateTagUtils::getPercentages( $title );
168 - if ( !$status ) return '';
169 -
170 - global $wgLang;
171 -
172 - // Sort by language code
173 - ksort( $status );
174 -
175 - // $lobj = $parser->getFunctionLang();
176 - $sk = $parser->mOptions->getSkin();
177 - $legend = wfMsg( 'otherlanguages' );
178 -
179 - global $wgTranslateCssLocation;
180 -
181 - $languages = array();
182 - foreach ( $status as $code => $percent ) {
183 - $name = TranslateUtils::getLanguageName( $code, false, $wgLang->getCode() );
184 -
185 - $percent *= 100;
186 - if ( $percent < 20 ) $image = 1;
187 - elseif ( $percent < 40 ) $image = 2;
188 - elseif ( $percent < 60 ) $image = 3;
189 - elseif ( $percent < 80 ) $image = 4;
190 - else $image = 5;
191 -
192 - $percent = Xml::element( 'img', array(
193 - 'src' => "$wgTranslateCssLocation/images/prog-$image.png"
194 - ) );
195 - $label = "$name $percent";
196 -
197 - $_title = TranslateTagUtils::codefyTitle( $title, $code );
198 - $languages[] = $sk->link( $_title, $label );
199 - }
200 -
201 - $languages = implode( '&nbsp;• ', $languages );
202 -
203 - return <<<FOO
204 -<div class="LanguageLinks">
205 -<table style="border: 1px solid rgb(170, 170, 170); background: rgb(246, 249, 237) none repeat scroll 0% 0%; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; border-collapse: collapse; line-height: 1.2;" border="0" cellpadding="2" cellspacing="0" rules="all" width="100%">
206 -
207 -<tbody><tr valign="top">
208 -<td style="border-right: 1px solid rgb(170, 170, 170); padding: 0.5em; background: rgb(238, 243, 226) none repeat scroll 0% 0%; width: 200px; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial;"><b>$legend:</b>
209 -
210 -</td><td style="padding: 0.5em;">$languages</td></tr></tbody></table>
211 -</div>
212 -FOO;
213 - }
214 -
215 - public static function onTemplate( $parser, &$title, &$skip, &$id ) {
216 - global $wgLang;
217 - $type = TranslateTagUtils::T_SOURCE;
218 - if ( TranslateTagUtils::isTagPage( $title, $type ) ) {
219 - $newtitle = TranslateTagUtils::codefyTitle( $title, $wgLang->getCode() );
220 - if ( $newtitle->exists() ) $title = $newtitle;
221 - }
222 - return true;
223 - }
224 -
225 -}
\ No newline at end of file
Index: trunk/extensions/Translate/tag/Tag.php
@@ -1,476 +0,0 @@
2 -<?php
3 -
4 -/**
5 - * Code for handling pages marked with <translate> tag. This class does parsing.
6 - *
7 - * Section: piece of text, separated usually with two new lines, that is used as
8 - * an single translation unit for change tracking and so on.
9 - * Occurance: contents of <translate>..</translate>, which there can be many on
10 - * one page.
11 - *
12 - * @addtogroup Extensions
13 - *
14 - * @author Niklas Laxström
15 - * @copyright Copyright © 2008, Niklas Laxström
16 - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
17 - */
18 -class TranslateTag {
19 - /** Counter for nth <translate>..</translate> invocation to know to which
20 - * occurance each section belongs to, when doing save-time parsing. */
21 - var $invocation = 0;
22 -
23 - // Deprecated, TODO: write a suitable factory functions for replacement
24 - public static function getInstance() {
25 - $obj = new self;
26 - // $obj->reset();
27 - return $obj;
28 - }
29 -
30 - /** Factory for creating a new instance from Title object */
31 - public static function newFromTitle( Title $title ) {
32 - $obj = new self();
33 - $obj->invocation = 0;
34 - $obj->title = $title;
35 -
36 - $text = TranslateTagUtils::getTagPageSource( $title );
37 -
38 - $cb = array( $obj, 'parseMetadata' );
39 - preg_replace_callback( self::METADATA, $cb, $text );
40 - return $obj;
41 - }
42 -
43 - /** Factory for creating a new instance from occurance */
44 - public static function newFromTagContents( &$text, $code ) {
45 - $obj = new self();
46 - $obj->invocation = - 1;
47 - $obj->title = null;
48 - $obj->renderCode = $code;
49 -
50 - $cb = array( $obj, 'parseMetadata' );
51 - preg_replace_callback( self::METADATA, $cb, $text );
52 - return $obj;
53 - }
54 -
55 - // TODO: Move to hook or utils?
56 - // Remember to to use TranslateUtils::injectCSS()
57 - public function getHeader( Title $title, $code = false ) {
58 - global $wgLang;
59 - $par = array(
60 - 'group' => 'page|' . $title->getPrefixedText(),
61 - 'language' => $code ? $code : $wgLang->getCode(),
62 - 'task' => 'view'
63 - );
64 - $translate = SpecialPage::getTitleFor( 'Translate' );
65 - $link = $translate->getFullUrl( wfArrayToCgi( $par ) );
66 -
67 - wfLoadExtensionMessages( 'Translate' );
68 - $linkDesc = wfMsgNoTrans( 'translate-tag-translate-link-desc' );
69 - $legendText = wfMsgNoTrans( 'translate-tag-legend' );
70 - $legendOther = wfMsgNoTrans( 'translate-tag-legend-fallback' );
71 - $legendFuzzy = wfMsgNoTrans( 'translate-tag-legend-fuzzy' );
72 -
73 -
74 - $legend = "<div style=\"font-size: x-small\">";
75 - $legend .= "<span class='plainlinks'>[$link $linkDesc]</span> ";
76 - if ( $code ) {
77 - $legend .= " | $legendText <span class=\"mw-translate-other\">$legendOther</span>";
78 - $legend .= " <span class=\"mw-translate-fuzzy\">$legendFuzzy</span>";
79 - }
80 - // TODO: the following text will of course be removed :)
81 - $legend .= ' | This page is translatable using the experimental wiki page translation feature.</div>';
82 - $legend .= "\n----\n";
83 - return $legend;
84 - }
85 -
86 - // Some regexps
87 - const METADATA = '~\n?<!--TS(.*?)-->\n?~us';
88 - const PATTERN_COMMENT = '~\n?<!--T[=:;](.*?)-->\n?~u';
89 - const PATTERN_TAG = '~(<translate>)\n?(.+?)(</translate>)~us';
90 -
91 - // Renders a translation page to given language
92 - public function renderPage( $text, Title $title, $code = false ) {
93 - $this->renderTitle = $title;
94 - $this->renderCode = $code;
95 - $cb = array( $this, 'parseMetadata' );
96 - preg_replace_callback( self::METADATA, $cb, $text );
97 -
98 - $cb = array( $this, 'replaceTagCb' );
99 - $text = StringUtils::delimiterReplaceCallback( '<translate>', '</translate>', $cb, $text );
100 -
101 - return preg_replace_callback( self::PATTERN_TAG, $cb, $text );
102 - }
103 -
104 - public function replaceTagCb( $matches ) {
105 - return $this->replaceTag( $matches[1] );
106 - }
107 -
108 - /**
109 - * Replaces sections with translations if available, and substitutes variables
110 - */
111 - public function replaceTag( $input ) {
112 - $regex = $this->getSectionRegex();
113 - $matches = array();
114 - preg_match_all( $regex, $input, $matches, PREG_SET_ORDER );
115 - foreach ( $matches as $match ) {
116 - $key = $match['id'];
117 - $section = $match['section'];
118 -
119 - $translation = null;
120 - if ( $this->renderCode ) {
121 - $translation = $this->getContents( $this->renderTitle, $key, $this->renderCode );
122 - }
123 -
124 - if ( $translation !== null ) {
125 - $vars = $this->extractVariablesFromSection( $section );
126 - foreach ( $vars as $v ) {
127 - list( $search, $replace ) = $v;
128 - $translation = str_replace( $search, $replace, $translation );
129 - }
130 -
131 - // Inject the translation in the source by replacing the definition with it
132 - $input = str_replace( $section, $translation, $input );
133 - } else {
134 - // Do in-place replace of variables, copy to keep $section intact for
135 - // the replace later
136 - $replace = $section;
137 - // Replace with newline to avoid eating the newline after header
138 - // and it doesn't hurt because surrounding is trimmed for inlines
139 - $replace = preg_replace( self::PATTERN_COMMENT, "\n", $replace );
140 - $this->extractVariablesFromSection( $replace, 'replace' );
141 -
142 - if ( $this->renderCode ) {
143 - $replace = $this->wrapAndClean( 'mw-translate-other', $replace );
144 - }
145 - $input = str_replace( $section, $replace, $input );
146 - $input = str_replace( $match['holder'], '', $input );
147 - }
148 - }
149 -
150 - // Clean any comments there may be left
151 - $input = preg_replace( self::PATTERN_PLACEHOLDER, '', $input );
152 - $input = preg_replace( self::METADATA, '', $input );
153 -
154 - return trim( $input );
155 -
156 - }
157 -
158 - public function extractVariablesFromSection( &$text, $subst = false ) {
159 - $regex = '~<tvar(?:\|(?P<id>[^>]+))>(?P<value>.*?)</>~u';
160 - $matches = array();
161 - // Quick return
162 - if ( !preg_match_all( $regex, $text, $matches, PREG_SET_ORDER ) ) return array();
163 -
164 - // Extracting
165 - $vars = array();
166 - foreach ( $matches as $match ) {
167 - $id = $match['id']; // Default to provided id
168 - // But if it isn't provided, autonumber them from one onwards
169 - if ( $id === '' ) $id = count( $vars ) ? max( array_keys( $vars ) ) + 1 : 1;
170 - // Index by id, for above to work.
171 - // Store array or replace, replacement for easy replace afterwards
172 - $vars[$id] = array( '$' . $id, $match['value'] );
173 - // If requested, subst them immediately
174 - if ( $subst === 'replace' )
175 - $text = str_replace( $match[0], $match['value'], $text );
176 - elseif ( $subst === 'holder' )
177 - $text = str_replace( $match[0], '$' . $id, $text );
178 - }
179 -
180 - return $vars;
181 - }
182 -
183 - /**
184 - * Fetches translation for a section.
185 - */
186 - public function getContents( Title $title, $key, $code ) {
187 - global $wgContLang;
188 -
189 - $namespace = $this->getNamespace( $title );
190 - $sectionPageName = $this->getTranslationPage( $title, $key, $code );
191 - $sectionTitle = Title::makeTitle( $namespace, $sectionPageName );
192 - if ( !$sectionTitle ) throw new MWException( 'bad title' );
193 -
194 - $revision = Revision::loadFromTitle( wfGetDB( DB_SLAVE ), $sectionTitle );
195 - if ( $revision ) {
196 - $translation = $revision->getText();
197 - if ( strpos( $translation, TRANSLATE_FUZZY ) !== false ) {
198 - $translation = str_replace( TRANSLATE_FUZZY, '', $translation );
199 - $translation = $this->wrapAndClean( 'mw-translate-fuzzy', $translation );
200 - }
201 -
202 - return $translation;
203 - }
204 -
205 - return null;
206 - }
207 -
208 - public function wrapAndClean( $class, $text ) {
209 - $text = trim( $text );
210 -
211 - $tag = 'div';
212 - $sep = "\n";
213 - if ( strpos( $text, "\n" ) === false && strpos( $text, '==' ) === false ) {
214 - $tag = 'span';
215 - $sep = '';
216 - }
217 -
218 - $class = htmlspecialchars( $class );
219 -
220 - return "<$tag class=\"$class\">$sep$text$sep</$tag>";
221 - }
222 -
223 - const PATTERN_SECTION = '~(<!--T:[^-]+-->)(.*?)<!--T;-->~us';
224 - const PATTERN_PLACEHOLDER = '~<!--T:[^-/]+/?-->~us';
225 -
226 - public function getSectionRegex( $taggedOnly = true ) {
227 - $id = '(:? *(?P<holder><!--T:(?P<id>[^-/]+)-->)\n?)';
228 - $end = '(?P<trail>\n{2,}|\s*\z)';
229 - $text = '(?Us:[^\n].*)';
230 - $header = '(?m:(?P<header>(?>={1,6}).+={1,6})[ |\n]?)';
231 -
232 - if ( $taggedOnly ) {
233 - $regex = "(?P<section>$header?$id$text?)$end";
234 - } else {
235 - $regex = "(?P<section>$header?$id?$text?)$end";
236 - // $regex = $text;
237 - }
238 -
239 - return "~$regex~u";
240 - }
241 -
242 - // Deprecated
243 - public function reset() {
244 - $this->sections = array();
245 - $this->placeholders = array();
246 - $this->invocation = 0;
247 - }
248 -
249 - public static function parseSectionDefinitions( Title $title, array &$namespaces ) {
250 - $obj = self::getInstance();
251 -
252 - $defs = array();
253 -
254 - $revision = Revision::newFromTitle( $title );
255 - $pagecontents = $revision->getText();
256 -
257 - $cb = array( $obj, 'parseMetadata' );
258 - preg_replace_callback( self::METADATA, $cb, $pagecontents );
259 -
260 - $matches = array();
261 - preg_match_all( $obj->getSectionRegex(), $pagecontents, $matches, PREG_SET_ORDER );
262 - foreach ( $matches as $match ) {
263 - $key = $match['id'];
264 -
265 - $contents = preg_replace( self::PATTERN_COMMENT, "\n", $match['section'] );
266 - $contents = trim( $contents );
267 - $key = $obj->getTranslationPage( $title, $key );
268 - $obj->extractVariablesFromSection( $contents, 'holder' );
269 - $defs[$key] = $contents;
270 - }
271 -
272 - $ns = $obj->getNamespace( $title );
273 - $namespaces = array( $ns, $ns + 1 );
274 -
275 - return $defs;
276 -
277 - }
278 -
279 - /** Use this to get the location of translations */
280 - public function getTranslationPage( Title $title, $key, $code = false ) {
281 - global $wgTranslateTagTranslationLocation;
282 - list( , $format ) = $wgTranslateTagTranslationLocation;
283 -
284 - // Some data
285 - $ns = $title->getNsText();
286 - $page = $title->getDBkey();
287 - $fullname = $title->getPrefixedDBkey();
288 - $snippet = $this->sections[$key]['page'];
289 -
290 - $search = array( '$NS', '$PAGE', '$FULLNAME', '$KEY', '$SNIPPET' );
291 - $replace = array( $ns, $page, $fullname, $key, $snippet );
292 - $pagename = str_replace( $search, $replace, $format );
293 - if ( $code !== false ) $pagename .= "/$code";
294 -
295 - return $pagename;
296 - }
297 -
298 - /** Use this to get the namespace for page names provided with
299 - * getTranslationPage
300 - */
301 - public function getNamespace( Title $title ) {
302 - global $wgTranslateTagTranslationLocation;
303 - list( $nsId, ) = $wgTranslateTagTranslationLocation;
304 - if ( $nsId === null ) $nsId = $title->getNamespace();
305 - return $nsId;
306 - }
307 -
308 - // Initiate fuzzyjobs on changed sections
309 - public function onArticleSaveComplete(
310 - $article, $user, $text, $summary, $isminor, $_, $_, $flags, $revision
311 - ) {
312 - if ( $revision === null || $isminor ) return true;
313 -
314 - $namespace = $this->getNamespace( $article->getTitle() );
315 -
316 - foreach ( $this->changed as $key ) {
317 - $page = $this->getTranslationPage( $article->getTitle(), $key );
318 - $title = Title::makeTitle( $namespace, $page );
319 - if ( !$title ) continue;
320 -
321 - $summary = str_replace( '-->', '-- >', $summary );
322 -
323 - $url = $article->getTitle()->getFullUrl( 'diff=' . $revision->getId() );
324 - $reason = wfMsgForContent( 'translate-tag-fuzzy-reason', $user->getName(), $url, $summary );
325 - $reason = "<!-- $reason -->";
326 - $comment = wfMsgForContent( 'translate-tag-fuzzy-comment', $user->getName(), $revision->getId() );
327 -
328 - FuzzyJob::fuzzyPages( $reason, $comment, $title );
329 - }
330 - return true;
331 - }
332 -
333 - public static function save( $article, $user, &$text, $summary, $isminor, $iswatch, $section ) {
334 - // Quick escape on normal pages
335 - if ( strpos( $text, '</translate>' ) === false ) return true;
336 -
337 - $obj = self::getInstance();
338 - $obj->reset();
339 -
340 - // Parse existing section mappings
341 - $obj->invocation = 0;
342 - $cb = array( $obj, 'parseMetadata' );
343 - $text = preg_replace_callback( self::METADATA, $cb, $text );
344 -
345 - $obj->changed = array();
346 - $obj->invocation = 0;
347 - $cb = array( $obj, 'saveCb' );
348 - $text = preg_replace_callback( self::PATTERN_TAG, $cb, $text );
349 -
350 - // Trim trailing whitespace. It is not allowed in wikitext and shows up in
351 - // diffs
352 - $text = rtrim( $text );
353 -
354 - if ( count( $obj->changed ) ) {
355 - // Register fuzzier
356 - // We need to do it later, so that we know the revision number
357 - global $wgHooks;
358 - $wgHooks['ArticleSaveComplete'][] = $obj;
359 - }
360 -
361 - return true;
362 - }
363 -
364 - public function saveCb( $matches ) {
365 -
366 - $data = $matches[2];
367 -
368 - // Add sections to unsectioned data
369 - $cb = array( $this, 'saveCbSectionCb' );
370 - $regex = $this->getSectionRegex( false );
371 - $data = preg_replace_callback( $regex, $cb, $data );
372 -
373 - // Add two newlines before metadata so that it wont be parsed as a part of
374 - // the section it is after
375 - $output = $matches[1] . "\n" . $data . "\n\n";
376 - $output .= $this->outputMetadata();
377 - $output .= $matches[3];
378 -
379 - $this->invocation++;
380 - return $output;
381 - }
382 -
383 - public function parseMetadata( $data ) {
384 - $matches = array();
385 - preg_match_all( '~^(.*?)\|(.*?)\|(.*?)$~umD', $data[1], $matches, PREG_SET_ORDER );
386 - foreach ( $matches as $match ) {
387 - $this->sections[$match[1]] = array(
388 - 'hash' => $match[2],
389 - 'page' => $match[3],
390 - 'invo' => $this->invocation,
391 - );
392 - }
393 -
394 - $this->invocation++;
395 - return '';
396 - }
397 -
398 - /**
399 - * Returns list of translation sections in an array.
400 - * @param $code language code for the pages.
401 - */
402 - public function getSectionPages() {
403 - $pages = array();
404 - foreach ( array_keys( $this->sections ) as $key ) {
405 - $pages[] = $this->getTranslationPage( $this->title, $key );
406 - }
407 - return $pages;
408 - }
409 -
410 - public function outputMetadata() {
411 - $s = "<!--TS\n";
412 - foreach ( $this->sections as $key => $section ) {
413 - if ( $section['invo'] !== $this->invocation ) continue;
414 - $s .= "$key|{$section['hash']}|{$section['page']}\n";
415 - }
416 - $s .= "-->\n";
417 - return $s;
418 - }
419 -
420 - public function saveCbSectionCb( array $matches ) {
421 - // Have to do rematch, because this is stupid
422 - preg_match( $this->getSectionRegex( false ), $matches[0], $match );
423 - $section = $match['section'];
424 -
425 - if ( trim( $match[0] ) === '' ) return $match[0];
426 -
427 - if ( $match['holder'] !== '' ) {
428 - $key = $match['id'];
429 - $newhash = self::hash( $match['section'] );
430 - $oldhash = $this->sections[$key]['hash'];
431 -
432 -
433 - if ( $newhash !== $oldhash ) {
434 - $this->changed[] = $key;
435 - }
436 -
437 - $page = @$this->sections[$key]['page'];
438 - // Create page, unless it is already choosen
439 - if ( $page === null ) $page = TranslateUtils::snippet( $section, 30 );
440 -
441 - $array = array(
442 - 'hash' => $newhash,
443 - 'invo' => $this->invocation,
444 - 'page' => $page,
445 - );
446 -
447 - // Update data
448 - $this->sections[$key] = $array;
449 -
450 - return $match[0];
451 - }
452 -
453 - if ( empty( $this->sections ) ) $key = 0;
454 - else $key = max( array_keys( $this->sections ) );
455 -
456 - $this->sections[++$key] = array(
457 - 'hash' => self::hash( $section ),
458 - 'page' => TranslateUtils::snippet( $section, 30 ),
459 - 'invo' => $this->invocation,
460 - );
461 -
462 - $holder = "<!--T:$key-->";
463 -
464 - if ( $match['header'] !== '' ) {
465 - $section = str_replace( $match['header'], $match['header'] . ' ' . $holder, $section );
466 - } else {
467 - $section = $holder . "\n" . $section;
468 - }
469 -
470 - return $section . $match['trail'];
471 - }
472 -
473 - public static function hash( $contents ) {
474 - return sha1( trim( $contents ) );
475 - }
476 -
477 -}
\ No newline at end of file
Index: trunk/extensions/Translate/tag/SpecialPageTranslation.php
@@ -0,0 +1,329 @@
 2+<?php
 3+/**
 4+ * A special page for marking revisions of pages for translation.
 5+ *
 6+ * @author Niklas Laxström
 7+ * @copyright Copyright © 2009 Niklas Laxström
 8+ * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
 9+ */
 10+class SpecialPageTranslation extends SpecialPage {
 11+ function __construct() {
 12+ SpecialPage::SpecialPage( 'PageTranslation' );
 13+ }
 14+
 15+ /**
 16+ * Access point for this special page.
 17+ * GLOBALS: $wgHooks, $wgOut.
 18+ */
 19+ public function execute( $parameters ) {
 20+ wfLoadExtensionMessages( 'PageTranslation' );
 21+ $this->setHeaders();
 22+
 23+ global $wgRequest, $wgOut, $wgUser;
 24+ $this->user = $wgUser;
 25+
 26+ $target = $wgRequest->getText( 'target', $parameters );
 27+ $revision = $wgRequest->getText( 'revision', 0 );
 28+
 29+ // No specific page or invalid input
 30+ $title = Title::newFromText( $target );
 31+ if ( !$title ) {
 32+ if ( $target !== '' ) {
 33+ $wgOut->addWikiMsg( 'tpt-badtitle' );
 34+ } else {
 35+ $this->listPages();
 36+ }
 37+ return;
 38+ }
 39+
 40+ // Check permissions
 41+ if ( !$this->user->isAllowed( 'pagetranslation' ) ) {
 42+ $wgOut->permissionRequired( 'pagetranslation' );
 43+ return;
 44+ }
 45+
 46+ // We are processing some specific page
 47+ if ( $revision === '0' ) {
 48+ $revision = $title->getLatestRevID();
 49+ } elseif ( $revision !== $title->getLatestRevID() ) {
 50+ $wgOut->addWikiMsg( 'tpt-oldrevision', $title->getPrefixedText(), $revision );
 51+ return;
 52+ }
 53+
 54+ $page = TranslatablePage::newFromRevision( $title, $revision );
 55+
 56+ if ( !$page instanceof TranslatablePage ) {
 57+ $wgOut->addWikiMsg( 'tpt-notsuitable', $title->getPrefixedText(), $revision );
 58+ $this->listPages();
 59+ return;
 60+ }
 61+
 62+ $lastrev = $page->getMarkedTag();
 63+ if ( $lastrev !== false && $lastrev === $revision ) {
 64+ $wgOut->addWikiMsg( 'tpt-already-marked' );
 65+ $this->listPages();
 66+ return;
 67+ }
 68+
 69+ // This will modify the sections to include name property
 70+ $error = false;
 71+ $sections = $this->checkInput( $page, &$error );
 72+ // Non-fatal error which prevents saving
 73+ if ( $error === false && $wgRequest->wasPosted() ) {
 74+ $err = $this->markForTranslation( $page, $sections );
 75+ if ( $err ) {
 76+ call_user_func_array( array($wgOut, 'addWikiMsg' ), $err );
 77+ } else {
 78+ $this->showSuccess( $page );
 79+ $this->listPages();
 80+ }
 81+ return;
 82+ }
 83+ $this->showPage( $page, $sections );
 84+ }
 85+
 86+ public function showSuccess( TranslatablePage $page ) {
 87+ global $wgOut, $wgLang;
 88+
 89+ $titleText = $page->getTitle()->getPrefixedText();
 90+ $num = $wgLang->formatNum( $page->getParse()->countSections() );
 91+ $link = SpecialPage::getTitleFor( 'Translate' )->getFullUrl(
 92+ array( 'group' => 'page|' . $page->getTitle()->getPrefixedText() ) );
 93+ $wgOut->addWikiMsg( 'tpt-saveok', $titleText, $num, $link );
 94+ }
 95+
 96+ public function loadPagesFromDB() {
 97+ $dbr = wfGetDB( DB_SLAVE );
 98+ $tables = array( 'page', 'revtag_type', 'revtag' );
 99+ $vars = array( 'page_id', 'page_title', 'page_namespace', 'page_latest', 'rt_revision', 'rtt_name' );
 100+ $conds = array(
 101+ 'page_id=rt_page',
 102+ 'rt_type=rtt_id',
 103+ 'rtt_name' => array( 'tp:mark', 'tp:tag' ),
 104+ );
 105+ $res = $dbr->select( $tables, $vars, $conds, __METHOD__ );
 106+ return $res;
 107+ }
 108+
 109+ public function listPages() {
 110+ global $wgOut;
 111+
 112+ $res = $this->loadPagesFromDB();
 113+ if ( !$res->numRows() ) {
 114+ $wgOut->addWikiMsg( 'tpt-list-nopages' );
 115+ return;
 116+ }
 117+
 118+ $old = $new = array();
 119+ foreach ( $res as $r ) {
 120+ if ( $r->rtt_name === 'tp:mark' ) {
 121+ $old[$r->page_id] = array(
 122+ $r->rt_revision,
 123+ Title::newFromRow( $r )
 124+ );
 125+ } elseif ( $r->rtt_name === 'tp:tag' ) {
 126+ $new[$r->page_id] = array(
 127+ $r->rt_revision,
 128+ Title::newFromRow( $r )
 129+ );
 130+ }
 131+ }
 132+
 133+ // Pages may have both tags, ignore the transtag if both
 134+ foreach( array_keys($old) as $k ) unset($new[$k]);
 135+
 136+ if ( count($old) ) {
 137+ $wgOut->addWikiMsg( 'tpt-old-pages' );
 138+ $wgOut->addHTML( '<ol>' );
 139+ foreach ( $old as $o ) {
 140+ list( $rev, $title ) = $o;
 141+ $link = $this->user->getSkin()->link( $title );
 142+ $acts = $this->actionLinks( $title, $rev, 'old' );
 143+ $wgOut->addHTML( "<li>$link ($acts) </li>" );
 144+ }
 145+ $wgOut->addHTML( '</ol>' );
 146+ }
 147+
 148+ if ( count($new) ) {
 149+ $wgOut->addWikiMsg( 'tpt-new-pages' );
 150+ $wgOut->addHTML( '<ol>' );
 151+ foreach ( $new as $n ) {
 152+ list( $rev, $title ) = $n;
 153+ $link = $this->user->getSkin()->link( $title );
 154+ $acts = $this->actionLinks( $title, $rev, 'new' );
 155+ $wgOut->addHTML( "<li>$link ($acts) </li>" );
 156+ }
 157+ $wgOut->addHTML( '</ol>' );
 158+ }
 159+
 160+ }
 161+
 162+ protected function actionLinks( $title, $rev, $old = 'old' ) {
 163+ $actions = array();
 164+ $latest = $title->getLatestRevId();
 165+
 166+ if ( $latest !== $rev ) {
 167+ $text = wfMsg( 'tpt-rev-old', $rev );
 168+ $actions[] = $this->user->getSkin()->link(
 169+ $title,
 170+ htmlspecialchars( $text ),
 171+ array(),
 172+ array( 'oldid' => $rev, 'diff' => $title->getLatestRevId() )
 173+ );
 174+ } else {
 175+ $actions[] = wfMsgHtml( 'tpt-rev-latest' );
 176+ }
 177+
 178+ if ( $this->user->isAllowed( 'pagetranslation') &&
 179+ (($old === 'new' && $latest === $rev) ||
 180+ ($old === 'old' && $latest !== $rev)) ) {
 181+ $actions[] = $this->user->getSkin()->link(
 182+ $this->getTitle(),
 183+ wfMsgHtml( 'tpt-rev-mark-new' ),
 184+ array(),
 185+ array(
 186+ 'target' => $title->getPrefixedText(),
 187+ 'revision' => $title->getLatestRevId()
 188+ )
 189+ );
 190+ }
 191+
 192+ if ( $old === 'old' && $this->user->isAllowed( 'translate' ) ) {
 193+ $actions[] = $this->user->getSkin()->link(
 194+ SpecialPage::getTitleFor( 'Translate' ),
 195+ wfMsgHtml( 'tpt-translate-this' ),
 196+ array(),
 197+ array( 'group' => 'page|' . $title->getPrefixedText() )
 198+ );
 199+ }
 200+
 201+ global $wgLang;
 202+ return $wgLang->semicolonList( $actions );
 203+ }
 204+
 205+ public function checkInput( TranslatablePage $page, &$error = false ) {
 206+ global $wgOut, $wgRequest;
 207+
 208+ $parse = $page->getParse();
 209+ $sections = $parse->getSectionsForSave();
 210+ foreach ( $sections as $s ) {
 211+ // We want to preserve $id, because it is the only thing we can use
 212+ // to link the new names to current sections. Name will become
 213+ // the new id only after it is saved into db and the page.
 214+ // Do not allow changing names for old sections
 215+ $s->name = $s->id;
 216+ if ( $s->type !== 'new' ) continue;
 217+
 218+ $name = $wgRequest->getText( 'tpt-sect-' . $s->id, $s->id );
 219+
 220+ $sectionTitle = Title::makeTitleSafe(
 221+ NS_TRANSLATIONS,
 222+ $page->getTitle()->getPrefixedText() . '/' . $name . '/qqq'
 223+ );
 224+ if ( trim($name) === '' || !$sectionTitle ) {
 225+ $wgOut->addWikiMsg( 'tpt-badsect', $name, $s->id );
 226+ $error = true;
 227+ } else {
 228+ // Update the name
 229+ $s->name = $name;
 230+ }
 231+ }
 232+
 233+ return $sections;
 234+ }
 235+
 236+ public function showPage( TranslatablePage $page, $sections ) {
 237+ global $wgOut, $wgScript;
 238+
 239+
 240+ $wgOut->addWikiMsg( 'tpt-showpage-intro' );
 241+
 242+ $wgOut->addHTML(
 243+ Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->getTitle()->getFullURL() ) ) .
 244+ Xml::hidden( 'title', $this->getTitle()->getPrefixedText() ) .
 245+ Xml::hidden( 'revision', $page->getRevision() ) .
 246+ Xml::hidden( 'target', $page->getTitle()->getPrefixedtext() )
 247+ );
 248+
 249+ foreach ( $sections as $s ) {
 250+ if ( $s->type === 'new' ) {
 251+ $name = wfMsgHtml('tpt-section-new') . ' ' . Xml::input( 'tpt-sect-' . $s->id, 10, $s->name );
 252+ } else {
 253+ $name = wfMsgHtml('tpt-section') . ' ' . htmlspecialchars( $s->name );
 254+ }
 255+
 256+ if ( $s->type === 'changed' ) {
 257+ $diff = new DifferenceEngine;
 258+ $diff->setText( $s->getOldText(), $s->getText() );
 259+ $text = $diff->getDiff( wfMsgHtml('tpt-diff-old'), wfMsgHtml('tpt-diff-new') );
 260+ $diff->showDiffStyle();
 261+ } else {
 262+ $text = TranslateUtils::convertWhiteSpaceToHTML( $s->getText() );
 263+ }
 264+
 265+ $wgOut->addHTML(
 266+ Xml::openElement( 'fieldset' ) .
 267+ Xml::tags( 'legend', null, $name ) .
 268+ $text .
 269+ Xml::closeElement( 'fieldset' )
 270+ );
 271+ }
 272+
 273+ $deletedSections = $page->getParse()->getDeletedSections();
 274+ if ( count($deletedSections) ) {
 275+ $wgOut->addWikiMsg( 'tpt-deletedsections' );
 276+ foreach ( $deletedSections as $s ) {
 277+ $name = htmlspecialchars( $s->id );
 278+ $wgOut->addHTML(
 279+ Xml::openElement( 'fieldset' ) .
 280+ Xml::tags( 'legend', null, wfMsgHtml('tpt-section') . ' ' . $name ) .
 281+ TranslateUtils::convertWhiteSpaceToHTML( $s->getText() ) .
 282+ Xml::closeElement( 'fieldset' )
 283+ );
 284+ }
 285+ }
 286+ $wgOut->addHTML(
 287+ Xml::submitButton( wfMsg( 'tpt-submit' ) ) .
 288+ Xml::closeElement( 'form' )
 289+ );
 290+ }
 291+
 292+ public function markForTranslation( TranslatablePage $page, $sections ) {
 293+ $text = $page->getParse()->getSourcePageText();
 294+
 295+ $article = new Article( $page->getTitle() );
 296+ $status = $article->doEdit(
 297+ $text,
 298+ wfMsgForContent( 'tpt-mark-summary' ),
 299+ EDIT_FORCE_BOT | EDIT_UPDATE,
 300+ $page->getRevision()
 301+ );
 302+
 303+ if ( !$status->isOK() ) return array( 'tpt-edit-failed', $status->getWikiText() );
 304+
 305+ $newrevision = $status->value['revision'];
 306+ if ( $newrevision === null ) {
 307+ // Probably a no-change edit, so no new revision was assigned
 308+ $newrevision = $page->getTitle()->getLatestRevId();
 309+ }
 310+
 311+ $inserts = array();
 312+ foreach ( $sections as $s ) {
 313+ $inserts[] = array(
 314+ 'trs_page' => $page->getTitle()->getArticleId(),
 315+ 'trs_key' => $s->name,
 316+ 'trs_text' => $s->getText(),
 317+ );
 318+ }
 319+
 320+ $dbw = wfGetDB( DB_MASTER );
 321+ $dbw->delete( 'translate_sections', array( 'trs_page' => $page->getTitle()->getArticleId() ), __METHOD__ );
 322+ $ok = $dbw->insert( 'translate_sections', $inserts, __METHOD__ );
 323+ if ( $ok === false ) return array( 'tpt-insert-failed' );
 324+
 325+ $page->addMarkedTag( $newrevision );
 326+
 327+ return false;
 328+ }
 329+
 330+}
\ No newline at end of file
Property changes on: trunk/extensions/Translate/tag/SpecialPageTranslation.php
___________________________________________________________________
Added: svn:eol-style
1331 + native
Index: trunk/extensions/Translate/tag/TPParse.php
@@ -0,0 +1,127 @@
 2+<?php
 3+/**
 4+ * This class represents the results of parsed source page, that is, the
 5+ * extracted sections and a template.
 6+ *
 7+ * @author Niklas Laxström
 8+ * @copyright Copyright © 2009 Niklas Laxström
 9+ * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
 10+ */
 11+class TPParse {
 12+ protected $title = null;
 13+
 14+ public $sections = array();
 15+ public $template = null;
 16+ public $dbSections = null;
 17+
 18+ public function __construct( Title $title ) {
 19+ $this->title = $title;
 20+ }
 21+
 22+ public function countSections() {
 23+ return count($this->sections);
 24+ }
 25+
 26+ public function getTemplate() {
 27+ return $this->template;
 28+ }
 29+
 30+ public function getSectionsForSave() {
 31+ $this->loadFromDatabase();
 32+
 33+ $sections = $this->sections;
 34+ $highest = 0;
 35+ if ( count($this->dbSections) ) {
 36+ $highest = call_user_func_array( 'max', array_keys( $this->dbSections ) );
 37+ }
 38+
 39+ foreach ( $sections as $_ ) $highest = max( $_->id, $highest );
 40+ foreach ( $sections as $s ) {
 41+ $s->type = 'old';
 42+
 43+ if ( $s->id === -1 ) {
 44+ $s->type = 'new';
 45+ $s->id = ++$highest;
 46+ } else {
 47+ if ( isset($this->dbSections[$s->id]) ) {
 48+ $storedText = $this->dbSections[$s->id]->text;
 49+ if ( $s->text !== $storedText ) {
 50+ $s->type = 'changed';
 51+ $s->oldtext = $storedText;
 52+ }
 53+ }
 54+ }
 55+
 56+ }
 57+ return $sections;
 58+ }
 59+
 60+ public function getDeletedSections() {
 61+ $sections = $this->getSectionsForSave();
 62+
 63+ $deleted = $this->dbSections;
 64+ foreach ( $sections as $s ) {
 65+ if ( isset($deleted[$s->id]) )
 66+ unset($deleted[$s->id]);
 67+ }
 68+ return $deleted;
 69+ }
 70+
 71+ protected function loadFromDatabase() {
 72+ if ( $this->dbSections !== null ) return;
 73+
 74+ $this->dbSections = array();
 75+
 76+ $db = wfGetDB( DB_SLAVE );
 77+ $tables = 'translate_sections';
 78+ $vars = array( 'trs_key', 'trs_text' );
 79+ $conds = array( 'trs_page' => $this->title->getArticleID() );
 80+
 81+ $res = $db->select( $tables, $vars, $conds, __METHOD__ );
 82+ foreach ( $res as $r ) {
 83+ $section = new TPsection;
 84+ $section->id = $r->trs_key;
 85+ $section->text = $r->trs_text;
 86+ $section->type = 'db';
 87+ $this->dbSections[$r->trs_key] = $section;
 88+ }
 89+ }
 90+
 91+ public function getSourcePageText() {
 92+ $text = $this->template;
 93+ foreach ( $this->sections as $ph => $s ) {
 94+ $text = str_replace( $ph, $s->getMarkedText(), $text );
 95+ }
 96+ return $text;
 97+ }
 98+
 99+ public function getTranslationPageText( MessageCollection $collection ) {
 100+ $text = $this->template; // The source
 101+
 102+ // For finding the messages
 103+ $prefix = $prefix = $this->title->getPrefixedText() . '/';
 104+
 105+ foreach ( $this->sections as $ph => $s ) {
 106+ if ( isset($collection[$prefix.$s->id]) ) {
 107+ $msg = $collection[$prefix.$s->id];
 108+ if ( $msg->translation() === null ) {
 109+ // Just use
 110+ $text = str_replace( $ph, $s->getTextForTrans(), $text );
 111+ } else {
 112+ $sectiontext = $msg->translation();
 113+ $vars = $s->getVariables();
 114+ foreach ( $vars as $key => $value ) {
 115+ $sectiontext = str_replace( $key, $value, $sectiontext );
 116+ }
 117+ if ( $msg->fuzzy() ) {
 118+ $sectiontext = "<div class=\"mw-translate-fuzzy\">$\n$sectiontext\n</div>";
 119+ }
 120+ $text = str_replace( $ph, $sectiontext, $text );
 121+ }
 122+ } else {
 123+ $text = str_replace( $ph, $s->getTextForTrans(), $text );
 124+ }
 125+ }
 126+ return $text;
 127+ }
 128+}
\ No newline at end of file
Property changes on: trunk/extensions/Translate/tag/TPParse.php
___________________________________________________________________
Added: svn:eol-style
1129 + native
Index: trunk/extensions/Translate/tag/PageTranslationHooks.php
@@ -0,0 +1,343 @@
 2+<?php
 3+
 4+class PageTranslationHooks {
 5+
 6+ // Uuugly hack
 7+ static $allowTargetEdit = false;
 8+
 9+ public static function renderTagPage( $parser, &$text, $state ) {
 10+ $title = $parser->getTitle();
 11+ if ( strpos( $text, '</translate>' ) !== false ) {
 12+ $cb = array( __CLASS__, 'replaceTagCb' );
 13+ # Remove the tags nicely, trying to not leave excess whitespace lying around
 14+ $text = preg_replace_callback( '~(\n?<translate>\s*?)(.*?)(\s*?</translate>)~s', $cb, $text );
 15+ # Replace variable markers
 16+ $text = preg_replace_callback( '~(<tvar[^<>]+>)(.*)(</>)~s', $cb, $text );
 17+ }
 18+ return true;
 19+ }
 20+
 21+ public static function replaceTagCb( $matches ) {
 22+ return $matches[2];
 23+ }
 24+
 25+ public static function injectCss( $outputpage, $text ) {
 26+ TranslateUtils::injectCSS();
 27+ return true;
 28+ }
 29+
 30+ public static function onSectionSave( $article, $user, $text, $summary, $minor,
 31+ $_, $_, $flags, $revision ) {
 32+ global $wgTranslateFuzzyBotName;
 33+ $title = $article->getTitle();
 34+
 35+ // Some checks
 36+
 37+ // We are only interested in the translations namespace
 38+ if ( $title->getNamespace() != NS_TRANSLATIONS ) return true;
 39+ // Do not trigger renders for fuzzybot or fuzzy
 40+ if ( strpos( $text, TRANSLATE_FUZZY ) !== false ) return true;
 41+ // For null revisions, don't do anything
 42+ if ( $revision === null ) return true;
 43+
 44+ // Figure out the group
 45+ $groupKey = MessageIndex::titleToGroup( $title );
 46+ $group = MessageGroups::getGroup( $groupKey );
 47+ if ( !$group instanceof WikiPageMessageGroup ) return;
 48+
 49+ // Finally we know the title and can construct a Translatable page
 50+ $page = TranslatablePage::newFromTitle( $group->title );
 51+
 52+ // Add a tracking mark
 53+ self::addSectionTag( $title, $revision->getId(), $page->getMarkedTag() );
 54+
 55+ // Update the target translation page
 56+ list(, $code ) = TranslateUtils::figureMessage( $title->getDBkey() );
 57+ self::updateTranslationPage( $page, $group, $code, $user, $flags, $summary );
 58+
 59+ return true;
 60+ }
 61+
 62+ protected static function addSectionTag( Title $title, $revision, $pageRevision ) {
 63+ if ( $pageRevision === null ) throw new MWException( 'Page revision is null' );
 64+ if ( $revision === null ) throw new MWException( 'Revision is null' );
 65+
 66+ $dbw = wfGetDB( DB_MASTER );
 67+
 68+ // Can this be done in one query?
 69+ $id = $dbw->selectField( 'revtag_type', 'rtt_id',
 70+ array( 'rtt_name' => 'tp:transver' ), __METHOD__ );
 71+
 72+ $conds = array(
 73+ 'rt_page' => $title->getArticleId(),
 74+ 'rt_type' => $id,
 75+ 'rt_revision' => $revision
 76+ );
 77+ $dbw->delete( 'revtag', $conds, __METHOD__ );
 78+
 79+ $conds['rt_value'] = $pageRevision;
 80+
 81+ $dbw->insert( 'revtag', $conds, __METHOD__ );
 82+ }
 83+
 84+ public static function updateTranslationPage( TranslatablePage $page,
 85+ MessageGroup $group, $code, $user, $flags, $summary ) {
 86+
 87+ $source = $page->getTitle();
 88+ $target = Title::makeTitle( $source->getNamespace(), $source->getDBkey() . "/$code" );
 89+
 90+ $collection = $group->initCollection( $code );
 91+ $group->fillCollection( $collection );
 92+
 93+ $text = $page->getParse()->getTranslationPageText( $collection );
 94+
 95+ // Same as in renderSourcePage
 96+ $cb = array( __CLASS__, 'replaceTagCb' );
 97+ $text = preg_replace_callback( '~(\n?<translate>\s*?)(.*?)(\s*?</translate>)~s', $cb, $text );
 98+
 99+ #$flags |= EDIT_SUPPRESS_RC; // We can filter using CleanChanges
 100+ $flags &= ~EDIT_NEW & ~EDIT_UPDATE; // We don't know
 101+
 102+ $article = new Article( $target );
 103+
 104+ self::$allowTargetEdit = true;
 105+ $article->doEdit( $text, $summary, $flags );
 106+ self::$allowTargetEdit = false;
 107+ }
 108+
 109+ public static function addSidebar( $out, $tpl ) {
 110+ // TODO: fixme
 111+ return true;
 112+ global $wgLang;
 113+
 114+ // Sort by translation percentage
 115+ arsort( $status, SORT_NUMERIC );
 116+
 117+ foreach ( $status as $code => $percent ) {
 118+ $name = TranslateUtils::getLanguageName( $code, false, $wgLang->getCode() );
 119+ $percent = $wgLang->formatNum( round( 100 * $percent ) );
 120+ $label = "$name ($percent%)";
 121+
 122+ $_title = TranslateTagUtils::codefyTitle( $title, $code );
 123+
 124+ $items[] = array(
 125+ 'text' => $label,
 126+ 'href' => $_title->getFullURL(),
 127+ 'id' => 'foo',
 128+ );
 129+ }
 130+
 131+ $sidebar = $out->buildSidebar();
 132+ $sidebar['TRANSLATIONS'] = $items;
 133+
 134+ $tpl->set( 'sidebar', $sidebar );
 135+
 136+ return true;
 137+ }
 138+
 139+ public static function languages( $data, $params, $parser ) {
 140+ $title = $parser->getTitle();
 141+ $page = TranslatablePage::newFromTitle( $title );
 142+ $status = $page->getTranslationPercentages();
 143+ if ( !$status ) return '';
 144+
 145+ global $wgLang;
 146+
 147+ // Sort by language code
 148+ ksort( $status );
 149+
 150+ // $lobj = $parser->getFunctionLang();
 151+ $sk = $parser->mOptions->getSkin();
 152+ $legend = wfMsg( 'otherlanguages' );
 153+
 154+ global $wgTranslateCssLocation;
 155+
 156+ $languages = array();
 157+ foreach ( $status as $code => $percent ) {
 158+ $name = TranslateUtils::getLanguageName( $code, false, $wgLang->getCode() );
 159+
 160+ $percent *= 100;
 161+ if ( $percent < 10 ) continue;
 162+ if ( $percent < 20 ) $image = 1;
 163+ elseif ( $percent < 40 ) $image = 2;
 164+ elseif ( $percent < 60 ) $image = 3;
 165+ elseif ( $percent < 80 ) $image = 4;
 166+ else $image = 5;
 167+
 168+ $percent = Xml::element( 'img', array(
 169+ 'src' => "$wgTranslateCssLocation/images/prog-$image.png"
 170+ ) );
 171+ $label = "$name $percent";
 172+
 173+ $suffix = ( $code === 'en' ) ? '' : "/$code";
 174+ $_title = Title::makeTitle( $title->getNamespace(), $title->getDBKey() . $suffix );
 175+ $languages[] = $sk->link( $_title, $label );
 176+ }
 177+
 178+ $languages = implode( '&nbsp;• ', $languages );
 179+
 180+ return <<<FOO
 181+<div class="mw-pt-languages">
 182+<table><tbody>
 183+
 184+<tr valign="top">
 185+<td class="mw-pt-languages-label"><b>$legend:</b></td>
 186+<td class="mw-pt-languages-list">$languages</td></tr>
 187+
 188+</tbody></table>
 189+</div>
 190+FOO;
 191+ }
 192+
 193+ public static function tpSyntaxCheck( $article, $user, $text, $summary,
 194+ $minor, $_, $_, $flags, $status ) {
 195+ // Quick escape on normal pages
 196+ if ( strpos( $text, '</translate>' ) === false ) return true;
 197+
 198+ $page = TranslatablePage::newFromText( $article->getTitle(), $text );
 199+ try {
 200+ $page->getParse();
 201+ } catch ( TPException $e ) {
 202+ call_user_func_array( array( $status, 'fatal' ), $ret );
 203+ return false;
 204+ }
 205+
 206+ return true;
 207+ }
 208+
 209+ public static function addTranstag( $article, $user, $text, $summary,
 210+ $minor, $_, $_, $flags, $revision ) {
 211+ // We are not interested in null revisions
 212+ if ( $revision === null ) return true;
 213+
 214+ // Quick escape on normal pages
 215+ if ( strpos( $text, '</translate>' ) === false ) return true;
 216+
 217+ // Add the ready tag
 218+ $page = TranslatablePage::newFromTitle( $article->getTitle() );
 219+ $page->addReadyTag( $revision->getId() );
 220+
 221+ return true;
 222+ }
 223+
 224+ // Here we disable editing of some existing or unknown pages
 225+ public static function translationsCheck( $title, $user, $action, &$result ) {
 226+
 227+ // Case 1: Unknown section translations
 228+ if ( $title->getNamespace() == NS_TRANSLATIONS && $action === 'edit' ) {
 229+ $group = MessageIndex::titleToGroup( $title );
 230+ if ( $group === null ) {
 231+ // No group means that the page is currently not
 232+ // registered to any page translation message groups
 233+ wfLoadExtensionMessages( 'PageTranslation' );
 234+ $result = array( 'tpt-unknown-page' );
 235+ return false;
 236+ }
 237+
 238+ // Case 2: Target pages
 239+ } elseif( $title->getBaseText() != $title->getText() ) {
 240+ $newtitle = Title::makeTitle( $title->getNamespace(), $title->getBaseText() );
 241+
 242+ if ( $newtitle && $newtitle->exists() ) {
 243+ $page = TranslatablePage::newFromTitle( $newtitle );
 244+
 245+ if ( $page->getMarkedTag() && !self::$allowTargetEdit) {
 246+ wfLoadExtensionMessages( 'PageTranslation' );
 247+ $result = array(
 248+ 'tpt-target-page',
 249+ $newtitle->getPrefixedText(),
 250+ $page->getTranslationUrl( $title->getSubpageText() )
 251+ );
 252+ return false;
 253+ }
 254+ }
 255+ }
 256+
 257+ return true;
 258+ }
 259+
 260+ public static function onLoadExtensionSchemaUpdates() {
 261+ global $wgExtNewTables;
 262+ $dir = dirname( __FILE__ ) . '/..';
 263+ $wgExtNewTables[] = array( 'translate_sections', "$dir/translate.sql" );
 264+ $wgExtNewTables[] = array( 'revtag_type', "$dir/revtags.sql" );
 265+
 266+ // Add our tags if they are not registered yet
 267+ // tp:tag is called also the ready tag
 268+ $tags = array( 'tp:mark', 'tp:tag', 'tp:transver' );
 269+
 270+ $dbw = wfGetDB( DB_MASTER );
 271+ foreach ( $tags as $tag ) {
 272+ $field = array( 'rtt_name' => $tag );
 273+ $ret = $dbw->selectField( 'revtag_type', 'rtt_name', $field, __METHOD__ );
 274+ if ( $ret !== $tag ) $dbw->insert( 'revtag_type', $field, __METHOD__ );
 275+ }
 276+ return true;
 277+ }
 278+
 279+ public static function test(&$article, &$outputDone, &$pcache) {
 280+ global $wgOut;
 281+ if ( !$article->getOldID() ) {
 282+ $wgOut->addHTML( self::getHeader( $article->getTitle() ) );
 283+ } else {
 284+ echo "foo";
 285+ }
 286+ return true;
 287+ }
 288+
 289+ public static function getHeader( Title $title, $code = false ) {
 290+ global $wgLang, $wgUser;
 291+
 292+ $sk = $wgUser->getSkin();
 293+
 294+ $page = TranslatablePage::newFromText( $title, '' );
 295+
 296+ $marked = $page->getMarkedTag();
 297+ $ready = $page->getReadyTag();
 298+ if ( $marked === false && $ready === false ) return '';
 299+
 300+ $latest = $title->getLatestRevId();
 301+ $canmark = $ready === $latest && $marked !== $latest;
 302+
 303+ wfLoadExtensionMessages( 'PageTranslation' );
 304+
 305+ $actions = array();
 306+
 307+ if ( $marked && $wgUser->isAllowed('translate') ) {
 308+ $par = array(
 309+ 'group' => 'page|' . $title->getPrefixedText(),
 310+ 'language' => $code ? $code : $wgLang->getCode(),
 311+ 'task' => 'view'
 312+ );
 313+ $translate = SpecialPage::getTitleFor( 'Translate' );
 314+ $linkDesc = wfMsgHtml( 'translate-tag-translate-link-desc' );
 315+ $actions[] = $sk->link( $translate, $linkDesc, array(), $par);
 316+ }
 317+
 318+ if ( $canmark && $wgUser->isAllowed('pagetranslation') ) {
 319+ $par = array(
 320+ 'target' => $title->getPrefixedText()
 321+ );
 322+ $translate = SpecialPage::getTitleFor( 'PageTranslation' );
 323+ $linkDesc = wfMsgHtml( 'translate-tag-markthis' );
 324+ $actions[] = $sk->link( $translate, $linkDesc, array(), $par);
 325+ }
 326+
 327+
 328+ if ( $code ) {
 329+ $legendText = wfMsgHtml( 'translate-tag-legend' );
 330+ $legendOther = wfMsgHtml( 'translate-tag-legend-fallback' );
 331+ $legendFuzzy = wfMsgHtml( 'translate-tag-legend-fuzzy' );
 332+
 333+ $actions[] = "$legendText <span class=\"mw-translate-other\">$legendOther</span>" .
 334+ " <span class=\"mw-translate-fuzzy\">$legendFuzzy</span>";
 335+ }
 336+
 337+ if ( !count($actions) ) return '';
 338+ $legend = "<div style=\"font-size: x-small; text-align: center\">";
 339+ $legend .= $wgLang->semicolonList( $actions );
 340+ $legend .= '</div><hr />';
 341+ return $legend;
 342+ }
 343+
 344+}
\ No newline at end of file
Property changes on: trunk/extensions/Translate/tag/PageTranslationHooks.php
___________________________________________________________________
Added: svn:eol-style
1345 + native
Index: trunk/extensions/Translate/tag/TPSection.php
@@ -0,0 +1,49 @@
 2+<?php
 3+/**
 4+ * This class represents one section of a translatable page.
 5+ *
 6+ * @author Niklas Laxström
 7+ * @copyright Copyright © 2009 Niklas Laxström
 8+ * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
 9+ */
 10+class TPSection {
 11+ public $id, $name, $text, $type;
 12+
 13+ public function getText() {
 14+ return $this->text;
 15+ }
 16+
 17+ public function getTextForTrans() {
 18+ $re = '~<tvar\|([^>]+)>(.*?)</>~u';
 19+ return preg_replace( $re, '\2', $this->text );
 20+ }
 21+
 22+ public function getMarkedText() {
 23+ $id = isset($this->name) ? $this->name : $this->id;
 24+ $header = "<!--T:{$id}-->";
 25+ $re = '~^(=+.*?=+\s*?)\n~';
 26+ $rep = "\\1 $header\n";
 27+ $count = 0;
 28+
 29+ $text = preg_replace( $re, $rep, $this->text, 1, &$count );
 30+ if ( $count === 0 ) {
 31+ $text = $header . "\n" . $this->text;
 32+ }
 33+ return $text;
 34+ }
 35+
 36+ public function getOldText() {
 37+ return isset($this->oldtext) ? $this->oldtext : $this->text;
 38+ }
 39+
 40+ public function getVariables() {
 41+ $re = '~<tvar\|([^>]+)>(.*?)</>~u';
 42+ $matches = array();
 43+ preg_match_all( $re, $this->text, $matches, PREG_SET_ORDER );
 44+ $vars = array();
 45+ foreach( $matches as $m ) {
 46+ $vars[$m[1]] = $m[2];
 47+ }
 48+ return $vars;
 49+ }
 50+}
\ No newline at end of file
Property changes on: trunk/extensions/Translate/tag/TPSection.php
___________________________________________________________________
Added: svn:eol-style
151 + native
Index: trunk/extensions/Translate/tag/TranslatablePage.php
@@ -0,0 +1,362 @@
 2+<?php
 3+class TranslatablePage {
 4+
 5+ private $title = null;
 6+ private $text = null;
 7+ private $revision = null;
 8+ private $source = null;
 9+ private $init = false;
 10+
 11+ private function __construct( Title $title ) {
 12+ $this->title = $title;
 13+ }
 14+
 15+ // Public constructors //
 16+
 17+ public static function newFromText( Title $title, $text ) {
 18+ $obj = new self( $title );
 19+ $obj->text = $text;
 20+ $obj->source = 'text';
 21+ return $obj;
 22+ }
 23+
 24+ public static function newFromRevision( Title $title, $revision ) {
 25+ $rev = Revision::newFromTitle( $title, $revision );
 26+ if ( $rev === null ) throw new MWException( 'Revision is null' );
 27+
 28+ $obj = new self( $title );
 29+ $obj->source = 'revision';
 30+ $obj->revision = $revision;
 31+ return $obj;
 32+ }
 33+
 34+ public static function newFromTitle( Title $title ) {
 35+ $obj = new self( $title );
 36+ $obj->source = 'title';
 37+ return $obj;
 38+ }
 39+
 40+ // Getters //
 41+
 42+ public function getTitle() {
 43+ return $this->title;
 44+ }
 45+
 46+ public function getText() {
 47+ if ( $this->init === false ) {
 48+ switch ( $this->source ) {
 49+ case 'text':
 50+ break;
 51+ case 'title':
 52+ $revision = $this->getMarkedTag();
 53+ case 'revision':
 54+ $rev = Revision::newFromTitle( $this->getTitle(), $this->revision );
 55+ $this->text = $rev->getText();
 56+ break;
 57+ }
 58+ }
 59+
 60+ if ( !is_string($this->text) ) throw new MWException( 'We have no text' );
 61+ return $this->text;
 62+ }
 63+
 64+ /**
 65+ * Revision is null if object was constructed using newFromText.
 66+ */
 67+ public function getRevision() {
 68+ return $this->revision;
 69+ }
 70+
 71+ // Public functions //
 72+
 73+ public function getParse() {
 74+ if ( isset($this->cachedParse) ) return $this->cachedParse;
 75+
 76+ $text = $this->getText();
 77+
 78+ $sections = array();
 79+ $tagPlaceHolders = array();
 80+
 81+ while (true) {
 82+ $re = '~(<translate>)\s*(.*?)(</translate>)~s';
 83+ $matches = array();
 84+ $ok = preg_match_all( $re, $text, $matches, PREG_OFFSET_CAPTURE );
 85+ if ( $ok === false ) return array( 'pt-error', 'tag', $text );
 86+ if ( $ok === 0 ) break; // No matches
 87+
 88+ // Do-placehold for the whole stuff
 89+ $ph = $this->getUniq();
 90+ $start = $matches[0][0][1];
 91+ $len = strlen($matches[0][0][0]);
 92+ $end = $start + $len;
 93+ $text = self::index_replace( $text, $ph, $start, $end );
 94+
 95+ // Sectionise the contents
 96+ // Strip the surrounding tags
 97+ $contents = $matches[0][0][0]; // full match
 98+ $start = $matches[2][0][1] - $matches[0][0][1]; // bytes before actual content
 99+ $len = strlen($matches[2][0][0]); // len of the content
 100+ $end = $start + $len;
 101+
 102+ $ret = $this->sectionise( &$sections, substr( $contents, $start, $len ) );
 103+
 104+ $tagPlaceHolders[$ph] =
 105+ self::index_replace( $contents, $ret, $start, $end );
 106+ }
 107+
 108+ if ( strpos( $text, '<translate>' ) !== false )
 109+ throw new TPException( array( 'pt-parse-open', $text ) );
 110+
 111+ if ( strpos( $text, '</translate>' ) !== false )
 112+ throw new TPException( array( 'pt-parse-close', $text ) );
 113+
 114+ foreach ( $tagPlaceHolders as $ph => $value ) {
 115+ $text = str_replace( $ph, $value, $text );
 116+ }
 117+
 118+ $parse = new TPParse( $this->getTitle() );
 119+ $parse->template = $text;
 120+ $parse->sections = $sections;
 121+
 122+ // Cache it
 123+ $this->cachedParse = $parse;
 124+
 125+ return $parse;
 126+ }
 127+
 128+ // Inner functionality //
 129+
 130+ protected function getUniq() {
 131+ static $i = 0;
 132+ return "\x7fUNIQ" . dechex(mt_rand(0, 0x7fffffff)) . dechex(mt_rand(0, 0x7fffffff)) . '|' . $i++;
 133+ }
 134+
 135+ protected static function index_replace( $string, $rep, $start, $end ) {
 136+ return substr( $string, 0, $start ) . $rep . substr( $string, $end );
 137+ }
 138+
 139+ protected function sectionise( $sections, $text ) {
 140+ $flags = PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE;
 141+ $parts = preg_split( '~(\s*\n\n\s*|\s*$)~', $text, -1, $flags );
 142+
 143+ $template = '';
 144+ foreach ( $parts as $_ ) {
 145+ if ( trim( $_ ) === '' ) {
 146+ $template .= $_;
 147+ } else {
 148+ $ph = $this->getUniq();
 149+ $sections[$ph] = $this->shakeSection( $_ );
 150+ $template .= $ph;
 151+ }
 152+ }
 153+ return $template;
 154+ }
 155+
 156+ protected function shakeSection( $content ) {
 157+ $re = '~<!--T:(.*?)-->~';
 158+ $matches = array();
 159+ $count = preg_match_all( $re, $content, $matches, PREG_SET_ORDER );
 160+ if ( $count === false ) throw new TPException( 'pt-error', 'shake', $content );
 161+ if ( $count > 1 ) throw new TPException( 'pt-shake-dupid', $content );
 162+
 163+ $section = new TPSection;
 164+ if ( $count === 1 ) {
 165+ foreach ( $matches as $match ) {
 166+ list( /*full*/, $id ) = $match;
 167+ $section->id = $id;
 168+
 169+ $rer1 = '~^\s*<!--T:(.*?)-->\s*~'; // Normal sections
 170+ $rer2 = '~\s*<!--T:(.*?)-->~'; // Sections with title
 171+ $content = preg_replace( $rer1, '', $content );
 172+ $content = preg_replace( $rer2, '', $content );
 173+ }
 174+ } else { // New section
 175+ $section->id = -1;
 176+ }
 177+
 178+ if ( $content === '' ) throw new TPException( 'pt-shake-empty' );
 179+
 180+ $section->text = $content;
 181+
 182+ return $section;
 183+ }
 184+
 185+ public function addMarkedTag( $revision ) {
 186+ $this->addTag( 'tp:mark', $revision );
 187+ }
 188+
 189+ public function addReadyTag( $revision ) {
 190+ $this->addTag( 'tp:tag', $revision );
 191+ }
 192+
 193+ protected function addTag( $tag, $revision ) {
 194+ $dbw = wfGetDB( DB_MASTER );
 195+
 196+ // Can this be done in one query?
 197+ $id = $dbw->selectField( 'revtag_type', 'rtt_id',
 198+ array( 'rtt_name' => $tag ), __METHOD__ );
 199+
 200+ $conds = array(
 201+ 'rt_page' => $this->getTitle()->getArticleId(),
 202+ 'rt_type' => $id,
 203+ 'rt_revision' => $revision
 204+ );
 205+ $dbw->delete( 'revtag', $conds, __METHOD__ );
 206+ $dbw->insert( 'revtag', $conds, __METHOD__ );
 207+ }
 208+
 209+ public function getMarkedTag($db = DB_SLAVE) {
 210+ return $this->getTag( 'tp:mark' );
 211+ }
 212+ public function getReadyTag($db = DB_SLAVE) {
 213+ return $this->getTag( 'tp:tag' );
 214+ }
 215+
 216+ protected function getTag( $tag, $dbt = DB_SLAVE ) {
 217+ $db = wfGetDB( $dbt );
 218+
 219+ // Can this be done in one query?
 220+ $id = $db->selectField( 'revtag_type', 'rtt_id',
 221+ array( 'rtt_name' => $tag ), __METHOD__ );
 222+
 223+ $fields = 'rt_revision';
 224+ $conds = array(
 225+ 'rt_page' => $this->getTitle()->getArticleId(),
 226+ 'rt_type' => $id,
 227+ );
 228+ $options = array( 'ORDER BY', 'rt_revision DESC' );
 229+ return $db->selectField( 'revtag', $fields, $conds, __METHOD__, $options );
 230+ }
 231+
 232+
 233+ public function getTranslationUrl( $code ) {
 234+ $translate = SpecialPage::getTitleFor( 'Translate' );
 235+ $params = array(
 236+ 'group' => 'page|' . $this->getTitle()->getPrefixedText(),
 237+ 'task' => 'view'
 238+ );
 239+ if ( $code ) $params['language'] = $code;
 240+ return $translate->getFullURL( $params );
 241+ }
 242+
 243+ protected function getMarkedRevs( $tag ) {
 244+ $db = wfGetDB( DB_SLAVE );
 245+
 246+ // Can this be done in one query?
 247+ $id = $db->selectField( 'revtag_type', 'rtt_id',
 248+ array( 'rtt_name' => $tag ), __METHOD__ );
 249+
 250+ $fields = array( 'rt_revision', 'rt_value' );
 251+ $conds = array(
 252+ 'rt_page' => $this->getTitle()->getArticleId(),
 253+ 'rt_type' => $id,
 254+ );
 255+ $options = array( 'ORDER BY', 'rt_revision DESC' );
 256+ return $db->select( 'revtag', $fields, $conds, __METHOD__, $options );
 257+ }
 258+
 259+ public function getTranslationPercentages() {
 260+ // Check the memory cache, as this is very slow to calculate
 261+ global $wgMemc;
 262+ $memcKey = wfMemcKey( 'pt', 'status', $this->getTitle()->getPrefixedText() );
 263+ $cache = $wgMemc->get( $memcKey );
 264+ #if ( is_array( $cache ) ) return $cache;
 265+
 266+ // Fetch the available translation pages from database
 267+ $dbr = wfGetDB( DB_SLAVE );
 268+ $likePattern = $dbr->escapeLike( $this->getTitle()->getDBkey() ) . '/%%';
 269+ $res = $dbr->select(
 270+ 'page',
 271+ array( 'page_namespace', 'page_title' ),
 272+ array(
 273+ 'page_namespace' => $this->getTitle()->getNamespace(),
 274+ "page_title LIKE '$likePattern'"
 275+ ), __METHOD__ );
 276+
 277+ $titles = TitleArray::newFromResult( $res );
 278+
 279+ // Calculate percentages for the available translations
 280+ $group = MessageGroups::getGroup( 'page|' . $this->getTitle()->getPrefixedText() );
 281+ if ( !$group instanceof WikiPageMessageGroup ) return null;
 282+
 283+ $markedRevs = $this->getMarkedRevs( 'tp:mark' );
 284+
 285+
 286+ $temp = array();
 287+ foreach ( $titles as $t ) {
 288+ if ( $t->getSubpageText() === $t->getText() ) continue;
 289+ $collection = $group->initCollection( $t->getSubpageText() );
 290+ $group->fillCollection( $collection );
 291+ $temp[$collection->code] = $this->getPercentageInternal( $collection, $markedRevs );
 292+ }
 293+ // English is always up-to-date
 294+ $temp['en'] = 1.00;
 295+
 296+ // TODO: Ideally there would be some kind of purging here
 297+ $wgMemc->set( $memcKey, $temp, 60*60*12 );
 298+ return $temp;
 299+ }
 300+
 301+ protected function getPercentageInternal( $collection, $markedRevs ) {
 302+ $count = count($collection);
 303+ if ( $count === 0 ) return 0;
 304+
 305+ $total = 0;
 306+
 307+ foreach ( $collection as $key => $message ) {
 308+ if ( !$message->translated() ) continue; // No score
 309+
 310+ $score = 1;
 311+
 312+ // Fuzzy halves
 313+ if ( $message->fuzzy() ) $score *= 0.5;
 314+
 315+ // Reduce 20% for every newer revision than what is translated against
 316+ $rev = $this->getTransrev( $key .'/' . $collection->code );
 317+ foreach ( $markedRevs as $r ) {
 318+ if ( $rev === $r->rt_revision ) break;
 319+ $score *= 0.8;
 320+ }
 321+ $total += $score;
 322+ }
 323+ return $total/$count;
 324+ }
 325+
 326+ protected function getTransRev( $suffix ) {
 327+ $id = $this->getTagId( 'tp:transver' );
 328+ $title = Title::makeTitle( NS_TRANSLATIONS,
 329+ $this->getTitle()->getPrefixedText() . '/' . $suffix
 330+ );
 331+
 332+ $db = wfGetDB( DB_SLAVE );
 333+ $fields = 'rt_value';
 334+ $conds = array(
 335+ 'rt_page' => $title->getArticleId(),
 336+ 'rt_type' => $id,
 337+ );
 338+ $options = array( 'ORDER BY', 'rt_revision DESC' );
 339+ return $db->selectField( 'revtag', $fields, $conds, __METHOD__, $options );
 340+
 341+ }
 342+
 343+ protected function getTagId( $tag ) {
 344+ static $tagcache = array();
 345+ if ( !isset( $tagcache[$tag] ) ) {
 346+ $db = wfGetDB( DB_SLAVE );
 347+ $tagcache[$tag] = $db->selectField(
 348+ 'revtag_type', // Table
 349+ 'rtt_id', // Field
 350+ array( 'rtt_name' => $tag ), // Conds
 351+ __METHOD__
 352+ );
 353+ }
 354+ return $tagcache[$tag];
 355+ }
 356+}
 357+
 358+class TPException extends MWException {
 359+ public function __construct( $msg ) {
 360+ parent::__construct( call_user_func_array( 'wfMsg', $msg ) );
 361+ }
 362+}
 363+
Property changes on: trunk/extensions/Translate/tag/TranslatablePage.php
___________________________________________________________________
Added: svn:eol-style
1364 + native
Index: trunk/extensions/Translate/Translate.php
@@ -11,7 +11,7 @@
1212 * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
1313 */
1414
15 -define( 'TRANSLATE_VERSION', '11:2009-04-27' );
 15+define( 'TRANSLATE_VERSION', '12:2009-05-09' );
1616
1717 $wgExtensionCredits['specialpage'][] = array(
1818 'path' => __FILE__,
@@ -28,6 +28,7 @@
2929 require_once( $dir . '_autoload.php' );
3030
3131 $wgExtensionMessagesFiles['Translate'] = $dir . 'Translate.i18n.php';
 32+$wgExtensionMessagesFiles['PageTranslation'] = $dir . 'PageTranslation.i18n.php';
3233 $wgExtensionAliasesFiles['Translate'] = $dir . 'Translate.alias.php';
3334 $wgExtensionFunctions[] = 'efTranslateInit';
3435
@@ -43,6 +44,7 @@
4445 $wgSpecialPageGroups['TranslationChanges'] = 'changes';
4546 $wgSpecialPageGroups['TranslationStats'] = 'wiki';
4647 $wgSpecialPageGroups['LanguageStats'] = 'wiki';
 48+$wgSpecialPageGroups['PageTranslation'] = 'pagetools';
4749
4850 $wgHooks['EditPage::showEditForm:initial'][] = 'TranslateEditAddons::addTools';
4951 $wgHooks['OutputPageBeforeHTML'][] = 'TranslateEditAddons::addNavigation';
@@ -56,19 +58,10 @@
5759 $wgHooks['SpecialRecentChangesPanel'][] = 'TranslateRcFilter::translationFilterForm';
5860 $wgHooks['SkinTemplateToolboxEnd'][] = 'TranslateToolbox::toolboxAllTranslations';
5961
60 -// Tag hooks
61 -$wgHooks['getUserPermissionsErrorsExpensive'][] = 'TranslateTagHooks::disableEdit';
62 -$wgHooks['ParserAfterStrip'][] = 'TranslateTagHooks::renderTagPage';
63 -$wgHooks['OutputPageBeforeHTML'][] = 'TranslateTagHooks::injectCss';
64 -$wgHooks['ArticleSaveComplete'][] = 'TranslateTagHooks::onSave';
65 -$wgHooks['SkinTemplateOutputPageBeforeExec'][] = 'TranslateTagHooks::addSidebar';
66 -$wgHooks['ArticleSave'][] = 'TranslateTag::save';
67 -$wgHooks['ParserFirstCallInit'][] = 'efTranslateInitTags';
68 -$wgHooks['BeforeParserFetchTemplateAndtitle'][] = 'TranslateTagHooks::onTemplate';
 62+$wgEnablePageTranslation = false;
 63+$wgPageTranslationNamespace = 1198;
6964
70 -
71 -$wgJobClasses['FuzzyJob'] = 'FuzzyJob';
72 -$wgJobClasses['RenderJob'] = 'RenderJob';
 65+//$wgJobClasses['RenderJob'] = 'RenderJob';
7366 $wgAvailableRights[] = 'translate';
7467
7568 define( 'TRANSLATE_FUZZY', '!!FUZZY!!' );
@@ -181,46 +174,86 @@
182175 $wgTranslatePHPlot = false;
183176 $wgTranslatePHPlotFont = '/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans.ttf';
184177
185 -// Options for <translate> tag
186 -/**
187 - * Can be used to define where translation of sections are stored for tag
188 - * translations.
189 - * Two item array, where first item is namespace and latter is page name.
190 - * Namespace is either null which means the same as the current namespace, or
191 - * integer denoting some fixed namespace.
192 - * Page name can have following variables:
193 - * - $NS: the namespace of the source page with
194 - * - $PAGE: name of the page without namespace
195 - * - $FULLNAME: name of the page with possible namespace
196 - * - $KEY: automatically or assigned unique key for section,
197 - * without this page names may not be unique!
198 - * - $SNIPPET: automatically constructed snippet of the section contents to give
199 - * more meaningful names for the pages.
200 - */
201 -$wgTranslateTagTranslationLocation = array(
202 - null, // Namespace is whatever the page is in
203 - '$PAGE/translations/$KEY-$SNIPPET'
204 -);
205178
206 -#$wgContentTranslation = true;
 179+function wfMemIn() { }
 180+function wfMemOut() { }
207181
208 -if ( $wgDebugComments ) {
209 - require_once( "$dir/utils/MemProfile.php" );
210 -} else {
211 - function wfMemIn() { }
212 - function wfMemOut() { }
213 -}
214 -
215182 function efTranslateInit() {
216183 global $wgTranslatePHPlot, $wgAutoloadClasses;
217184 if ( $wgTranslatePHPlot ) {
218185 $wgAutoloadClasses['PHPlot'] = $wgTranslatePHPlot;
219186 }
 187+
 188+ global $wgEnablePageTranslation;
 189+ if ( $wgEnablePageTranslation ) {
 190+
 191+ // Special page + the right to use it
 192+ global $wgSpecialPages, $wgAvailableRights;
 193+ $wgSpecialPages['PageTranslation'] = 'SpecialPageTranslation';
 194+ $wgAvailableRights[] = 'pagetranslation';
 195+
 196+ // Namespaces
 197+ global $wgPageTranslationNamespace, $wgExtraNamespaces;
 198+ global $wgNamespacesWithSubpages, $wgNamespaceProtection;
 199+ global $wgTranslateMessageNamespaces;
 200+ // Defines for nice usage
 201+ define ( 'NS_TRANSLATIONS', $wgPageTranslationNamespace );
 202+ define ( 'NS_TRANSLATIONS_TALK', $wgPageTranslationNamespace +1 );
 203+ // Register them as namespaces
 204+ $wgExtraNamespaces[NS_TRANSLATIONS] = 'Translations';
 205+ $wgExtraNamespaces[NS_TRANSLATIONS_TALK] = 'Translations_talk';
 206+ $wgNamespacesWithSubpages[NS_TRANSLATIONS] = true;
 207+ $wgNamespacesWithSubpages[NS_TRANSLATIONS_TALK] = true;
 208+ // Standard protection and register it for filtering
 209+ $wgNamespaceProtection[NS_TRANSLATIONS] = array( 'translate' );
 210+ $wgTranslateMessageNamespaces[] = NS_TRANSLATIONS;
 211+
 212+ // Page translation hooks
 213+ global $wgHooks;
 214+
 215+ // Register our css, is there a better place for this?
 216+ $wgHooks['OutputPageBeforeHTML'][] = 'PageTranslationHooks::injectCss';
 217+
 218+ // Add transver tags and update translation target pages
 219+ $wgHooks['ArticleSaveComplete'][] = 'PageTranslationHooks::onSectionSave';
 220+
 221+ // Foo
 222+ #$wgHooks['SkinTemplateOutputPageBeforeExec'][] = 'TranslateTagHooks::addSidebar';
 223+
 224+ // Register <languages/>
 225+ $wgHooks['ParserFirstCallInit'][] = 'efTranslateInitTags';
 226+
 227+ // Strip <translate> tags etc. from source pages when rendering
 228+ $wgHooks['ParserBeforeStrip'][] = 'PageTranslationHooks::renderTagPage';
 229+
 230+ // Check syntax for <translate>
 231+ $wgHooks['ArticleSave'][] = 'PageTranslationHooks::tpSyntaxCheck';
 232+
 233+ // Add transtag to page props for discovery
 234+ $wgHooks['ArticleSaveComplete'][] = 'PageTranslationHooks::addTranstag';
 235+
 236+ // Prevent editing of unknown pages in Translations namespace
 237+ $wgHooks['getUserPermissionsErrorsExpensive'][] = 'PageTranslationHooks::translationsCheck';
 238+
 239+ $wgHooks['ArticleViewHeader'][] = 'PageTranslationHooks::test';
 240+
 241+ // Database schema
 242+ $wgHooks['LoadExtensionSchemaUpdates'][] = 'PageTranslationHooks::onLoadExtensionSchemaUpdates';
 243+
 244+ }
 245+
220246 }
221247
222 -
223248 function efTranslateInitTags( $parser ) {
224249 // For nice language list in-page
225 - $parser->setHook( 'languages', array( 'TranslateTagHooks', 'languages' ) );
 250+ $parser->setHook( 'languages', array( 'PageTranslationHooks', 'languages' ) );
226251 return true;
227252 }
 253+
 254+
 255+if ( !defined('TRANSLATE_CLI') ) {
 256+ function STDOUT() {}
 257+ function STDERR() {}
 258+}
 259+
 260+$wgGroupPermissions['staff' ]['pagetranslation'] = true;
Index: trunk/extensions/Translate/translate.sql
@@ -0,0 +1,16 @@
 2+-- SQL tables for Translate extension
 3+
 4+-- List of each section which has a name and text
 5+CREATE TABLE /*$wgDBprefix*/translate_sections (
 6+ -- Key to page_id
 7+ trs_page int unsigned NOT NULL,
 8+
 9+ -- Customizable section name
 10+ trs_key varchar(255) binary NOT NULL,
 11+
 12+ -- Section contents
 13+ trs_text mediumblob NOT NULL,
 14+
 15+ PRIMARY KEY (trs_page, trs_key),
 16+ KEY trs_page (trs_page)
 17+) /*$wgDBTableOptions*/;
\ No newline at end of file
Property changes on: trunk/extensions/Translate/translate.sql
___________________________________________________________________
Added: svn:eol-style
118 + native
Index: trunk/extensions/Translate/_autoload.php
@@ -38,6 +38,7 @@
3939 $wgAutoloadClasses['SpecialTranslations'] = $dir . 'SpecialTranslations.php';
4040 $wgAutoloadClasses['SpecialLanguageStats'] = $dir . 'SpecialLanguageStats.php';
4141
 42+
4243 $wgAutoloadClasses['SimpleFormatReader'] = $dir . 'ffs/Simple.php';
4344 $wgAutoloadClasses['SimpleFormatWriter'] = $dir . 'ffs/Simple.php';
4445 $wgAutoloadClasses['WikiFormatReader'] = $dir . 'ffs/Wiki.php';
@@ -66,6 +67,8 @@
6768 $wgAutoloadClasses['TranslatePreferences'] = $dir . 'utils/UserToggles.php';
6869 $wgAutoloadClasses['TranslateToolbox'] = $dir . 'utils/ToolBox.php';
6970
 71+$wgAutoloadClasses['MessageIndex'] = $dir . 'utils/MessageIndex.php';
 72+
7073 # predefined groups
7174 $wgAutoloadClasses['PremadeMediawikiExtensionGroups'] = $dir . 'groups/MediaWikiExtensions.php';
7275 $wgAutoloadClasses['CommonistMessageGroup'] = $dir . 'groups/Commonist.php';
@@ -74,8 +77,11 @@
7578 $wgAutoloadClasses['NoccMessageGroup'] = $dir . 'groups/Nocc.php';
7679
7780 # tag
78 -$wgAutoloadClasses['TranslateTag'] = $dir . 'tag/Tag.php';
79 -$wgAutoloadClasses['TranslateTagHooks'] = $dir . 'tag/Hooks.php';
80 -$wgAutoloadClasses['TranslateTagUtils'] = $dir . 'tag/Utils.php';
81 -$wgAutoloadClasses['FuzzyJob'] = $dir . 'tag/FuzzyJob.php';
82 -$wgAutoloadClasses['RenderJob'] = $dir . 'tag/RenderJob.php';
 81+#$wgAutoloadClasses['RenderJob'] = $dir . 'tag/RenderJob.php';
 82+# page translation
 83+$wgAutoloadClasses['PageTranslationHooks'] = $dir . 'tag/PageTranslationHooks.php';
 84+$wgAutoloadClasses['TranslatablePage'] = $dir . 'tag/TranslatablePage.php';
 85+$wgAutoloadClasses['TPException'] = $dir . 'tag/TranslatablePage.php';
 86+$wgAutoloadClasses['TPParse'] = $dir . 'tag/TPParse.php';
 87+$wgAutoloadClasses['TPSection'] = $dir . 'tag/TPSection.php';
 88+$wgAutoloadClasses['SpecialPageTranslation'] = $dir . 'tag/SpecialPageTranslation.php';
Index: trunk/extensions/Translate/utils/MessageIndex.php
@@ -0,0 +1,123 @@
 2+<?php
 3+/**
 4+ * Only used for page translation currently.
 5+ *
 6+ * @author Niklas Laxstrom
 7+ *
 8+ * @copyright Copyright © 2008-2009, Niklas Laxström
 9+ * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
 10+ * @file
 11+ */
 12+
 13+class MessageIndex {
 14+ static $cache = array();
 15+
 16+ // Nice shortcut
 17+ public static function titleToGroup( Title $title ) {
 18+ $namespace = $title->getNamespace();
 19+ $text = $title->getDBkey();
 20+ list( $key, ) = TranslateUtils::figureMessage( $text );
 21+ return self::messageToGroup( $namespace, $key );
 22+ }
 23+
 24+ public static function messageToGroup( $namespace, $key ) {
 25+ $namepace = MWNamespace::getCanonicalName( $namespace );
 26+ if ( $namespace === false ) return null;
 27+
 28+ $key = self::normaliseKey( $key );
 29+ $index = self::index( $namespace );
 30+ return @$index[$key];
 31+ }
 32+
 33+ public static function cache( $namespace = null ) {
 34+ if ( $namespace !== null ) {
 35+ $namepace = MWNamespace::getCanonicalName( $namespace );
 36+ if ( $namespace === false ) return null;
 37+ }
 38+
 39+ $groups = MessageGroups::singleton()->getGroups();
 40+
 41+ $hugearray = array();
 42+ $postponed = array();
 43+
 44+ STDOUT( "Working with ", 'main' );
 45+
 46+ foreach ( $groups as $g ) {
 47+ # the cache is split per namespace for efficieny
 48+ if ( $namespace !== null && $g->namespaces[0] !== $namespace )
 49+ continue;
 50+
 51+ # Skip meta thingies
 52+ if ( $g->isMeta() ) {
 53+ $postponed[] = $g;
 54+ continue;
 55+ }
 56+
 57+ self::checkAndAdd( $hugearray, $g );
 58+ }
 59+
 60+ foreach ( $postponed as $g ) {
 61+ self::checkAndAdd( $hugearray, $g, true );
 62+ }
 63+
 64+ foreach ( $hugearray as $ns => $array ) {
 65+ wfMkdirParents( dirname( self::file($ns) ) );
 66+ file_put_contents( self::file($ns), serialize( $array ) );
 67+ }
 68+ }
 69+
 70+ protected static function checkAndAdd( &$hugearray, $g, $ignore = false ) {
 71+ $messages = $g->getDefinitions();
 72+ $id = $g->getId();
 73+
 74+ if ( !is_array( $messages ) ) continue;
 75+
 76+ STDOUT( "$id ", 'main' );
 77+
 78+ $namespace = $g->namespaces[0];
 79+
 80+ foreach ( $messages as $key => $data ) {
 81+ # Force all keys to lower case, because the case doesn't matter and it is
 82+ # easier to do comparing when the case of first letter is unknown, because
 83+ # mediawiki forces it to upper case
 84+ $key = self::normaliseKey( $key );
 85+ if ( isset( $hugearray[$namespace][$key] ) ) {
 86+ if ( !$ignore )
 87+ STDERR( "Key $key already belongs to $hugearray[$namespace][$key], conflict with $id" );
 88+ } else {
 89+ $hugearray[$namespace][$key] = &$id;
 90+ }
 91+ }
 92+ unset( $id ); // Disconnect the previous references to this $id
 93+
 94+ }
 95+
 96+ protected static function file( $namespace ) {
 97+ $dir = realpath( dirname( __FILE__ ) . '/../data' );
 98+ $namepace = MWNamespace::getCanonicalName( $namespace );
 99+ return "$dir/messageindex-$namespace.ser";
 100+ }
 101+
 102+ protected static function normaliseKey( $key ) {
 103+ return str_replace( " ", "_", strtolower( $key ) );
 104+ }
 105+
 106+ protected static function index( $namespace ) {
 107+ if ( !isset(self::$cache[$namespace]) ) {
 108+
 109+ $file = self::file( $namespace );
 110+ if ( !file_exists( $file ) ) {
 111+ self::cache( $namespace );
 112+ }
 113+
 114+ if ( file_exists( $file ) ) {
 115+ self::$cache[$namespace] = unserialize( file_get_contents( $file ) );
 116+ } else {
 117+ self::$cache[$namespace] = array();
 118+ wfDebug( __METHOD__ . ": Message index missing." );
 119+ }
 120+ }
 121+
 122+ return self::$cache[$namespace];
 123+ }
 124+}
\ No newline at end of file
Property changes on: trunk/extensions/Translate/utils/MessageIndex.php
___________________________________________________________________
Added: svn:eol-style
1125 + native

Status & tagging log