29 class LanguageConverter {
36 static public $languagesWithVariants =
array(
48 public $mMainLanguageCode;
49 public $mVariants, $mVariantFallbacks, $mVariantNames;
50 public $mTablesLoaded =
false;
62 public $mDescCodeSep =
':', $mDescVarSep =
';';
63 public $mUcfirst =
false;
64 public $mConvRuleTitle =
false;
67 public $mHeaderVariant;
68 public $mMaxDepth = 10;
69 public $mVarSeparatorPattern;
71 const CACHE_VERSION_KEY =
'VERSION 7';
83 public function __construct( $langobj, $maincode, $variants =
array(),
85 $manualLevel =
array() ) {
86 global $wgDisabledVariants;
87 $this->mLangObj = $langobj;
88 $this->mMainLanguageCode = $maincode;
89 $this->mVariants = array_diff( $variants, $wgDisabledVariants );
90 $this->mVariantFallbacks = $variantfallbacks;
92 $this->mCacheKey =
wfMemcKey(
'conversiontables', $maincode );
93 $defaultflags =
array(
106 $this->mFlags = array_merge( $defaultflags,
$flags );
107 foreach ( $this->mVariants
as $v ) {
108 if ( array_key_exists( $v, $manualLevel ) ) {
109 $this->mManualLevel[$v] = $manualLevel[$v];
111 $this->mManualLevel[$v] =
'bidirectional';
113 $this->mFlags[$v] = $v;
123 public function getVariants() {
124 return $this->mVariants;
138 public function getVariantFallbacks( $variant ) {
139 if ( isset( $this->mVariantFallbacks[$variant] ) ) {
140 return $this->mVariantFallbacks[$variant];
142 return $this->mMainLanguageCode;
149 public function getConvRuleTitle() {
150 return $this->mConvRuleTitle;
157 public function getPreferredVariant() {
160 $req = $this->getURLVariant();
162 if (
$wgUser->isLoggedIn() && !$req ) {
163 $req = $this->getUserVariant();
165 $req = $this->getHeaderVariant();
168 if ( $wgDefaultLanguageVariant && !$req ) {
169 $req = $this->validateVariant( $wgDefaultLanguageVariant );
176 if ( $this->validateVariant( $req ) ) {
179 return $this->mMainLanguageCode;
187 public function getDefaultVariant() {
188 global $wgDefaultLanguageVariant;
190 $req = $this->getURLVariant();
193 $req = $this->getHeaderVariant();
196 if ( $wgDefaultLanguageVariant && !$req ) {
197 $req = $this->validateVariant( $wgDefaultLanguageVariant );
203 return $this->mMainLanguageCode;
211 public function validateVariant( $variant =
null ) {
212 if ( $variant !==
null && in_array( $variant, $this->mVariants ) ) {
223 public function getURLVariant() {
226 if ( $this->mURLVariant ) {
227 return $this->mURLVariant;
231 $ret = $wgRequest->getText(
'variant' );
234 $ret = $wgRequest->getVal(
'uselang' );
237 $this->mURLVariant = $this->validateVariant(
$ret );
238 return $this->mURLVariant;
246 protected function getUserVariant() {
260 if ( $this->mMainLanguageCode ==
$wgContLang->getCode() ) {
263 $ret =
$wgUser->getOption(
'variant-' . $this->mMainLanguageCode );
271 $this->mUserVariant = $this->validateVariant(
$ret );
272 return $this->mUserVariant;
280 protected function getHeaderVariant() {
283 if ( $this->mHeaderVariant ) {
284 return $this->mHeaderVariant;
289 $languages = array_keys( $wgRequest->getAcceptLang() );
294 $fallbackLanguages =
array();
296 $this->mHeaderVariant = $this->validateVariant( $language );
297 if ( $this->mHeaderVariant ) {
304 $fallbacks = $this->getVariantFallbacks( $language );
305 if ( is_string( $fallbacks ) && $fallbacks !== $this->mMainLanguageCode ) {
306 $fallbackLanguages[] = $fallbacks;
307 } elseif ( is_array( $fallbacks ) ) {
309 array_merge( $fallbackLanguages, $fallbacks );
313 if ( !$this->mHeaderVariant ) {
315 $fallback_languages = array_unique( $fallbackLanguages );
316 foreach ( $fallback_languages
as $language ) {
317 $this->mHeaderVariant = $this->validateVariant( $language );
318 if ( $this->mHeaderVariant ) {
324 return $this->mHeaderVariant;
337 public function autoConvert( $text, $toVariant =
false ) {
343 $toVariant = $this->getPreferredVariant();
350 if ( $this->guessVariant( $text, $toVariant ) ) {
362 $marker =
'|' .
$wgParser->UniqPrefix() .
'[\-a-zA-Z0-9]+';
368 $htmlfix =
'|<[^>]+$|^[^<>]*>';
371 $codefix =
'<code>.+?<\/code>|';
373 $scriptfix =
'<script.*?>.*?<\/script>|';
375 $prefix =
'<pre.*?>.*?<\/pre>|';
377 $reg =
'/' . $codefix . $scriptfix . $prefix .
378 '<[^>]+>|&[a-zA-Z#][a-z0-9]+;' . $marker . $htmlfix .
'/s';
384 $text = str_replace(
"\000",
'', $text );
386 $markupMatches =
null;
387 $elementMatches =
null;
388 while ( $startPos < strlen( $text ) ) {
389 if ( preg_match( $reg, $text, $markupMatches, PREG_OFFSET_CAPTURE, $startPos ) ) {
390 $elementPos = $markupMatches[0][1];
391 $element = $markupMatches[0][0];
393 $elementPos = strlen( $text );
398 $sourceBlob .= substr( $text, $startPos, $elementPos - $startPos ) .
"\000";
401 $startPos = $elementPos + strlen( $element );
405 && preg_match(
'/^(<[^>\s]*)\s([^>]*)(.*)$/', $element, $elementMatches )
409 foreach (
array(
'title',
'alt' )
as $attrName ) {
410 if ( !isset( $attrs[$attrName] ) ) {
413 $attr = $attrs[$attrName];
415 if ( !strpos( $attr,
'://' ) ) {
416 $attr = $this->recursiveConvertTopLevel( $attr, $toVariant );
420 $attr = preg_replace(
'/<[^>]+>/',
'', $attr );
421 if ( $attr !== $attrs[$attrName] ) {
422 $attrs[$attrName] = $attr;
431 $literalBlob .= $element .
"\000";
435 $translatedBlob = $this->
translate( $sourceBlob, $toVariant );
441 while ( $translatedIter->valid() && $literalIter->valid() ) {
442 $output .= $translatedIter->current();
443 $output .= $literalIter->current();
444 $translatedIter->next();
445 $literalIter->next();
461 public function translate( $text, $variant ) {
465 if ( trim( $text ) ) {
467 $text = $this->mTables[$variant]->replace( $text );
479 public function autoConvertToAllVariants( $text ) {
484 foreach ( $this->mVariants
as $variant ) {
497 protected function applyManualConv( $convRule ) {
502 $newConvRuleTitle = $convRule->getTitle();
503 if ( $newConvRuleTitle ) {
505 $this->mConvRuleTitle = $newConvRuleTitle;
509 $convTable = $convRule->getConvTable();
510 $action = $convRule->getRulesAction();
511 foreach ( $convTable
as $variant => $pair ) {
512 if ( !$this->validateVariant( $variant ) ) {
516 if ( $action ==
'add' ) {
517 foreach ( $pair
as $from => $to ) {
520 if (
$from || $to ) {
522 $this->mTables[$variant]->setPair(
$from, $to );
525 } elseif ( $action ==
'remove' ) {
526 $this->mTables[$variant]->removeArray( $pair );
538 public function convertTitle(
$title ) {
539 $variant = $this->getPreferredVariant();
540 $index =
$title->getNamespace();
542 $text = $this->convertNamespace( $index, $variant ) .
':';
557 public function convertNamespace( $index, $variant =
null ) {
558 if ( $variant ===
null ) {
559 $variant = $this->getPreferredVariant();
565 $nsConvMsg =
wfMessage(
'conversion-ns' . $index )->inLanguage( $variant );
566 if ( $nsConvMsg->exists() ) {
567 return $nsConvMsg->plain();
571 $nsConvMsg =
wfMessage(
'conversion-ns' . $index )->inContentLanguage();
572 if ( $nsConvMsg->exists() ) {
573 return $this->
translate( $nsConvMsg->plain(), $variant );
576 $langObj = $this->mLangObj->factory( $variant );
577 return $langObj->getFormattedNsText( $index );
595 public function convert( $text ) {
596 $variant = $this->getPreferredVariant();
597 return $this->convertTo( $text, $variant );
607 public function convertTo( $text, $variant ) {
608 global $wgDisableLangConversion;
609 if ( $wgDisableLangConversion ) {
613 $this->mConvRuleTitle =
false;
614 return $this->recursiveConvertTopLevel( $text, $variant );
626 protected function recursiveConvertTopLevel( $text, $variant, $depth = 0 ) {
629 $length = strlen( $text );
630 $shouldConvert = !$this->guessVariant( $text, $variant );
632 while ( $startPos < $length ) {
633 $pos = strpos( $text,
'-{', $startPos );
635 if ( $pos ===
false ) {
637 $fragment = substr( $text, $startPos );
638 $out .= $shouldConvert ? $this->autoConvert( $fragment, $variant ) : $fragment;
644 $fragment = substr( $text, $startPos, $pos - $startPos );
645 $out .= $shouldConvert ? $this->autoConvert( $fragment, $variant ) : $fragment;
651 $out .= $this->recursiveConvertRule( $text, $variant, $startPos, $depth + 1 );
668 protected function recursiveConvertRule( $text, $variant, &$startPos, $depth = 0 ) {
670 if ( $text[$startPos] !==
'-' || $text[$startPos + 1] !==
'{' ) {
671 throw new MWException( __METHOD__ .
': invalid input string' );
676 $warningDone =
false;
677 $length = strlen( $text );
679 while ( $startPos < $length ) {
681 preg_match(
'/-\{|\}-/', $text, $m, PREG_OFFSET_CAPTURE, $startPos );
692 $inner .= substr( $text, $startPos, $pos - $startPos );
700 if ( $depth >= $this->mMaxDepth ) {
702 if ( !$warningDone ) {
703 $inner .=
'<span class="error">' .
704 wfMessage(
'language-converter-depth-warning' )
705 ->numParams( $this->mMaxDepth )->inContentLanguage()->text() .
713 $inner .= $this->recursiveConvertRule( $text, $variant, $startPos, $depth + 1 );
719 $rule->parse( $variant );
720 $this->applyManualConv( $rule );
721 return $rule->getDisplay();
723 throw new MWException( __METHOD__ .
': invalid regex match' );
728 if ( $startPos < $length ) {
729 $inner .= substr( $text, $startPos );
732 return '-{' . $this->autoConvert( $inner, $variant );
746 public function findVariantLink( &
$link, &$nt, $ignoreOtherCond =
false ) {
747 # If the article has already existed, there is no need to
748 # check it again, otherwise it may cause a fault.
749 if ( is_object( $nt ) && $nt->exists() ) {
753 global $wgDisableLangConversion, $wgDisableTitleConversion, $wgRequest;
754 $isredir = $wgRequest->getText(
'redirect',
'yes' );
755 $action = $wgRequest->getText(
'action' );
756 $linkconvert = $wgRequest->getText(
'linkconvert',
'yes' );
757 $disableLinkConversion = $wgDisableLangConversion
758 || $wgDisableTitleConversion;
763 if ( $disableLinkConversion ||
764 ( !$ignoreOtherCond &&
767 || $action ==
'submit'
768 || $linkconvert ==
'no' ) ) ) {
772 if ( is_object( $nt ) ) {
773 $ns = $nt->getNamespace();
776 $variants = $this->autoConvertToAllVariants(
$link );
783 foreach ( $variants
as $v ) {
786 if ( !is_null( $varnt ) ) {
787 $linkBatch->addObj( $varnt );
794 $linkBatch->execute();
797 if ( $varnt->getArticleID() > 0 ) {
799 $link = $varnt->getText();
810 public function getExtraHashOptions() {
811 $variant = $this->getPreferredVariant();
812 return '!' . $variant;
825 public function guessVariant( $text, $variant ) {
836 function loadDefaultTables() {
837 $name = get_class( $this );
838 throw new MWException(
"Must implement loadDefaultTables() method in class $name" );
846 function loadTables( $fromCache =
true ) {
849 if ( $this->mTablesLoaded ) {
854 $this->mTablesLoaded =
true;
855 $this->mTables =
false;
861 if ( !$this->mTables || !array_key_exists( self::CACHE_VERSION_KEY, $this->mTables ) ) {
866 $this->loadDefaultTables();
867 foreach ( $this->mVariants
as $var ) {
868 $cached = $this->parseCachedTable( $var );
869 $this->mTables[$var]->mergeArray( $cached );
872 $this->postLoadTables();
873 $this->mTables[self::CACHE_VERSION_KEY] =
true;
884 function postLoadTables() { }
891 function reloadTables() {
892 if ( $this->mTables ) {
893 unset( $this->mTables );
895 $this->mTablesLoaded =
false;
896 $this->loadTables(
false );
918 function parseCachedTable( $code, $subpage =
'', $recursive =
true ) {
919 static $parsed =
array();
921 $key =
'Conversiontable/' . $code;
923 $key .=
'/' . $subpage;
925 if ( array_key_exists( $key, $parsed ) ) {
929 $parsed[$key] =
true;
931 if ( $subpage ===
'' ) {
940 $txt = $revision->getContent(
Revision::RAW )->getNativeData();
948 # Nothing to parse if there's no text
949 if ( $txt ===
false || $txt ===
null || $txt ===
'' ) {
955 $linkhead = $this->mLangObj->getNsText(
NS_MEDIAWIKI ) .
959 foreach ( $subs
as $sub ) {
960 $link = explode(
']]', $sub, 2 );
961 if ( count(
$link ) != 2 ) {
964 $b = explode(
'|',
$link[0], 2 );
965 $b = explode(
'/', trim( $b[0] ), 3 );
966 if ( count( $b ) == 3 ) {
972 if ( $b[0] == $linkhead && $b[1] == $code ) {
973 $sublinks[] = $sublink;
981 foreach ( $blocks
as $block ) {
987 $mappings = explode(
'}-', $block, 2 );
988 $stripped = str_replace(
array(
"'",
'"',
'*',
'#' ),
'', $mappings[0] );
990 foreach ( $table
as $t ) {
991 $m = explode(
'=>',
$t, 3 );
992 if ( count( $m ) != 2 ) {
996 $tt = explode(
'//', $m[1], 2 );
997 $ret[trim( $m[0] )] = trim( $tt[0] );
1003 foreach ( $sublinks
as $link ) {
1004 $s = $this->parseCachedTable( $code,
$link, $recursive );
1009 if ( $this->mUcfirst ) {
1010 foreach (
$ret as $k => $v ) {
1011 $ret[$this->mLangObj->ucfirst( $k )] = $this->mLangObj->ucfirst( $v );
1025 public function markNoConversion( $text, $noParse =
false ) {
1026 # don't mark if already marked
1027 if ( strpos( $text,
'-{' ) || strpos( $text,
'}-' ) ) {
1031 $ret =
"-{R|$text}-";
1043 function convertCategoryKey( $key ) {
1063 function OnPageContentSaveComplete( $page,
$user, $content,
$summary, $isMinor,
1065 $titleobj = $page->getTitle();
1067 $title = $titleobj->getDBkey();
1070 if ( $c > 1 &&
$t[0] ==
'Conversiontable' ) {
1071 if ( $this->validateVariant(
$t[1] ) ) {
1072 $this->reloadTables();
1088 public function armourMath( $text ) {
1091 $text = strtr( $text,
array(
'-{' =>
'-{',
'}-' =>
'}-' ) );
1098 function getVarSeparatorPattern() {
1099 if ( is_null( $this->mVarSeparatorPattern ) ) {
1112 foreach ( $this->mVariants
as $variant ) {
1114 $pat .= $variant .
'\s*:|';
1116 $pat .=
'[^;]*?=>\s*' . $variant .
'\s*:|';
1119 $this->mVarSeparatorPattern = $pat;
1121 return $this->mVarSeparatorPattern;