MediaWiki  master
LanguageConverter.php
Go to the documentation of this file.
1 <?php
31 
37 abstract class LanguageConverter implements ILanguageConverter {
39 
45  public static $languagesWithVariants = [
46  'ban',
47  'en',
48  'crh',
49  'gan',
50  'iu',
51  'kk',
52  'ku',
53  'shi',
54  'sr',
55  'tg',
56  'uz',
57  'zh',
58  ];
59 
60  private $mTablesLoaded = false;
61 
65  protected $mTables;
66 
70  private $mLangObj;
71 
72  private $mUcfirst = false;
73  private $mConvRuleTitle = false;
74  private $mURLVariant;
75  private $mUserVariant;
76  private $mHeaderVariant;
77  private $mMaxDepth = 10;
79 
80  private const CACHE_VERSION_KEY = 'VERSION 7';
81 
85  public function __construct( $langobj ) {
86  $this->deprecatePublicProperty( 'mUcfirst', '1.35', __CLASS__ );
87  $this->deprecatePublicProperty( 'mConvRuleTitle', '1.35', __CLASS__ );
88  $this->deprecatePublicProperty( 'mUserVariant', '1.35', __CLASS__ );
89  $this->deprecatePublicProperty( 'mHeaderVariant', '1.35', __CLASS__ );
90  $this->deprecatePublicProperty( 'mMaxDepth = 10', '1.35', __CLASS__ );
91  $this->deprecatePublicProperty( 'mVarSeparatorPattern', '1.35', __CLASS__ );
92  $this->deprecatePublicProperty( 'mLangObj', '1.35', __CLASS__ );
93  $this->deprecatePublicProperty( 'mTablesLoaded', '1.35', __CLASS__ );
94  $this->deprecatePublicProperty( 'mTables', '1.35', __CLASS__ );
95 
96  $this->mLangObj = $langobj;
97 
98  $this->deprecatePublicPropertyFallback( 'mVariants', '1.36', function () {
99  return $this->getVariants();
100  } );
101 
102  $this->deprecatePublicPropertyFallback( 'mMainLanguageCode', '1.36', function () {
103  return $this->getMainCode();
104  } );
105 
106  $this->deprecatePublicPropertyFallback( 'mVariantFallbacks', '1.36', function () {
107  return $this->getVariantsFallbacks();
108  } );
109 
110  $this->deprecatePublicPropertyFallback( 'mFlags', '1.36', function () {
111  return $this->getFlags();
112  } );
113 
114  $this->deprecatePublicPropertyFallback( 'mVariantNames', '1.36', function () {
115  return $this->getVariantNames();
116  } );
117 
118  $this->deprecatePublicPropertyFallback( 'mDescCodeSep', '1.36', function () {
119  return $this->getDescCodeSeparator();
120  } );
121 
122  $this->deprecatePublicPropertyFallback( 'mDescVarSep', '1.36', function () {
123  return $this->getDescVarSeparator();
124  } );
125  }
126 
133  abstract public function getMainCode(): string;
134 
141  abstract protected function getLanguageVariants(): array;
142 
149  abstract public function getVariantsFallbacks(): array;
150 
157  final public function getFlags(): array {
158  $defaultflags = [
159  // 'S' show converted text
160  // '+' add rules for alltext
161  // 'E' the gave flags is error
162  // these flags above are reserved for program
163  'A' => 'A', // add rule for convert code (all text convert)
164  'T' => 'T', // title convert
165  'R' => 'R', // raw content
166  'D' => 'D', // convert description (subclass implement)
167  '-' => '-', // remove convert (not implement)
168  'H' => 'H', // add rule for convert code (but no display in placed code)
169  'N' => 'N', // current variant name
170  ];
171  $flags = array_merge( $defaultflags, $this->getAdditionalFlags() );
172  foreach ( $this->getVariants() as $v ) {
173  $flags[$v] = $v;
174  }
175  return $flags;
176  }
177 
184  protected function getAdditionalFlags(): array {
185  return [];
186  }
187 
194  final public function getManualLevel() {
195  $manualLevel = $this->getAdditionalManualLevel();
196  $result = [];
197  foreach ( $this->getVariants() as $v ) {
198  if ( array_key_exists( $v, $manualLevel ) ) {
199  $result[$v] = $manualLevel[$v];
200  } else {
201  $result[$v] = 'bidirectional';
202  }
203  }
204  return $result;
205  }
206 
214  protected function getAdditionalManualLevel(): array {
215  return [];
216  }
217 
225  public function getDescCodeSeparator(): string {
226  return ':';
227  }
228 
236  public function getDescVarSeparator(): string {
237  return ';';
238  }
239 
245  public function getVariantNames(): array {
246  return MediaWikiServices::getInstance()
247  ->getLanguageNameUtils()
248  ->getLanguageNames();
249  }
250 
256  final public function getVariants() {
257  $disabledVariants = MediaWikiServices::getInstance()->getMainConfig()->get( 'DisabledVariants' );
258  return array_diff( $this->getLanguageVariants(), $disabledVariants );
259  }
260 
272  public function getVariantFallbacks( $variant ) {
273  return $this->getVariantsFallbacks()[$variant] ?? $this->getMainCode();
274  }
275 
280  public function getConvRuleTitle() {
281  return $this->mConvRuleTitle;
282  }
283 
288  public function getPreferredVariant() {
289  $defaultLanguageVariant = MediaWikiServices::getInstance()->getMainConfig()->get( 'DefaultLanguageVariant' );
290 
291  $req = $this->getURLVariant();
292 
293  Hooks::runner()->onGetLangPreferredVariant( $req );
294 
295  $user = RequestContext::getMain()->getUser();
296  // NOTE: For some calls there may not be a context user or session that is safe
297  // to use, see (T235360)
298  // Use case: During autocreation, UserNameUtils::isUsable is called which uses interface
299  // messages for reserved usernames.
300  if ( $user->isSafeToLoad() && $user->isRegistered() && !$req ) {
301  $req = $this->getUserVariant( $user );
302  } elseif ( !$req ) {
303  $req = $this->getHeaderVariant();
304  }
305 
306  if ( $defaultLanguageVariant && !$req ) {
307  $req = $this->validateVariant( $defaultLanguageVariant );
308  }
309 
310  $req = $this->validateVariant( $req );
311 
312  // This function, unlike the other get*Variant functions, is
313  // not memoized (i.e. there return value is not cached) since
314  // new information might appear during processing after this
315  // is first called.
316  if ( $req ) {
317  return $req;
318  }
319  return $this->getMainCode();
320  }
321 
326  public function getDefaultVariant() {
327  $defaultLanguageVariant = MediaWikiServices::getInstance()->getMainConfig()->get( 'DefaultLanguageVariant' );
328 
329  $req = $this->getURLVariant();
330 
331  if ( !$req ) {
332  $req = $this->getHeaderVariant();
333  }
334 
335  if ( $defaultLanguageVariant && !$req ) {
336  $req = $this->validateVariant( $defaultLanguageVariant );
337  }
338 
339  if ( $req ) {
340  return $req;
341  }
342  return $this->getMainCode();
343  }
344 
354  public function validateVariant( $variant = null ) {
355  if ( $variant === null ) {
356  return null;
357  }
358  // Our internal variants are always lower-case; the variant we
359  // are validating may have mixed case.
360  $variant = LanguageCode::replaceDeprecatedCodes( strtolower( $variant ) );
361  if ( in_array( $variant, $this->getVariants() ) ) {
362  return $variant;
363  }
364  // Browsers are supposed to use BCP 47 standard in the
365  // Accept-Language header, but not all of our internal
366  // mediawiki variant codes are BCP 47. Map BCP 47 code
367  // to our internal code.
368  foreach ( $this->getVariants() as $v ) {
369  // Case-insensitive match (BCP 47 is mixed case)
370  if ( strtolower( LanguageCode::bcp47( $v ) ) === $variant ) {
371  return $v;
372  }
373  }
374  return null;
375  }
376 
382  public function getURLVariant() {
383  global $wgRequest;
384 
385  if ( $this->mURLVariant ) {
386  return $this->mURLVariant;
387  }
388 
389  // see if the preference is set in the request
390  $ret = $wgRequest->getText( 'variant' );
391 
392  if ( !$ret ) {
393  $ret = $wgRequest->getVal( 'uselang' );
394  }
395 
396  $this->mURLVariant = $this->validateVariant( $ret );
397  return $this->mURLVariant;
398  }
399 
406  protected function getUserVariant( User $user ) {
407  // This should only be called within the class after the user is known to be
408  // safe to load and logged in, but check just in case.
409  if ( !$user->isSafeToLoad() ) {
410  return false;
411  }
412 
413  if ( !$this->mUserVariant ) {
414  $services = MediaWikiServices::getInstance();
415  if ( $user->isRegistered() ) {
416  // Get language variant preference from logged in users
417  if (
418  $this->getMainCode() ==
419  $services->getContentLanguage()->getCode()
420  ) {
421  $optionName = 'variant';
422  } else {
423  $optionName = 'variant-' . $this->getMainCode();
424  }
425  } else {
426  // figure out user lang without constructing wgLang to avoid
427  // infinite recursion
428  $optionName = 'language';
429  }
430  $ret = $services->getUserOptionsLookup()->getOption( $user, $optionName );
431 
432  $this->mUserVariant = $this->validateVariant( $ret );
433  }
434 
435  return $this->mUserVariant;
436  }
437 
443  protected function getHeaderVariant() {
444  global $wgRequest;
445 
446  if ( $this->mHeaderVariant ) {
447  return $this->mHeaderVariant;
448  }
449 
450  // See if some supported language variant is set in the
451  // HTTP header.
452  $languages = array_keys( $wgRequest->getAcceptLang() );
453  if ( empty( $languages ) ) {
454  return null;
455  }
456 
457  $fallbackLanguages = [];
458  foreach ( $languages as $language ) {
459  $this->mHeaderVariant = $this->validateVariant( $language );
460  if ( $this->mHeaderVariant ) {
461  break;
462  }
463 
464  // To see if there are fallbacks of current language.
465  // We record these fallback variants, and process
466  // them later.
467  $fallbacks = $this->getVariantFallbacks( $language );
468  if ( is_string( $fallbacks ) && $fallbacks !== $this->getMainCode() ) {
469  $fallbackLanguages[] = $fallbacks;
470  } elseif ( is_array( $fallbacks ) ) {
471  $fallbackLanguages =
472  array_merge( $fallbackLanguages, $fallbacks );
473  }
474  }
475 
476  if ( !$this->mHeaderVariant ) {
477  // process fallback languages now
478  $fallback_languages = array_unique( $fallbackLanguages );
479  foreach ( $fallback_languages as $language ) {
480  $this->mHeaderVariant = $this->validateVariant( $language );
481  if ( $this->mHeaderVariant ) {
482  break;
483  }
484  }
485  }
486 
487  return $this->mHeaderVariant;
488  }
489 
500  public function autoConvert( $text, $toVariant = false ) {
501  $this->loadTables();
502 
503  if ( !$toVariant ) {
504  $toVariant = $this->getPreferredVariant();
505  if ( !$toVariant ) {
506  return $text;
507  }
508  }
509 
510  if ( $this->guessVariant( $text, $toVariant ) ) {
511  return $text;
512  }
513  /* we convert everything except:
514  1. HTML markups (anything between < and >)
515  2. HTML entities
516  3. placeholders created by the parser
517  IMPORTANT: Beware of failure from pcre.backtrack_limit (T124404).
518  Minimize use of backtracking where possible.
519  */
520  static $reg;
521  if ( $reg === null ) {
522  $marker = '|' . Parser::MARKER_PREFIX . '[^\x7f]++\x7f';
523 
524  // this one is needed when the text is inside an HTML markup
525  $htmlfix = '|<[^>\004]++(?=\004$)|^[^<>]*+>';
526 
527  // Optimize for the common case where these tags have
528  // few or no children. Thus try and possessively get as much as
529  // possible, and only engage in backtracking when we hit a '<'.
530 
531  // disable convert to variants between <code> tags
532  $codefix = '<code>[^<]*+(?:(?:(?!<\/code>).)[^<]*+)*+<\/code>|';
533  // disable conversion of <script> tags
534  $scriptfix = '<script[^>]*+>[^<]*+(?:(?:(?!<\/script>).)[^<]*+)*+<\/script>|';
535  // disable conversion of <pre> tags
536  $prefix = '<pre[^>]*+>[^<]*+(?:(?:(?!<\/pre>).)[^<]*+)*+<\/pre>|';
537  // The "|.*+)" at the end, is in case we missed some part of html syntax,
538  // we will fail securely (hopefully) by matching the rest of the string.
539  $htmlFullTag = '<(?:[^>=]*+(?>[^>=]*+=\s*+(?:"[^"]*"|\'[^\']*\'|[^\'">\s]*+))*+[^>=]*+>|.*+)|';
540 
541  $reg = '/' . $codefix . $scriptfix . $prefix . $htmlFullTag .
542  '&[a-zA-Z#][a-z0-9]++;' . $marker . $htmlfix . '|\004$/s';
543  }
544  $startPos = 0;
545  $sourceBlob = '';
546  $literalBlob = '';
547 
548  // Guard against delimiter nulls in the input
549  // (should never happen: see T159174)
550  $text = str_replace( "\000", '', $text );
551  $text = str_replace( "\004", '', $text );
552 
553  $markupMatches = null;
554  $elementMatches = null;
555 
556  // We add a marker (\004) at the end of text, to ensure we always match the
557  // entire text (Otherwise, pcre.backtrack_limit might cause silent failure)
558  $textWithMarker = $text . "\004";
559  while ( $startPos < strlen( $text ) ) {
560  if ( preg_match( $reg, $textWithMarker, $markupMatches, PREG_OFFSET_CAPTURE, $startPos ) ) {
561  $elementPos = $markupMatches[0][1];
562  $element = $markupMatches[0][0];
563  if ( $element === "\004" ) {
564  // We hit the end.
565  $elementPos = strlen( $text );
566  $element = '';
567  } elseif ( substr( $element, -1 ) === "\004" ) {
568  // This can sometimes happen if we have
569  // unclosed html tags (For example
570  // when converting a title attribute
571  // during a recursive call that contains
572  // a &lt; e.g. <div title="&lt;">.
573  $element = substr( $element, 0, -1 );
574  }
575  } else {
576  // If we hit here, then Language Converter could be tricked
577  // into doing an XSS, so we refuse to translate.
578  // If non-crazy input manages to reach this code path,
579  // we should consider it a bug.
580  $log = LoggerFactory::getInstance( 'languageconverter' );
581  $log->error( "Hit pcre.backtrack_limit in " . __METHOD__
582  . ". Disabling language conversion for this page.",
583  [
584  "method" => __METHOD__,
585  "variant" => $toVariant,
586  "startOfText" => substr( $text, 0, 500 )
587  ]
588  );
589  return $text;
590  }
591  // Queue the part before the markup for translation in a batch
592  $sourceBlob .= substr( $text, $startPos, $elementPos - $startPos ) . "\000";
593 
594  // Advance to the next position
595  $startPos = $elementPos + strlen( $element );
596 
597  // Translate any alt or title attributes inside the matched element
598  if ( $element !== ''
599  && preg_match( '/^(<[^>\s]*+)\s([^>]*+)(.*+)$/', $element, $elementMatches )
600  ) {
601  // FIXME, this decodes entities, so if you have something
602  // like <div title="foo&lt;bar"> the bar won't get
603  // translated since after entity decoding it looks like
604  // unclosed html and we call this method recursively
605  // on attributes.
606  $attrs = Sanitizer::decodeTagAttributes( $elementMatches[2] );
607  // Ensure self-closing tags stay self-closing.
608  $close = substr( $elementMatches[2], -1 ) === '/' ? ' /' : '';
609  $changed = false;
610  foreach ( [ 'title', 'alt' ] as $attrName ) {
611  if ( !isset( $attrs[$attrName] ) ) {
612  continue;
613  }
614  $attr = $attrs[$attrName];
615  // Don't convert URLs
616  if ( !strpos( $attr, '://' ) ) {
617  $attr = $this->recursiveConvertTopLevel( $attr, $toVariant );
618  }
619 
620  if ( $attr !== $attrs[$attrName] ) {
621  $attrs[$attrName] = $attr;
622  $changed = true;
623  }
624  }
625  if ( $changed ) {
626  // @phan-suppress-next-line SecurityCheck-DoubleEscaped Explained above with decodeTagAttributes
627  $element = $elementMatches[1] . Html::expandAttributes( $attrs ) .
628  $close . $elementMatches[3];
629  }
630  }
631  $literalBlob .= $element . "\000";
632  }
633 
634  // Do the main translation batch
635  $translatedBlob = $this->translate( $sourceBlob, $toVariant );
636 
637  // Put the output back together
638  $translatedIter = StringUtils::explode( "\000", $translatedBlob );
639  $literalIter = StringUtils::explode( "\000", $literalBlob );
640  $output = '';
641  while ( $translatedIter->valid() && $literalIter->valid() ) {
642  $output .= $translatedIter->current();
643  $output .= $literalIter->current();
644  $translatedIter->next();
645  $literalIter->next();
646  }
647 
648  return $output;
649  }
650 
660  public function translate( $text, $variant ) {
661  // If $text is empty or only includes spaces, do nothing
662  // Otherwise translate it
663  if ( trim( $text ) ) {
664  $this->loadTables();
665  $text = $this->mTables[$variant]->replace( $text );
666  }
667  return $text;
668  }
669 
676  public function autoConvertToAllVariants( $text ) {
677  $this->loadTables();
678 
679  $ret = [];
680  foreach ( $this->getVariants() as $variant ) {
681  $ret[$variant] = $this->translate( $text, $variant );
682  }
683 
684  return $ret;
685  }
686 
692  protected function applyManualConv( ConverterRule $convRule ) {
693  // Use syntax -{T|zh-cn:TitleCN; zh-tw:TitleTw}- to custom
694  // title conversion.
695  // T26072: $mConvRuleTitle was overwritten by other manual
696  // rule(s) not for title, this breaks the title conversion.
697  $newConvRuleTitle = $convRule->getTitle();
698  if ( $newConvRuleTitle !== false ) {
699  // So I add an empty check for getTitle()
700  $this->mConvRuleTitle = $newConvRuleTitle;
701  }
702 
703  // merge/remove manual conversion rules to/from global table
704  $convTable = $convRule->getConvTable();
705  $action = $convRule->getRulesAction();
706  foreach ( $convTable as $variant => $pair ) {
707  $v = $this->validateVariant( $variant );
708  if ( !$v ) {
709  continue;
710  }
711 
712  if ( $action == 'add' ) {
713  // More efficient than array_merge(), about 2.5 times.
714  foreach ( $pair as $from => $to ) {
715  $this->mTables[$v]->setPair( $from, $to );
716  }
717  } elseif ( $action == 'remove' ) {
718  $this->mTables[$v]->removeArray( $pair );
719  }
720  }
721  }
722 
730  public function convertTitle( $title ) {
731  $variant = $this->getPreferredVariant();
732  $index = $title->getNamespace();
733  if ( $index !== NS_MAIN ) {
734  $text = $this->convertNamespace( $index, $variant ) . ':';
735  } else {
736  $text = '';
737  }
738  $name = str_replace( '_', ' ', $title->getDBKey() );
739  $text .= $this->translate( $name, $variant );
740 
741  return $text;
742  }
743 
751  public function convertNamespace( $index, $variant = null ) {
752  if ( $index === NS_MAIN ) {
753  return '';
754  }
755 
756  if ( $variant === null ) {
757  $variant = $this->getPreferredVariant();
758  }
759 
760  $cache = MediaWikiServices::getInstance()->getLocalServerObjectCache();
761  $key = $cache->makeKey( 'languageconverter', 'namespace-text', $index, $variant );
762  return $cache->getWithSetCallback(
763  $key,
764  BagOStuff::TTL_MINUTE,
765  function () use ( $index, $variant ) {
766  return $this->computeNsVariantText( $index, $variant );
767  }
768  );
769  }
770 
776  private function computeNsVariantText( int $index, ?string $variant ): string {
777  $nsVariantText = false;
778 
779  // First check if a message gives a converted name in the target variant.
780  $nsConvMsg = wfMessage( 'conversion-ns' . $index )->inLanguage( $variant );
781  if ( $nsConvMsg->exists() ) {
782  $nsVariantText = $nsConvMsg->plain();
783  }
784 
785  // Then check if a message gives a converted name in content language
786  // which needs extra translation to the target variant.
787  if ( $nsVariantText === false ) {
788  $nsConvMsg = wfMessage( 'conversion-ns' . $index )->inContentLanguage();
789  if ( $nsConvMsg->exists() ) {
790  $nsVariantText = $this->translate( $nsConvMsg->plain(), $variant );
791  }
792  }
793 
794  if ( $nsVariantText === false ) {
795  // No message exists, retrieve it from the target variant's namespace names.
796  $mLangObj = MediaWikiServices::getInstance()
797  ->getLanguageFactory()
798  ->getLanguage( $variant );
799  $nsVariantText = $mLangObj->getFormattedNsText( $index );
800  }
801  return $nsVariantText;
802  }
803 
822  public function convert( $text ) {
823  $variant = $this->getPreferredVariant();
824  return $this->convertTo( $text, $variant );
825  }
826 
836  public function convertTo( $text, $variant ) {
837  $languageConverterFactory = MediaWikiServices::getInstance()->getLanguageConverterFactory();
838  if ( $languageConverterFactory->isConversionDisabled() ) {
839  return $text;
840  }
841  // Reset converter state for a new converter run.
842  $this->mConvRuleTitle = false;
843  return $this->recursiveConvertTopLevel( $text, $variant );
844  }
845 
855  protected function recursiveConvertTopLevel( $text, $variant, $depth = 0 ) {
856  $startPos = 0;
857  $out = '';
858  $length = strlen( $text );
859  $shouldConvert = !$this->guessVariant( $text, $variant );
860  $continue = true;
861 
862  $noScript = '<script.*?>.*?<\/script>(*SKIP)(*FAIL)';
863  $noStyle = '<style.*?>.*?<\/style>(*SKIP)(*FAIL)';
864  // phpcs:ignore Generic.Files.LineLength
865  $noHtml = '<(?:[^>=]*+(?>[^>=]*+=\s*+(?:"[^"]*"|\'[^\']*\'|[^\'">\s]*+))*+[^>=]*+>|.*+)(*SKIP)(*FAIL)';
866  while ( $startPos < $length && $continue ) {
867  $continue = preg_match(
868  // Only match -{ outside of html.
869  "/$noScript|$noStyle|$noHtml|-\{/",
870  $text,
871  $m,
872  PREG_OFFSET_CAPTURE,
873  $startPos
874  );
875 
876  if ( !$continue ) {
877  // No more markup, append final segment
878  $fragment = substr( $text, $startPos );
879  $out .= $shouldConvert ? $this->autoConvert( $fragment, $variant ) : $fragment;
880  return $out;
881  }
882 
883  // Offset of the match of the regex pattern.
884  $pos = $m[0][1];
885 
886  // Append initial segment
887  $fragment = substr( $text, $startPos, $pos - $startPos );
888  $out .= $shouldConvert ? $this->autoConvert( $fragment, $variant ) : $fragment;
889  // -{ marker found, not in attribute
890  // Advance position up to -{ marker.
891  $startPos = $pos;
892  // Do recursive conversion
893  // Note: This passes $startPos by reference, and advances it.
894  $out .= $this->recursiveConvertRule( $text, $variant, $startPos, $depth + 1 );
895  }
896  return $out;
897  }
898 
910  protected function recursiveConvertRule( $text, $variant, &$startPos, $depth = 0 ) {
911  // Quick check (no function calls)
912  if ( $text[$startPos] !== '-' || $text[$startPos + 1] !== '{' ) {
913  throw new MWException( __METHOD__ . ': invalid input string' );
914  }
915 
916  $startPos += 2;
917  $inner = '';
918  $warningDone = false;
919  $length = strlen( $text );
920 
921  while ( $startPos < $length ) {
922  $m = false;
923  preg_match( '/-\{|\}-/', $text, $m, PREG_OFFSET_CAPTURE, $startPos );
924  if ( !$m ) {
925  // Unclosed rule
926  break;
927  }
928 
929  $token = $m[0][0];
930  $pos = $m[0][1];
931 
932  // Markup found
933  // Append initial segment
934  $inner .= substr( $text, $startPos, $pos - $startPos );
935 
936  // Advance position
937  $startPos = $pos;
938 
939  switch ( $token ) {
940  case '-{':
941  // Check max depth
942  if ( $depth >= $this->mMaxDepth ) {
943  $inner .= '-{';
944  if ( !$warningDone ) {
945  $inner .= '<span class="error">' .
946  wfMessage( 'language-converter-depth-warning' )
947  ->numParams( $this->mMaxDepth )->inContentLanguage()->text() .
948  '</span>';
949  $warningDone = true;
950  }
951  $startPos += 2;
952  break;
953  }
954  // Recursively parse another rule
955  $inner .= $this->recursiveConvertRule( $text, $variant, $startPos, $depth + 1 );
956  break;
957  case '}-':
958  // Apply the rule
959  $startPos += 2;
960  $rule = new ConverterRule( $inner, $this );
961  $rule->parse( $variant );
962  $this->applyManualConv( $rule );
963  return $rule->getDisplay();
964  default:
965  throw new MWException( __METHOD__ . ': invalid regex match' );
966  }
967  }
968 
969  // Unclosed rule
970  if ( $startPos < $length ) {
971  $inner .= substr( $text, $startPos );
972  }
973  $startPos = $length;
974  return '-{' . $this->autoConvert( $inner, $variant );
975  }
976 
988  public function findVariantLink( &$link, &$nt, $ignoreOtherCond = false ) {
989  # If the article has already existed, there is no need to
990  # check it again, otherwise it may cause a fault.
991  if ( is_object( $nt ) && $nt->exists() ) {
992  return;
993  }
994 
995  global $wgRequest;
996  $isredir = $wgRequest->getText( 'redirect', 'yes' );
997  $action = $wgRequest->getText( 'action' );
998  if ( $action == 'edit' && $wgRequest->getBool( 'redlink' ) ) {
999  $action = 'view';
1000  }
1001  $linkconvert = $wgRequest->getText( 'linkconvert', 'yes' );
1002  $disableLinkConversion =
1003  MediaWikiServices::getInstance()->getLanguageConverterFactory()
1004  ->isLinkConversionDisabled();
1005  $linkBatchFactory = MediaWikiServices::getInstance()->getLinkBatchFactory();
1006  $linkBatch = $linkBatchFactory->newLinkBatch();
1007 
1008  $ns = NS_MAIN;
1009 
1010  if ( $disableLinkConversion ||
1011  ( !$ignoreOtherCond &&
1012  ( $isredir == 'no'
1013  || $action == 'edit'
1014  || $action == 'submit'
1015  || $linkconvert == 'no' ) ) ) {
1016  return;
1017  }
1018 
1019  if ( is_object( $nt ) ) {
1020  $ns = $nt->getNamespace();
1021  }
1022 
1023  $variants = $this->autoConvertToAllVariants( $link );
1024  if ( !$variants ) { // give up
1025  return;
1026  }
1027 
1028  $titles = [];
1029 
1030  foreach ( $variants as $v ) {
1031  if ( $v != $link ) {
1032  $varnt = Title::newFromText( $v, $ns );
1033  if ( $varnt !== null ) {
1034  $linkBatch->addObj( $varnt );
1035  $titles[] = $varnt;
1036  }
1037  }
1038  }
1039 
1040  // fetch all variants in single query
1041  $linkBatch->execute();
1042 
1043  foreach ( $titles as $varnt ) {
1044  if ( $varnt->getArticleID() > 0 ) {
1045  $nt = $varnt;
1046  $link = $varnt->getText();
1047  break;
1048  }
1049  }
1050  }
1051 
1057  public function getExtraHashOptions() {
1058  $variant = $this->getPreferredVariant();
1059 
1060  return '!' . $variant;
1061  }
1062 
1073  public function guessVariant( $text, $variant ) {
1074  return false;
1075  }
1076 
1083  protected function loadDefaultTables() {
1084  $class = static::class;
1085  throw new MWException( "Must implement loadDefaultTables() method in class $class" );
1086  }
1087 
1093  protected function loadTables( $fromCache = true ) {
1094  $languageConverterCacheType = MediaWikiServices::getInstance()
1095  ->getMainConfig()->get( 'LanguageConverterCacheType' );
1096 
1097  if ( $this->mTablesLoaded ) {
1098  return;
1099  }
1100 
1101  $this->mTablesLoaded = true;
1102  // Do not use null as starting value, as that would confuse phan a lot.
1103  $this->mTables = [];
1104  $cache = ObjectCache::getInstance( $languageConverterCacheType );
1105  $cacheKey = $cache->makeKey( 'conversiontables', $this->getMainCode() );
1106  if ( $fromCache ) {
1107  $this->mTables = $cache->get( $cacheKey );
1108  }
1109  if ( !$this->mTables || !array_key_exists( self::CACHE_VERSION_KEY, $this->mTables ) ) {
1110  // not in cache, or we need a fresh reload.
1111  // We will first load the default tables
1112  // then update them using things in MediaWiki:Conversiontable/*
1113  $this->loadDefaultTables();
1114  foreach ( $this->getVariants() as $var ) {
1115  $cached = $this->parseCachedTable( $var );
1116  // @phan-suppress-next-next-line PhanTypeArraySuspiciousNullable
1117  // FIXME: $this->mTables could theoretically be null here
1118  $this->mTables[$var]->mergeArray( $cached );
1119  }
1120 
1121  $this->postLoadTables();
1122  $this->mTables[self::CACHE_VERSION_KEY] = true;
1123 
1124  $cache->set( $cacheKey, $this->mTables, 43200 );
1125  }
1126  }
1127 
1131  protected function postLoadTables() {
1132  }
1133 
1141  private function reloadTables() {
1142  if ( $this->mTables ) {
1143  // @phan-suppress-next-line PhanTypeObjectUnsetDeclaredProperty
1144  unset( $this->mTables );
1145  }
1146 
1147  $this->mTablesLoaded = false;
1148  $this->loadTables( false );
1149  }
1150 
1170  private function parseCachedTable( $code, $subpage = '', $recursive = true ) {
1171  static $parsed = [];
1172 
1173  $key = 'Conversiontable/' . $code;
1174  if ( $subpage ) {
1175  $key .= '/' . $subpage;
1176  }
1177  if ( array_key_exists( $key, $parsed ) ) {
1178  return [];
1179  }
1180 
1181  $parsed[$key] = true;
1182 
1183  if ( $subpage === '' ) {
1184  $messageCache = MediaWikiServices::getInstance()->getMessageCache();
1185  $txt = $messageCache->getMsgFromNamespace( $key, $code );
1186  } else {
1187  $txt = false;
1189  if ( $title && $title->exists() ) {
1190  $revision = MediaWikiServices::getInstance()
1191  ->getRevisionLookup()
1192  ->getRevisionByTitle( $title );
1193  if ( $revision ) {
1194  $model = $revision->getSlot(
1195  SlotRecord::MAIN,
1196  RevisionRecord::RAW
1197  )->getModel();
1198  if ( $model == CONTENT_MODEL_WIKITEXT ) {
1199  // @phan-suppress-next-line PhanUndeclaredMethod
1200  $txt = $revision->getContent(
1201  SlotRecord::MAIN,
1202  RevisionRecord::RAW
1203  )->getText();
1204  }
1205 
1206  // @todo in the future, use a specialized content model, perhaps based on json!
1207  }
1208  }
1209  }
1210 
1211  # Nothing to parse if there's no text
1212  if ( $txt === false || $txt === null || $txt === '' ) {
1213  return [];
1214  }
1215 
1216  // get all subpage links of the form
1217  // [[MediaWiki:Conversiontable/zh-xx/...|...]]
1218  $linkhead = $this->mLangObj->getNsText( NS_MEDIAWIKI ) .
1219  ':Conversiontable';
1220  $subs = StringUtils::explode( '[[', $txt );
1221  $sublinks = [];
1222  foreach ( $subs as $sub ) {
1223  $link = explode( ']]', $sub, 2 );
1224  if ( count( $link ) != 2 ) {
1225  continue;
1226  }
1227  $b = explode( '|', $link[0], 2 );
1228  $b = explode( '/', trim( $b[0] ), 3 );
1229  if ( count( $b ) == 3 ) {
1230  $sublink = $b[2];
1231  } else {
1232  $sublink = '';
1233  }
1234 
1235  if ( $b[0] == $linkhead && $b[1] == $code ) {
1236  $sublinks[] = $sublink;
1237  }
1238  }
1239 
1240  // parse the mappings in this page
1241  $blocks = StringUtils::explode( '-{', $txt );
1242  $ret = [];
1243  $first = true;
1244  foreach ( $blocks as $block ) {
1245  if ( $first ) {
1246  // Skip the part before the first -{
1247  $first = false;
1248  continue;
1249  }
1250  $mappings = explode( '}-', $block, 2 )[0];
1251  $stripped = str_replace( [ "'", '"', '*', '#' ], '', $mappings );
1252  $table = StringUtils::explode( ';', $stripped );
1253  foreach ( $table as $t ) {
1254  $m = explode( '=>', $t, 3 );
1255  if ( count( $m ) != 2 ) {
1256  continue;
1257  }
1258  // trim any trailing comments starting with '//'
1259  $tt = explode( '//', $m[1], 2 );
1260  $ret[trim( $m[0] )] = trim( $tt[0] );
1261  }
1262  }
1263 
1264  // recursively parse the subpages
1265  if ( $recursive ) {
1266  foreach ( $sublinks as $link ) {
1267  $s = $this->parseCachedTable( $code, $link, $recursive );
1268  $ret = $s + $ret;
1269  }
1270  }
1271 
1272  if ( $this->mUcfirst ) {
1273  foreach ( $ret as $k => $v ) {
1274  $ret[$this->mLangObj->ucfirst( $k )] = $this->mLangObj->ucfirst( $v );
1275  }
1276  }
1277  return $ret;
1278  }
1279 
1288  public function markNoConversion( $text, $noParse = false ) {
1289  # don't mark if already marked
1290  if ( strpos( $text, '-{' ) || strpos( $text, '}-' ) ) {
1291  return $text;
1292  }
1293 
1294  $ret = "-{R|$text}-";
1295  return $ret;
1296  }
1297 
1306  public function convertCategoryKey( $key ) {
1307  return $key;
1308  }
1309 
1316  public function updateConversionTable( LinkTarget $linkTarget ) {
1317  if ( $linkTarget->getNamespace() === NS_MEDIAWIKI ) {
1318  $t = explode( '/', $linkTarget->getDBkey(), 3 );
1319  $c = count( $t );
1320  if ( $c > 1 && $t[0] == 'Conversiontable' ) {
1321  if ( $this->validateVariant( $t[1] ) ) {
1322  $this->reloadTables();
1323  }
1324  }
1325  }
1326  }
1327 
1332  public function getVarSeparatorPattern() {
1333  if ( $this->mVarSeparatorPattern === null ) {
1334  // varsep_pattern for preg_split:
1335  // text should be split by ";" only if a valid variant
1336  // name exist after the markup, for example:
1337  // -{zh-hans:<span style="font-size:120%;">xxx</span>;zh-hant:\
1338  // <span style="font-size:120%;">yyy</span>;}-
1339  // we should split it as:
1340  // [
1341  // [0] => 'zh-hans:<span style="font-size:120%;">xxx</span>'
1342  // [1] => 'zh-hant:<span style="font-size:120%;">yyy</span>'
1343  // [2] => ''
1344  // ]
1345  $expandedVariants = [];
1346  foreach ( $this->getVariants() as $variant ) {
1347  $expandedVariants[ $variant ] = 1;
1348  // Accept standard BCP 47 names for variants as well.
1349  $expandedVariants[ LanguageCode::bcp47( $variant ) ] = 1;
1350  }
1351  // Accept old deprecated names for variants
1352  foreach ( LanguageCode::getDeprecatedCodeMapping() as $old => $new ) {
1353  if ( isset( $expandedVariants[ $new ] ) ) {
1354  $expandedVariants[ $old ] = 1;
1355  }
1356  }
1357 
1358  $pat = '/;\s*(?=';
1359  foreach ( $expandedVariants as $variant => $ignore ) {
1360  // zh-hans:xxx;zh-hant:yyy
1361  $pat .= $variant . '\s*:|';
1362  // xxx=>zh-hans:yyy; xxx=>zh-hant:zzz
1363  $pat .= '[^;]*?=>\s*' . $variant . '\s*:|';
1364  }
1365  $pat .= '\s*$)/';
1366  $this->mVarSeparatorPattern = $pat;
1367  }
1369  }
1370 
1378  public function hasVariants() {
1379  return count( $this->getVariants() ) > 1;
1380  }
1381 
1392  public function hasVariant( $variant ) {
1393  return $variant && ( $variant === $this->validateVariant( $variant ) );
1394  }
1395 
1404  public function convertHtml( $text ) {
1405  // @phan-suppress-next-line SecurityCheck-DoubleEscaped convert() is documented to return html
1406  return htmlspecialchars( $this->convert( $text ) );
1407  }
1408 }
LanguageConverter\getVarSeparatorPattern
getVarSeparatorPattern()
Get the cached separator pattern for ConverterRule::parseRules()
Definition: LanguageConverter.php:1332
LanguageCode\replaceDeprecatedCodes
static replaceDeprecatedCodes( $code)
Replace deprecated language codes that were used in previous versions of MediaWiki to up-to-date,...
Definition: LanguageCode.php:161
LanguageConverter\hasVariant
hasVariant( $variant)
Strict check if the language has the specific variant.
Definition: LanguageConverter.php:1392
LanguageConverter\getLanguageVariants
getLanguageVariants()
Get supported variants of the language.
ConverterRule
The rules used for language conversion, this processes the rules extracted by Parser from the -{ }- w...
Definition: ConverterRule.php:28
LanguageCode\getDeprecatedCodeMapping
static getDeprecatedCodeMapping()
Returns a mapping of deprecated language codes that were used in previous versions of MediaWiki to up...
Definition: LanguageCode.php:124
LanguageConverter\getVariantNames
getVariantNames()
Get variant names.
Definition: LanguageConverter.php:245
Title\newFromText
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:377
LanguageConverter\$mMaxDepth
$mMaxDepth
Definition: LanguageConverter.php:77
MediaWiki\Revision\RevisionRecord
Page revision base class.
Definition: RevisionRecord.php:47
LanguageConverter\getConvRuleTitle
getConvRuleTitle()
Get the title produced by the conversion rule.
Definition: LanguageConverter.php:280
NS_MEDIAWIKI
const NS_MEDIAWIKI
Definition: Defines.php:72
LanguageConverter\getExtraHashOptions
getExtraHashOptions()
Returns language specific hash options.
Definition: LanguageConverter.php:1057
User\isRegistered
isRegistered()
Get whether the user is registered.
Definition: User.php:2530
LanguageConverter\CACHE_VERSION_KEY
const CACHE_VERSION_KEY
Definition: LanguageConverter.php:80
LanguageConverter\$mUcfirst
$mUcfirst
Definition: LanguageConverter.php:72
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:203
$wgRequest
$wgRequest
Definition: Setup.php:732
LanguageConverter\__construct
__construct( $langobj)
Definition: LanguageConverter.php:85
LanguageConverter\$mLangObj
Language $mLangObj
Definition: LanguageConverter.php:70
LanguageConverter\$mURLVariant
$mURLVariant
Definition: LanguageConverter.php:74
LanguageConverter\getManualLevel
getManualLevel()
Get manual level limit for supported variants.
Definition: LanguageConverter.php:194
LanguageConverter\updateConversionTable
updateConversionTable(LinkTarget $linkTarget)
Refresh the cache of conversion tables when MediaWiki:Conversiontable* is updated.
Definition: LanguageConverter.php:1316
LanguageConverter\loadDefaultTables
loadDefaultTables()
Load default conversion tables.
Definition: LanguageConverter.php:1083
LanguageConverter\$mConvRuleTitle
$mConvRuleTitle
Definition: LanguageConverter.php:73
LanguageConverter\$mVarSeparatorPattern
$mVarSeparatorPattern
Definition: LanguageConverter.php:78
Html\expandAttributes
static expandAttributes(array $attribs)
Given an associative array of element attributes, generate a string to stick after the element name i...
Definition: Html.php:479
LanguageConverter\getURLVariant
getURLVariant()
Get the variant specified in the URL.
Definition: LanguageConverter.php:382
LanguageConverter\findVariantLink
findVariantLink(&$link, &$nt, $ignoreOtherCond=false)
If a language supports multiple variants, it is possible that non-existing link in one variant actual...
Definition: LanguageConverter.php:988
wfMessage
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Definition: GlobalFunctions.php:1167
User\isSafeToLoad
isSafeToLoad()
Test if it's safe to load this User object.
Definition: User.php:347
LanguageConverter\getDescVarSeparator
getDescVarSeparator()
Get desc var separator.
Definition: LanguageConverter.php:236
LanguageConverter\$mTables
ReplacementArray[] bool[] $mTables
Definition: LanguageConverter.php:65
Parser\MARKER_PREFIX
const MARKER_PREFIX
Definition: Parser.php:149
Page\PageReference
Interface for objects (potentially) representing a page that can be viewable and linked to on a wiki.
Definition: PageReference.php:49
LanguageConverter\hasVariants
hasVariants()
Check if this is a language with variants.
Definition: LanguageConverter.php:1378
LanguageConverter\$mHeaderVariant
$mHeaderVariant
Definition: LanguageConverter.php:76
NS_MAIN
const NS_MAIN
Definition: Defines.php:64
LanguageConverter\$mUserVariant
$mUserVariant
Definition: LanguageConverter.php:75
LanguageConverter\getVariantFallbacks
getVariantFallbacks( $variant)
In case some variant is not defined in the markup, we need to have some fallback.
Definition: LanguageConverter.php:272
LanguageConverter\reloadTables
reloadTables()
Reload the conversion tables.
Definition: LanguageConverter.php:1141
MediaWiki\Linker\LinkTarget\getNamespace
getNamespace()
Get the namespace index.
LanguageConverter\validateVariant
validateVariant( $variant=null)
Validate the variant and return an appropriate strict internal variant code if one exists.
Definition: LanguageConverter.php:354
deprecatePublicPropertyFallback
deprecatePublicPropertyFallback(string $property, string $version, $getter, $setter=null, $class=null, $component=null)
Mark a removed public property as deprecated and provide fallback getter and setter callables.
Definition: DeprecationHelper.php:125
MWException
MediaWiki exception.
Definition: MWException.php:29
MediaWiki\Logger\LoggerFactory
PSR-3 logger instance factory.
Definition: LoggerFactory.php:45
LanguageConverter\getVariants
getVariants()
Get all valid variants for current Converter.
Definition: LanguageConverter.php:256
LanguageConverter\recursiveConvertTopLevel
recursiveConvertTopLevel( $text, $variant, $depth=0)
Recursively convert text on the outside.
Definition: LanguageConverter.php:855
LanguageConverter\getDescCodeSeparator
getDescCodeSeparator()
Get desc code separator.
Definition: LanguageConverter.php:225
StringUtils\explode
static explode( $separator, $subject)
Workalike for explode() with limited memory usage.
Definition: StringUtils.php:326
LanguageConverter\translate
translate( $text, $variant)
Translate a string to a variant.
Definition: LanguageConverter.php:660
ObjectCache\getInstance
static getInstance( $id)
Get a cached instance of the specified type of cache object.
Definition: ObjectCache.php:74
LanguageConverter\getDefaultVariant
getDefaultVariant()
This function would not be affected by user's settings.
Definition: LanguageConverter.php:326
$title
$title
Definition: testCompression.php:38
LanguageConverter\recursiveConvertRule
recursiveConvertRule( $text, $variant, &$startPos, $depth=0)
Recursively convert text on the inside.
Definition: LanguageConverter.php:910
deprecatePublicProperty
deprecatePublicProperty( $property, $version, $class=null, $component=null)
Mark a property as deprecated.
Definition: DeprecationHelper.php:94
LanguageConverter\getVariantsFallbacks
getVariantsFallbacks()
Get language variants fallbacks.
LanguageConverter\postLoadTables
postLoadTables()
Hook for post processing after conversion tables are loaded.
Definition: LanguageConverter.php:1131
Title\makeTitleSafe
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:674
LanguageConverter\$mTablesLoaded
$mTablesLoaded
Definition: LanguageConverter.php:60
$s
foreach( $mmfl['setupFiles'] as $fileName) if( $queue) if(empty( $mmfl['quiet'])) $s
Definition: mergeMessageFileList.php:206
ReplacementArray
Wrapper around strtr() that holds replacements.
Definition: ReplacementArray.php:24
ILanguageConverter
The shared interface for all language converters.
Definition: ILanguageConverter.php:29
ConverterRule\getConvTable
getConvTable()
Get conversion table.
Definition: ConverterRule.php:492
LanguageConverter\getFlags
getFlags()
Get strings that maps to the flags.
Definition: LanguageConverter.php:157
LanguageConverter\convertCategoryKey
convertCategoryKey( $key)
Convert the sorting key for category links.
Definition: LanguageConverter.php:1306
LanguageConverter\parseCachedTable
parseCachedTable( $code, $subpage='', $recursive=true)
Parse the conversion table stored in the cache.
Definition: LanguageConverter.php:1170
Hooks\runner
static runner()
Get a HookRunner instance for calling hooks using the new interfaces.
Definition: Hooks.php:173
Language\getFormattedNsText
getFormattedNsText( $index)
A convenience function that returns the same thing as getNsText() except with '_' changed to ' ',...
Definition: Language.php:599
MediaWiki\Linker\LinkTarget\getDBkey
getDBkey()
Get the main part with underscores.
LanguageConverter\getPreferredVariant
getPreferredVariant()
Get preferred language variant.
Definition: LanguageConverter.php:288
LanguageConverter\markNoConversion
markNoConversion( $text, $noParse=false)
Enclose a string with the "no conversion" tag.
Definition: LanguageConverter.php:1288
LanguageConverter\autoConvert
autoConvert( $text, $toVariant=false)
Dictionary-based conversion.
Definition: LanguageConverter.php:500
CONTENT_MODEL_WIKITEXT
const CONTENT_MODEL_WIKITEXT
Definition: Defines.php:208
RequestContext\getMain
static getMain()
Get the RequestContext object associated with the main request.
Definition: RequestContext.php:484
LanguageConverter\convertTitle
convertTitle( $title)
Auto convert a LinkTarget or PageReference to a readable string in the preferred variant.
Definition: LanguageConverter.php:730
LanguageConverter\convert
convert( $text)
Convert text to different variants of a language.
Definition: LanguageConverter.php:822
LanguageConverter\loadTables
loadTables( $fromCache=true)
Load conversion tables either from the cache or the disk.
Definition: LanguageConverter.php:1093
LanguageConverter\getUserVariant
getUserVariant(User $user)
Determine if the user has a variant set.
Definition: LanguageConverter.php:406
LanguageConverter\computeNsVariantText
computeNsVariantText(int $index, ?string $variant)
Definition: LanguageConverter.php:776
LanguageConverter\$languagesWithVariants
static array $languagesWithVariants
languages supporting variants
Definition: LanguageConverter.php:45
ConverterRule\getRulesAction
getRulesAction()
Return how deal with conversion rules.
Definition: ConverterRule.php:483
$cache
$cache
Definition: mcc.php:33
LanguageConverter\getAdditionalManualLevel
getAdditionalManualLevel()
Provides additional flags for converter.
Definition: LanguageConverter.php:214
LanguageCode\bcp47
static bcp47( $code)
Get the normalised IETF language tag See unit test for examples.
Definition: LanguageCode.php:175
LanguageConverter\getMainCode
getMainCode()
Get main language code.
LanguageConverter\applyManualConv
applyManualConv(ConverterRule $convRule)
Apply manual conversion rules.
Definition: LanguageConverter.php:692
LanguageConverter\autoConvertToAllVariants
autoConvertToAllVariants( $text)
Call translate() to convert text to all valid variants.
Definition: LanguageConverter.php:676
Sanitizer\decodeTagAttributes
static decodeTagAttributes( $text)
Return an associative array of attribute names and values from a partial tag string.
Definition: Sanitizer.php:1004
LanguageConverter\convertTo
convertTo( $text, $variant)
Same as convert() except a extra parameter to custom variant.
Definition: LanguageConverter.php:836
$t
$t
Definition: testCompression.php:74
LanguageConverter\getHeaderVariant
getHeaderVariant()
Determine the language variant from the Accept-Language header.
Definition: LanguageConverter.php:443
MediaWiki\Linker\LinkTarget
Definition: LinkTarget.php:26
DeprecationHelper
trait DeprecationHelper
Use this trait in classes which have properties for which public access is deprecated or implementati...
Definition: DeprecationHelper.php:60
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:67
LanguageConverter\guessVariant
guessVariant( $text, $variant)
Guess if a text is written in a variant.
Definition: LanguageConverter.php:1073
LanguageConverter
Base class for multi-variant language conversion.
Definition: LanguageConverter.php:37
ConverterRule\getTitle
getTitle()
Get converted title.
Definition: ConverterRule.php:475
LanguageConverter\convertHtml
convertHtml( $text)
Perform output conversion on a string, and encode for safe HTML output.
Definition: LanguageConverter.php:1404
LanguageConverter\convertNamespace
convertNamespace( $index, $variant=null)
Get the namespace display name in the preferred variant.
Definition: LanguageConverter.php:751
LanguageConverter\getAdditionalFlags
getAdditionalFlags()
Provides additional flags for converter.
Definition: LanguageConverter.php:184
Language
Internationalisation code See https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation for more...
Definition: Language.php:42
MediaWiki\Revision\SlotRecord
Value object representing a content slot associated with a page revision.
Definition: SlotRecord.php:40