95 $variantfallbacks = [], $flags = [],
98 $this->mLangObj = $langobj;
99 $this->mMainLanguageCode = $maincode;
101 $this->mVariantFallbacks = $variantfallbacks;
116 $this->mFlags = array_merge( $defaultflags, $flags );
117 foreach ( $this->mVariants as $v ) {
118 if ( array_key_exists( $v, $manualLevel ) ) {
119 $this->mManualLevel[$v] = $manualLevel[$v];
121 $this->mManualLevel[$v] =
'bidirectional';
123 $this->mFlags[$v] = $v;
169 Hooks::run(
'GetLangPreferredVariant', [ &$req ] );
171 if ( $wgUser->isSafeToLoad() && $wgUser->isLoggedIn() && !$req ) {
177 if ( $wgDefaultLanguageVariant && !$req ) {
207 if ( $wgDefaultLanguageVariant && !$req ) {
227 if ( $variant ===
null ) {
233 if ( in_array( $variant, $this->mVariants ) ) {
240 foreach ( $this->mVariants as $v ) {
257 if ( $this->mURLVariant ) {
290 if ( !$wgUser->isSafeToLoad() ) {
293 if ( $wgUser->isLoggedIn() ) {
295 $this->mMainLanguageCode ==
296 MediaWikiServices::getInstance()->getContentLanguage()->getCode()
298 $ret = $wgUser->getOption(
'variant' );
300 $ret = $wgUser->getOption(
'variant-' . $this->mMainLanguageCode );
305 $ret = $wgUser->getOption(
'language' );
320 if ( $this->mHeaderVariant ) {
331 $fallbackLanguages = [];
334 if ( $this->mHeaderVariant ) {
342 if ( is_string( $fallbacks ) && $fallbacks !== $this->mMainLanguageCode ) {
343 $fallbackLanguages[] = $fallbacks;
344 } elseif ( is_array( $fallbacks ) ) {
346 array_merge( $fallbackLanguages, $fallbacks );
350 if ( !$this->mHeaderVariant ) {
352 $fallback_languages = array_unique( $fallbackLanguages );
353 foreach ( $fallback_languages as $language ) {
355 if ( $this->mHeaderVariant ) {
395 if ( $reg ===
null ) {
399 $htmlfix =
'|<[^>\004]++(?=\004$)|^[^<>]*+>';
406 $codefix =
'<code>[^<]*+(?:(?:(?!<\/code>).)[^<]*+)*+<\/code>|';
408 $scriptfix =
'<script[^>]*+>[^<]*+(?:(?:(?!<\/script>).)[^<]*+)*+<\/script>|';
410 $prefix =
'<pre[^>]*+>[^<]*+(?:(?:(?!<\/pre>).)[^<]*+)*+<\/pre>|';
413 $htmlFullTag =
'<(?:[^>=]*+(?>[^>=]*+=\s*+(?:"[^"]*"|\'[^\']*\'|[^\'">\s]*+))*+[^>=]*+>|.*+)|';
415 $reg =
'/' . $codefix . $scriptfix . $prefix . $htmlFullTag .
416 '&[a-zA-Z#][a-z0-9]++;' . $marker . $htmlfix .
'|\004$/s';
424 $text = str_replace(
"\000",
'', $text );
425 $text = str_replace(
"\004",
'', $text );
427 $markupMatches =
null;
428 $elementMatches =
null;
432 $textWithMarker = $text .
"\004";
433 while ( $startPos < strlen( $text ) ) {
434 if ( preg_match( $reg, $textWithMarker, $markupMatches, PREG_OFFSET_CAPTURE, $startPos ) ) {
435 $elementPos = $markupMatches[0][1];
436 $element = $markupMatches[0][0];
437 if ( $element ===
"\004" ) {
439 $elementPos = strlen( $text );
441 } elseif ( substr( $element, -1 ) ===
"\004" ) {
447 $element = substr( $element, 0, -1 );
454 $log = LoggerFactory::getInstance(
'languageconverter' );
455 $log->error(
"Hit pcre.backtrack_limit in " . __METHOD__
456 .
". Disabling language conversion for this page.",
458 "method" => __METHOD__,
459 "variant" => $toVariant,
460 "startOfText" => substr( $text, 0, 500 )
466 $sourceBlob .= substr( $text, $startPos, $elementPos - $startPos ) .
"\000";
469 $startPos = $elementPos + strlen( $element );
473 && preg_match(
'/^(<[^>\s]*+)\s([^>]*+)(.*+)$/', $element, $elementMatches )
482 $close = substr( $elementMatches[2], -1 ) ===
'/' ?
' /' :
'';
484 foreach ( [
'title',
'alt' ] as $attrName ) {
485 if ( !isset( $attrs[$attrName] ) ) {
488 $attr = $attrs[$attrName];
490 if ( !strpos( $attr,
'://' ) ) {
494 if ( $attr !== $attrs[$attrName] ) {
495 $attrs[$attrName] = $attr;
501 $close . $elementMatches[3];
504 $literalBlob .= $element .
"\000";
508 $translatedBlob = $this->
translate( $sourceBlob, $toVariant );
514 while ( $translatedIter->valid() && $literalIter->valid() ) {
515 $output .= $translatedIter->current();
516 $output .= $literalIter->current();
517 $translatedIter->next();
518 $literalIter->next();
536 if ( trim( $text ) ) {
538 $text = $this->mTables[$variant]->replace( $text );
553 foreach ( $this->mVariants as $variant ) {
554 $ret[$variant] = $this->
translate( $text, $variant );
570 $newConvRuleTitle = $convRule->getTitle();
571 if ( $newConvRuleTitle ) {
573 $this->mConvRuleTitle = $newConvRuleTitle;
577 $convTable = $convRule->getConvTable();
578 $action = $convRule->getRulesAction();
579 foreach ( $convTable as $variant => $pair ) {
585 if ( $action ==
'add' ) {
587 foreach ( $pair as $from => $to ) {
588 $this->mTables[$v]->setPair( $from, $to );
590 } elseif ( $action ==
'remove' ) {
591 $this->mTables[$v]->removeArray( $pair );
605 $index =
$title->getNamespace();
627 if ( $variant ===
null ) {
631 $cache = MediaWikiServices::getInstance()->getLocalServerObjectCache();
632 $key =
$cache->makeKey(
'languageconverter',
'namespace-text', $index, $variant );
633 $nsVariantText =
$cache->get( $key );
634 if ( $nsVariantText !==
false ) {
635 return $nsVariantText;
639 $nsConvMsg =
wfMessage(
'conversion-ns' . $index )->inLanguage( $variant );
640 if ( $nsConvMsg->exists() ) {
641 $nsVariantText = $nsConvMsg->plain();
646 if ( $nsVariantText ===
false ) {
647 $nsConvMsg =
wfMessage(
'conversion-ns' . $index )->inContentLanguage();
648 if ( $nsConvMsg->exists() ) {
649 $nsVariantText = $this->
translate( $nsConvMsg->plain(), $variant );
653 if ( $nsVariantText ===
false ) {
655 $langObj = $this->mLangObj->factory( $variant );
656 $nsVariantText = $langObj->getFormattedNsText( $index );
659 $cache->set( $key, $nsVariantText, 60 );
661 return $nsVariantText;
684 return $this->
convertTo( $text, $variant );
702 $this->mConvRuleTitle =
false;
718 $length = strlen( $text );
719 $shouldConvert = !$this->
guessVariant( $text, $variant );
722 $noScript =
'<script.*?>.*?<\/script>(*SKIP)(*FAIL)';
723 $noStyle =
'<style.*?>.*?<\/style>(*SKIP)(*FAIL)';
725 $noHtml =
'<(?:[^>=]*+(?>[^>=]*+=\s*+(?:"[^"]*"|\'[^\']*\'|[^\'">\s]*+))*+[^>=]*+>|.*+)(*SKIP)(*FAIL)';
726 while ( $startPos < $length && $continue ) {
727 $continue = preg_match(
729 "/$noScript|$noStyle|$noHtml|-\{/",
738 $fragment = substr( $text, $startPos );
739 $out .= $shouldConvert ? $this->
autoConvert( $fragment, $variant ) : $fragment;
747 $fragment = substr( $text, $startPos, $pos - $startPos );
748 $out .= $shouldConvert ? $this->
autoConvert( $fragment, $variant ) : $fragment;
772 if ( $text[$startPos] !==
'-' || $text[$startPos + 1] !==
'{' ) {
773 throw new MWException( __METHOD__ .
': invalid input string' );
778 $warningDone =
false;
779 $length = strlen( $text );
781 while ( $startPos < $length ) {
783 preg_match(
'/-\{|\}-/', $text, $m, PREG_OFFSET_CAPTURE, $startPos );
794 $inner .= substr( $text, $startPos, $pos - $startPos );
802 if ( $depth >= $this->mMaxDepth ) {
804 if ( !$warningDone ) {
805 $inner .=
'<span class="error">' .
806 wfMessage(
'language-converter-depth-warning' )
807 ->numParams( $this->mMaxDepth )->inContentLanguage()->text() .
821 $rule->parse( $variant );
823 return $rule->getDisplay();
825 throw new MWException( __METHOD__ .
': invalid regex match' );
830 if ( $startPos < $length ) {
831 $inner .= substr( $text, $startPos );
834 return '-{' . $this->
autoConvert( $inner, $variant );
849 # If the article has already existed, there is no need to
850 # check it again, otherwise it may cause a fault.
851 if ( is_object( $nt ) && $nt->exists() ) {
856 $isredir =
$wgRequest->getText(
'redirect',
'yes' );
858 if ( $action ==
'edit' &&
$wgRequest->getBool(
'redlink' ) ) {
861 $linkconvert =
$wgRequest->getText(
'linkconvert',
'yes' );
868 if ( $disableLinkConversion ||
869 ( !$ignoreOtherCond &&
872 || $action ==
'submit'
873 || $linkconvert ==
'no' ) ) ) {
877 if ( is_object( $nt ) ) {
878 $ns = $nt->getNamespace();
888 foreach ( $variants as $v ) {
891 if ( !is_null( $varnt ) ) {
892 $linkBatch->addObj( $varnt );
899 $linkBatch->execute();
901 foreach ( $titles as $varnt ) {
902 if ( $varnt->getArticleID() > 0 ) {
904 $link = $varnt->getText();
918 return '!' . $variant;
943 $class = static::class;
944 throw new MWException(
"Must implement loadDefaultTables() method in class $class" );
955 if ( $this->mTablesLoaded ) {
959 $this->mTablesLoaded =
true;
960 $this->mTables =
null;
962 $cacheKey =
$cache->makeKey(
'conversiontables', $this->mMainLanguageCode );
964 $this->mTables =
$cache->get( $cacheKey );
966 if ( !$this->mTables || !array_key_exists( self::CACHE_VERSION_KEY, $this->mTables ) ) {
971 foreach ( $this->mVariants as $var ) {
973 $this->mTables[$var]->mergeArray( $cached );
979 $cache->set( $cacheKey, $this->mTables, 43200 );
997 if ( $this->mTables ) {
999 unset( $this->mTables );
1002 $this->mTablesLoaded =
false;
1026 static $parsed = [];
1028 $key =
'Conversiontable/' . $code;
1030 $key .=
'/' . $subpage;
1032 if ( array_key_exists( $key, $parsed ) ) {
1036 $parsed[$key] =
true;
1038 if ( $subpage ===
'' ) {
1048 $txt = $revision->getContent( RevisionRecord::RAW )->getText();
1056 # Nothing to parse if there's no text
1057 if ( $txt ===
false || $txt ===
null || $txt ===
'' ) {
1063 $linkhead = $this->mLangObj->getNsText(
NS_MEDIAWIKI ) .
1067 foreach ( $subs as $sub ) {
1068 $link = explode(
']]', $sub, 2 );
1069 if ( count( $link ) != 2 ) {
1072 $b = explode(
'|', $link[0], 2 );
1073 $b = explode(
'/', trim( $b[0] ), 3 );
1074 if ( count( $b ) == 3 ) {
1080 if ( $b[0] == $linkhead && $b[1] == $code ) {
1081 $sublinks[] = $sublink;
1089 foreach ( $blocks as $block ) {
1095 $mappings = explode(
'}-', $block, 2 )[0];
1096 $stripped = str_replace( [
"'",
'"',
'*',
'#' ],
'', $mappings );
1098 foreach ( $table as
$t ) {
1099 $m = explode(
'=>',
$t, 3 );
1100 if ( count( $m ) != 2 ) {
1104 $tt = explode(
'//', $m[1], 2 );
1105 $ret[trim( $m[0] )] = trim( $tt[0] );
1111 foreach ( $sublinks as $link ) {
1117 if ( $this->mUcfirst ) {
1118 foreach ( $ret as $k => $v ) {
1119 $ret[$this->mLangObj->ucfirst( $k )] = $this->mLangObj->ucfirst( $v );
1134 # don't mark if already marked
1135 if ( strpos( $text,
'-{' ) || strpos( $text,
'}-' ) ) {
1139 $ret =
"-{R|$text}-";
1166 if ( $c > 1 &&
$t[0] ==
'Conversiontable' ) {
1179 if ( is_null( $this->mVarSeparatorPattern ) ) {
1191 $expandedVariants = [];
1192 foreach ( $this->mVariants as $variant ) {
1193 $expandedVariants[ $variant ] = 1;
1199 if ( isset( $expandedVariants[ $new ] ) ) {
1200 $expandedVariants[ $old ] = 1;
1205 foreach ( $expandedVariants as $variant => $ignore ) {
1207 $pat .= $variant .
'\s*:|';
1209 $pat .=
'[^;]*?=>\s*' . $variant .
'\s*:|';
1212 $this->mVarSeparatorPattern = $pat;