MediaWiki  master
LanguageConverter.php
Go to the documentation of this file.
1 <?php
30 
36 abstract class LanguageConverter implements ILanguageConverter {
38 
44  public static $languagesWithVariants = [
45  'ban',
46  'en',
47  'crh',
48  'gan',
49  'iu',
50  'kk',
51  'ku',
52  'shi',
53  'sr',
54  'tg',
55  'uz',
56  'zh',
57  ];
58 
59  private $mTablesLoaded = false;
60 
64  protected $mTables;
65 
69  private $mLangObj;
70 
71  private $mUcfirst = false;
72  private $mConvRuleTitle = false;
73  private $mURLVariant;
74  private $mUserVariant;
75  private $mHeaderVariant;
76  private $mMaxDepth = 10;
78 
79  private const CACHE_VERSION_KEY = 'VERSION 7';
80 
84  public function __construct( $langobj ) {
85  $this->deprecatePublicProperty( 'mUcfirst', '1.35', __CLASS__ );
86  $this->deprecatePublicProperty( 'mConvRuleTitle', '1.35', __CLASS__ );
87  $this->deprecatePublicProperty( 'mUserVariant', '1.35', __CLASS__ );
88  $this->deprecatePublicProperty( 'mHeaderVariant', '1.35', __CLASS__ );
89  $this->deprecatePublicProperty( 'mMaxDepth = 10', '1.35', __CLASS__ );
90  $this->deprecatePublicProperty( 'mVarSeparatorPattern', '1.35', __CLASS__ );
91  $this->deprecatePublicProperty( 'mLangObj', '1.35', __CLASS__ );
92  $this->deprecatePublicProperty( 'mVariantFallbacks', '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  global $wgDisabledVariants;
258  return array_diff( $this->getLanguageVariants(), $wgDisabledVariants );
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() {
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 ( $wgDefaultLanguageVariant && !$req ) {
307  $req = $this->validateVariant( $wgDefaultLanguageVariant );
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() {
328 
329  $req = $this->getURLVariant();
330 
331  if ( !$req ) {
332  $req = $this->getHeaderVariant();
333  }
334 
335  if ( $wgDefaultLanguageVariant && !$req ) {
336  $req = $this->validateVariant( $wgDefaultLanguageVariant );
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 ( $user->isRegistered() ) {
414  // Get language variant preference from logged in users
415  if (
416  $this->getMainCode() ==
417  MediaWikiServices::getInstance()->getContentLanguage()->getCode()
418  ) {
419  $ret = $user->getOption( 'variant' );
420  } else {
421  $ret = $user->getOption( 'variant-' . $this->getMainCode() );
422  }
423  } else {
424  // figure out user lang without constructing wgLang to avoid
425  // infinite recursion
426  $ret = $user->getOption( 'language' );
427  }
428 
429  $this->mUserVariant = $this->validateVariant( $ret );
430  return $this->mUserVariant;
431  }
432 
438  protected function getHeaderVariant() {
439  global $wgRequest;
440 
441  if ( $this->mHeaderVariant ) {
442  return $this->mHeaderVariant;
443  }
444 
445  // See if some supported language variant is set in the
446  // HTTP header.
447  $languages = array_keys( $wgRequest->getAcceptLang() );
448  if ( empty( $languages ) ) {
449  return null;
450  }
451 
452  $fallbackLanguages = [];
453  foreach ( $languages as $language ) {
454  $this->mHeaderVariant = $this->validateVariant( $language );
455  if ( $this->mHeaderVariant ) {
456  break;
457  }
458 
459  // To see if there are fallbacks of current language.
460  // We record these fallback variants, and process
461  // them later.
462  $fallbacks = $this->getVariantFallbacks( $language );
463  if ( is_string( $fallbacks ) && $fallbacks !== $this->getMainCode() ) {
464  $fallbackLanguages[] = $fallbacks;
465  } elseif ( is_array( $fallbacks ) ) {
466  $fallbackLanguages =
467  array_merge( $fallbackLanguages, $fallbacks );
468  }
469  }
470 
471  if ( !$this->mHeaderVariant ) {
472  // process fallback languages now
473  $fallback_languages = array_unique( $fallbackLanguages );
474  foreach ( $fallback_languages as $language ) {
475  $this->mHeaderVariant = $this->validateVariant( $language );
476  if ( $this->mHeaderVariant ) {
477  break;
478  }
479  }
480  }
481 
482  return $this->mHeaderVariant;
483  }
484 
495  public function autoConvert( $text, $toVariant = false ) {
496  $this->loadTables();
497 
498  if ( !$toVariant ) {
499  $toVariant = $this->getPreferredVariant();
500  if ( !$toVariant ) {
501  return $text;
502  }
503  }
504 
505  if ( $this->guessVariant( $text, $toVariant ) ) {
506  return $text;
507  }
508  /* we convert everything except:
509  1. HTML markups (anything between < and >)
510  2. HTML entities
511  3. placeholders created by the parser
512  IMPORTANT: Beware of failure from pcre.backtrack_limit (T124404).
513  Minimize use of backtracking where possible.
514  */
515  static $reg;
516  if ( $reg === null ) {
517  $marker = '|' . Parser::MARKER_PREFIX . '[^\x7f]++\x7f';
518 
519  // this one is needed when the text is inside an HTML markup
520  $htmlfix = '|<[^>\004]++(?=\004$)|^[^<>]*+>';
521 
522  // Optimize for the common case where these tags have
523  // few or no children. Thus try and possesively get as much as
524  // possible, and only engage in backtracking when we hit a '<'.
525 
526  // disable convert to variants between <code> tags
527  $codefix = '<code>[^<]*+(?:(?:(?!<\/code>).)[^<]*+)*+<\/code>|';
528  // disable conversion of <script> tags
529  $scriptfix = '<script[^>]*+>[^<]*+(?:(?:(?!<\/script>).)[^<]*+)*+<\/script>|';
530  // disable conversion of <pre> tags
531  $prefix = '<pre[^>]*+>[^<]*+(?:(?:(?!<\/pre>).)[^<]*+)*+<\/pre>|';
532  // The "|.*+)" at the end, is in case we missed some part of html syntax,
533  // we will fail securely (hopefully) by matching the rest of the string.
534  $htmlFullTag = '<(?:[^>=]*+(?>[^>=]*+=\s*+(?:"[^"]*"|\'[^\']*\'|[^\'">\s]*+))*+[^>=]*+>|.*+)|';
535 
536  $reg = '/' . $codefix . $scriptfix . $prefix . $htmlFullTag .
537  '&[a-zA-Z#][a-z0-9]++;' . $marker . $htmlfix . '|\004$/s';
538  }
539  $startPos = 0;
540  $sourceBlob = '';
541  $literalBlob = '';
542 
543  // Guard against delimiter nulls in the input
544  // (should never happen: see T159174)
545  $text = str_replace( "\000", '', $text );
546  $text = str_replace( "\004", '', $text );
547 
548  $markupMatches = null;
549  $elementMatches = null;
550 
551  // We add a marker (\004) at the end of text, to ensure we always match the
552  // entire text (Otherwise, pcre.backtrack_limit might cause silent failure)
553  $textWithMarker = $text . "\004";
554  while ( $startPos < strlen( $text ) ) {
555  if ( preg_match( $reg, $textWithMarker, $markupMatches, PREG_OFFSET_CAPTURE, $startPos ) ) {
556  $elementPos = $markupMatches[0][1];
557  $element = $markupMatches[0][0];
558  if ( $element === "\004" ) {
559  // We hit the end.
560  $elementPos = strlen( $text );
561  $element = '';
562  } elseif ( substr( $element, -1 ) === "\004" ) {
563  // This can sometimes happen if we have
564  // unclosed html tags (For example
565  // when converting a title attribute
566  // during a recursive call that contains
567  // a &lt; e.g. <div title="&lt;">.
568  $element = substr( $element, 0, -1 );
569  }
570  } else {
571  // If we hit here, then Language Converter could be tricked
572  // into doing an XSS, so we refuse to translate.
573  // If non-crazy input manages to reach this code path,
574  // we should consider it a bug.
575  $log = LoggerFactory::getInstance( 'languageconverter' );
576  $log->error( "Hit pcre.backtrack_limit in " . __METHOD__
577  . ". Disabling language conversion for this page.",
578  [
579  "method" => __METHOD__,
580  "variant" => $toVariant,
581  "startOfText" => substr( $text, 0, 500 )
582  ]
583  );
584  return $text;
585  }
586  // Queue the part before the markup for translation in a batch
587  $sourceBlob .= substr( $text, $startPos, $elementPos - $startPos ) . "\000";
588 
589  // Advance to the next position
590  $startPos = $elementPos + strlen( $element );
591 
592  // Translate any alt or title attributes inside the matched element
593  if ( $element !== ''
594  && preg_match( '/^(<[^>\s]*+)\s([^>]*+)(.*+)$/', $element, $elementMatches )
595  ) {
596  // FIXME, this decodes entities, so if you have something
597  // like <div title="foo&lt;bar"> the bar won't get
598  // translated since after entity decoding it looks like
599  // unclosed html and we call this method recursively
600  // on attributes.
601  $attrs = Sanitizer::decodeTagAttributes( $elementMatches[2] );
602  // Ensure self-closing tags stay self-closing.
603  $close = substr( $elementMatches[2], -1 ) === '/' ? ' /' : '';
604  $changed = false;
605  foreach ( [ 'title', 'alt' ] as $attrName ) {
606  if ( !isset( $attrs[$attrName] ) ) {
607  continue;
608  }
609  $attr = $attrs[$attrName];
610  // Don't convert URLs
611  if ( !strpos( $attr, '://' ) ) {
612  $attr = $this->recursiveConvertTopLevel( $attr, $toVariant );
613  }
614 
615  if ( $attr !== $attrs[$attrName] ) {
616  $attrs[$attrName] = $attr;
617  $changed = true;
618  }
619  }
620  if ( $changed ) {
621  // @phan-suppress-next-line SecurityCheck-DoubleEscaped Explained above with decodeTagAttributes
622  $element = $elementMatches[1] . Html::expandAttributes( $attrs ) .
623  $close . $elementMatches[3];
624  }
625  }
626  $literalBlob .= $element . "\000";
627  }
628 
629  // Do the main translation batch
630  $translatedBlob = $this->translate( $sourceBlob, $toVariant );
631 
632  // Put the output back together
633  $translatedIter = StringUtils::explode( "\000", $translatedBlob );
634  $literalIter = StringUtils::explode( "\000", $literalBlob );
635  $output = '';
636  while ( $translatedIter->valid() && $literalIter->valid() ) {
637  $output .= $translatedIter->current();
638  $output .= $literalIter->current();
639  $translatedIter->next();
640  $literalIter->next();
641  }
642 
643  return $output;
644  }
645 
655  public function translate( $text, $variant ) {
656  // If $text is empty or only includes spaces, do nothing
657  // Otherwise translate it
658  if ( trim( $text ) ) {
659  $this->loadTables();
660  $text = $this->mTables[$variant]->replace( $text );
661  }
662  return $text;
663  }
664 
671  public function autoConvertToAllVariants( $text ) {
672  $this->loadTables();
673 
674  $ret = [];
675  foreach ( $this->getVariants() as $variant ) {
676  $ret[$variant] = $this->translate( $text, $variant );
677  }
678 
679  return $ret;
680  }
681 
687  protected function applyManualConv( ConverterRule $convRule ) {
688  // Use syntax -{T|zh-cn:TitleCN; zh-tw:TitleTw}- to custom
689  // title conversion.
690  // T26072: $mConvRuleTitle was overwritten by other manual
691  // rule(s) not for title, this breaks the title conversion.
692  $newConvRuleTitle = $convRule->getTitle();
693  if ( $newConvRuleTitle ) {
694  // So I add an empty check for getTitle()
695  $this->mConvRuleTitle = $newConvRuleTitle;
696  }
697 
698  // merge/remove manual conversion rules to/from global table
699  $convTable = $convRule->getConvTable();
700  $action = $convRule->getRulesAction();
701  foreach ( $convTable as $variant => $pair ) {
702  $v = $this->validateVariant( $variant );
703  if ( !$v ) {
704  continue;
705  }
706 
707  if ( $action == 'add' ) {
708  // More efficient than array_merge(), about 2.5 times.
709  foreach ( $pair as $from => $to ) {
710  $this->mTables[$v]->setPair( $from, $to );
711  }
712  } elseif ( $action == 'remove' ) {
713  $this->mTables[$v]->removeArray( $pair );
714  }
715  }
716  }
717 
725  public function convertTitle( $title ) {
726  $variant = $this->getPreferredVariant();
727  $index = $title->getNamespace();
728  if ( $index !== NS_MAIN ) {
729  $text = $this->convertNamespace( $index, $variant ) . ':';
730  } else {
731  $text = '';
732  }
733  $name = str_replace( '_', ' ', $title->getDBKey() );
734  $text .= $this->translate( $name, $variant );
735 
736  return $text;
737  }
738 
746  public function convertNamespace( $index, $variant = null ) {
747  if ( $index === NS_MAIN ) {
748  return '';
749  }
750 
751  if ( $variant === null ) {
752  $variant = $this->getPreferredVariant();
753  }
754 
755  $cache = MediaWikiServices::getInstance()->getLocalServerObjectCache();
756  $key = $cache->makeKey( 'languageconverter', 'namespace-text', $index, $variant );
757  $nsVariantText = $cache->get( $key );
758  if ( $nsVariantText !== false ) {
759  return $nsVariantText;
760  }
761 
762  // First check if a message gives a converted name in the target variant.
763  $nsConvMsg = wfMessage( 'conversion-ns' . $index )->inLanguage( $variant );
764  if ( $nsConvMsg->exists() ) {
765  $nsVariantText = $nsConvMsg->plain();
766  }
767 
768  // Then check if a message gives a converted name in content language
769  // which needs extra translation to the target variant.
770  if ( $nsVariantText === false ) {
771  $nsConvMsg = wfMessage( 'conversion-ns' . $index )->inContentLanguage();
772  if ( $nsConvMsg->exists() ) {
773  $nsVariantText = $this->translate( $nsConvMsg->plain(), $variant );
774  }
775  }
776 
777  if ( $nsVariantText === false ) {
778  // No message exists, retrieve it from the target variant's namespace names.
779  $mLangObj = MediaWikiServices::getInstance()
780  ->getLanguageFactory()
781  ->getLanguage( $variant );
782  $nsVariantText = $mLangObj->getFormattedNsText( $index );
783  }
784 
785  $cache->set( $key, $nsVariantText, 60 );
786 
787  return $nsVariantText;
788  }
789 
808  public function convert( $text ) {
809  $variant = $this->getPreferredVariant();
810  return $this->convertTo( $text, $variant );
811  }
812 
822  public function convertTo( $text, $variant ) {
823  $languageConverterFactory = MediaWikiServices::getInstance()->getLanguageConverterFactory();
824  if ( $languageConverterFactory->isConversionDisabled() ) {
825  return $text;
826  }
827  // Reset converter state for a new converter run.
828  $this->mConvRuleTitle = false;
829  return $this->recursiveConvertTopLevel( $text, $variant );
830  }
831 
841  protected function recursiveConvertTopLevel( $text, $variant, $depth = 0 ) {
842  $startPos = 0;
843  $out = '';
844  $length = strlen( $text );
845  $shouldConvert = !$this->guessVariant( $text, $variant );
846  $continue = true;
847 
848  $noScript = '<script.*?>.*?<\/script>(*SKIP)(*FAIL)';
849  $noStyle = '<style.*?>.*?<\/style>(*SKIP)(*FAIL)';
850  // phpcs:ignore Generic.Files.LineLength
851  $noHtml = '<(?:[^>=]*+(?>[^>=]*+=\s*+(?:"[^"]*"|\'[^\']*\'|[^\'">\s]*+))*+[^>=]*+>|.*+)(*SKIP)(*FAIL)';
852  while ( $startPos < $length && $continue ) {
853  $continue = preg_match(
854  // Only match -{ outside of html.
855  "/$noScript|$noStyle|$noHtml|-\{/",
856  $text,
857  $m,
858  PREG_OFFSET_CAPTURE,
859  $startPos
860  );
861 
862  if ( !$continue ) {
863  // No more markup, append final segment
864  $fragment = substr( $text, $startPos );
865  $out .= $shouldConvert ? $this->autoConvert( $fragment, $variant ) : $fragment;
866  return $out;
867  }
868 
869  // Offset of the match of the regex pattern.
870  $pos = $m[0][1];
871 
872  // Append initial segment
873  $fragment = substr( $text, $startPos, $pos - $startPos );
874  $out .= $shouldConvert ? $this->autoConvert( $fragment, $variant ) : $fragment;
875  // -{ marker found, not in attribute
876  // Advance position up to -{ marker.
877  $startPos = $pos;
878  // Do recursive conversion
879  // Note: This passes $startPos by reference, and advances it.
880  $out .= $this->recursiveConvertRule( $text, $variant, $startPos, $depth + 1 );
881  }
882  return $out;
883  }
884 
896  protected function recursiveConvertRule( $text, $variant, &$startPos, $depth = 0 ) {
897  // Quick sanity check (no function calls)
898  if ( $text[$startPos] !== '-' || $text[$startPos + 1] !== '{' ) {
899  throw new MWException( __METHOD__ . ': invalid input string' );
900  }
901 
902  $startPos += 2;
903  $inner = '';
904  $warningDone = false;
905  $length = strlen( $text );
906 
907  while ( $startPos < $length ) {
908  $m = false;
909  preg_match( '/-\{|\}-/', $text, $m, PREG_OFFSET_CAPTURE, $startPos );
910  if ( !$m ) {
911  // Unclosed rule
912  break;
913  }
914 
915  $token = $m[0][0];
916  $pos = $m[0][1];
917 
918  // Markup found
919  // Append initial segment
920  $inner .= substr( $text, $startPos, $pos - $startPos );
921 
922  // Advance position
923  $startPos = $pos;
924 
925  switch ( $token ) {
926  case '-{':
927  // Check max depth
928  if ( $depth >= $this->mMaxDepth ) {
929  $inner .= '-{';
930  if ( !$warningDone ) {
931  $inner .= '<span class="error">' .
932  wfMessage( 'language-converter-depth-warning' )
933  ->numParams( $this->mMaxDepth )->inContentLanguage()->text() .
934  '</span>';
935  $warningDone = true;
936  }
937  $startPos += 2;
938  break;
939  }
940  // Recursively parse another rule
941  $inner .= $this->recursiveConvertRule( $text, $variant, $startPos, $depth + 1 );
942  break;
943  case '}-':
944  // Apply the rule
945  $startPos += 2;
946  $rule = new ConverterRule( $inner, $this );
947  $rule->parse( $variant );
948  $this->applyManualConv( $rule );
949  return $rule->getDisplay();
950  default:
951  throw new MWException( __METHOD__ . ': invalid regex match' );
952  }
953  }
954 
955  // Unclosed rule
956  if ( $startPos < $length ) {
957  $inner .= substr( $text, $startPos );
958  }
959  $startPos = $length;
960  return '-{' . $this->autoConvert( $inner, $variant );
961  }
962 
974  public function findVariantLink( &$link, &$nt, $ignoreOtherCond = false ) {
975  # If the article has already existed, there is no need to
976  # check it again, otherwise it may cause a fault.
977  if ( is_object( $nt ) && $nt->exists() ) {
978  return;
979  }
980 
981  global $wgRequest;
982  $isredir = $wgRequest->getText( 'redirect', 'yes' );
983  $action = $wgRequest->getText( 'action' );
984  if ( $action == 'edit' && $wgRequest->getBool( 'redlink' ) ) {
985  $action = 'view';
986  }
987  $linkconvert = $wgRequest->getText( 'linkconvert', 'yes' );
988  $disableLinkConversion =
989  MediaWikiServices::getInstance()->getLanguageConverterFactory()
990  ->isLinkConversionDisabled();
991  $linkBatchFactory = MediaWikiServices::getInstance()->getLinkBatchFactory();
992  $linkBatch = $linkBatchFactory->newLinkBatch();
993 
994  $ns = NS_MAIN;
995 
996  if ( $disableLinkConversion ||
997  ( !$ignoreOtherCond &&
998  ( $isredir == 'no'
999  || $action == 'edit'
1000  || $action == 'submit'
1001  || $linkconvert == 'no' ) ) ) {
1002  return;
1003  }
1004 
1005  if ( is_object( $nt ) ) {
1006  $ns = $nt->getNamespace();
1007  }
1008 
1009  $variants = $this->autoConvertToAllVariants( $link );
1010  if ( !$variants ) { // give up
1011  return;
1012  }
1013 
1014  $titles = [];
1015 
1016  foreach ( $variants as $v ) {
1017  if ( $v != $link ) {
1018  $varnt = Title::newFromText( $v, $ns );
1019  if ( $varnt !== null ) {
1020  $linkBatch->addObj( $varnt );
1021  $titles[] = $varnt;
1022  }
1023  }
1024  }
1025 
1026  // fetch all variants in single query
1027  $linkBatch->execute();
1028 
1029  foreach ( $titles as $varnt ) {
1030  if ( $varnt->getArticleID() > 0 ) {
1031  $nt = $varnt;
1032  $link = $varnt->getText();
1033  break;
1034  }
1035  }
1036  }
1037 
1043  public function getExtraHashOptions() {
1044  $variant = $this->getPreferredVariant();
1045 
1046  return '!' . $variant;
1047  }
1048 
1059  public function guessVariant( $text, $variant ) {
1060  return false;
1061  }
1062 
1069  protected function loadDefaultTables() {
1070  $class = static::class;
1071  throw new MWException( "Must implement loadDefaultTables() method in class $class" );
1072  }
1073 
1079  protected function loadTables( $fromCache = true ) {
1081 
1082  if ( $this->mTablesLoaded ) {
1083  return;
1084  }
1085 
1086  $this->mTablesLoaded = true;
1087  // Do not use null as starting value, as that would confuse phan a lot.
1088  $this->mTables = [];
1090  $cacheKey = $cache->makeKey( 'conversiontables', $this->getMainCode() );
1091  if ( $fromCache ) {
1092  $this->mTables = $cache->get( $cacheKey );
1093  }
1094  if ( !$this->mTables || !array_key_exists( self::CACHE_VERSION_KEY, $this->mTables ) ) {
1095  // not in cache, or we need a fresh reload.
1096  // We will first load the default tables
1097  // then update them using things in MediaWiki:Conversiontable/*
1098  $this->loadDefaultTables();
1099  foreach ( $this->getVariants() as $var ) {
1100  $cached = $this->parseCachedTable( $var );
1101  // @phan-suppress-next-next-line PhanTypeArraySuspiciousNullable
1102  // FIXME: $this->mTables could theoretically be null here
1103  $this->mTables[$var]->mergeArray( $cached );
1104  }
1105 
1106  $this->postLoadTables();
1107  $this->mTables[self::CACHE_VERSION_KEY] = true;
1108 
1109  $cache->set( $cacheKey, $this->mTables, 43200 );
1110  }
1111  }
1112 
1116  protected function postLoadTables() {
1117  }
1118 
1126  private function reloadTables() {
1127  if ( $this->mTables ) {
1128  // @phan-suppress-next-line PhanTypeObjectUnsetDeclaredProperty
1129  unset( $this->mTables );
1130  }
1131 
1132  $this->mTablesLoaded = false;
1133  $this->loadTables( false );
1134  }
1135 
1155  private function parseCachedTable( $code, $subpage = '', $recursive = true ) {
1156  static $parsed = [];
1157 
1158  $key = 'Conversiontable/' . $code;
1159  if ( $subpage ) {
1160  $key .= '/' . $subpage;
1161  }
1162  if ( array_key_exists( $key, $parsed ) ) {
1163  return [];
1164  }
1165 
1166  $parsed[$key] = true;
1167 
1168  if ( $subpage === '' ) {
1169  $messageCache = MediaWikiServices::getInstance()->getMessageCache();
1170  $txt = $messageCache->getMsgFromNamespace( $key, $code );
1171  } else {
1172  $txt = false;
1174  if ( $title && $title->exists() ) {
1175  $revision = MediaWikiServices::getInstance()
1176  ->getRevisionLookup()
1177  ->getRevisionByTitle( $title );
1178  if ( $revision ) {
1179  $model = $revision->getSlot(
1180  SlotRecord::MAIN,
1181  RevisionRecord::RAW
1182  )->getModel();
1183  if ( $model == CONTENT_MODEL_WIKITEXT ) {
1184  // @phan-suppress-next-line PhanUndeclaredMethod
1185  $txt = $revision->getContent(
1186  SlotRecord::MAIN,
1187  RevisionRecord::RAW
1188  )->getText();
1189  }
1190 
1191  // @todo in the future, use a specialized content model, perhaps based on json!
1192  }
1193  }
1194  }
1195 
1196  # Nothing to parse if there's no text
1197  if ( $txt === false || $txt === null || $txt === '' ) {
1198  return [];
1199  }
1200 
1201  // get all subpage links of the form
1202  // [[MediaWiki:Conversiontable/zh-xx/...|...]]
1203  $linkhead = $this->mLangObj->getNsText( NS_MEDIAWIKI ) .
1204  ':Conversiontable';
1205  $subs = StringUtils::explode( '[[', $txt );
1206  $sublinks = [];
1207  foreach ( $subs as $sub ) {
1208  $link = explode( ']]', $sub, 2 );
1209  if ( count( $link ) != 2 ) {
1210  continue;
1211  }
1212  $b = explode( '|', $link[0], 2 );
1213  $b = explode( '/', trim( $b[0] ), 3 );
1214  if ( count( $b ) == 3 ) {
1215  $sublink = $b[2];
1216  } else {
1217  $sublink = '';
1218  }
1219 
1220  if ( $b[0] == $linkhead && $b[1] == $code ) {
1221  $sublinks[] = $sublink;
1222  }
1223  }
1224 
1225  // parse the mappings in this page
1226  $blocks = StringUtils::explode( '-{', $txt );
1227  $ret = [];
1228  $first = true;
1229  foreach ( $blocks as $block ) {
1230  if ( $first ) {
1231  // Skip the part before the first -{
1232  $first = false;
1233  continue;
1234  }
1235  $mappings = explode( '}-', $block, 2 )[0];
1236  $stripped = str_replace( [ "'", '"', '*', '#' ], '', $mappings );
1237  $table = StringUtils::explode( ';', $stripped );
1238  foreach ( $table as $t ) {
1239  $m = explode( '=>', $t, 3 );
1240  if ( count( $m ) != 2 ) {
1241  continue;
1242  }
1243  // trim any trailling comments starting with '//'
1244  $tt = explode( '//', $m[1], 2 );
1245  $ret[trim( $m[0] )] = trim( $tt[0] );
1246  }
1247  }
1248 
1249  // recursively parse the subpages
1250  if ( $recursive ) {
1251  foreach ( $sublinks as $link ) {
1252  $s = $this->parseCachedTable( $code, $link, $recursive );
1253  $ret = $s + $ret;
1254  }
1255  }
1256 
1257  if ( $this->mUcfirst ) {
1258  foreach ( $ret as $k => $v ) {
1259  $ret[$this->mLangObj->ucfirst( $k )] = $this->mLangObj->ucfirst( $v );
1260  }
1261  }
1262  return $ret;
1263  }
1264 
1273  public function markNoConversion( $text, $noParse = false ) {
1274  # don't mark if already marked
1275  if ( strpos( $text, '-{' ) || strpos( $text, '}-' ) ) {
1276  return $text;
1277  }
1278 
1279  $ret = "-{R|$text}-";
1280  return $ret;
1281  }
1282 
1291  public function convertCategoryKey( $key ) {
1292  return $key;
1293  }
1294 
1301  public function updateConversionTable( LinkTarget $linkTarget ) {
1302  if ( $linkTarget->getNamespace() === NS_MEDIAWIKI ) {
1303  $t = explode( '/', $linkTarget->getDBkey(), 3 );
1304  $c = count( $t );
1305  if ( $c > 1 && $t[0] == 'Conversiontable' ) {
1306  if ( $this->validateVariant( $t[1] ) ) {
1307  $this->reloadTables();
1308  }
1309  }
1310  }
1311  }
1312 
1317  public function getVarSeparatorPattern() {
1318  if ( $this->mVarSeparatorPattern === null ) {
1319  // varsep_pattern for preg_split:
1320  // text should be splited by ";" only if a valid variant
1321  // name exist after the markup, for example:
1322  // -{zh-hans:<span style="font-size:120%;">xxx</span>;zh-hant:\
1323  // <span style="font-size:120%;">yyy</span>;}-
1324  // we should split it as:
1325  // [
1326  // [0] => 'zh-hans:<span style="font-size:120%;">xxx</span>'
1327  // [1] => 'zh-hant:<span style="font-size:120%;">yyy</span>'
1328  // [2] => ''
1329  // ]
1330  $expandedVariants = [];
1331  foreach ( $this->getVariants() as $variant ) {
1332  $expandedVariants[ $variant ] = 1;
1333  // Accept standard BCP 47 names for variants as well.
1334  $expandedVariants[ LanguageCode::bcp47( $variant ) ] = 1;
1335  }
1336  // Accept old deprecated names for variants
1337  foreach ( LanguageCode::getDeprecatedCodeMapping() as $old => $new ) {
1338  if ( isset( $expandedVariants[ $new ] ) ) {
1339  $expandedVariants[ $old ] = 1;
1340  }
1341  }
1342 
1343  $pat = '/;\s*(?=';
1344  foreach ( $expandedVariants as $variant => $ignore ) {
1345  // zh-hans:xxx;zh-hant:yyy
1346  $pat .= $variant . '\s*:|';
1347  // xxx=>zh-hans:yyy; xxx=>zh-hant:zzz
1348  $pat .= '[^;]*?=>\s*' . $variant . '\s*:|';
1349  }
1350  $pat .= '\s*$)/';
1351  $this->mVarSeparatorPattern = $pat;
1352  }
1354  }
1355 
1363  public function hasVariants() {
1364  return count( $this->getVariants() ) > 1;
1365  }
1366 
1377  public function hasVariant( $variant ) {
1378  return $variant && ( $variant === $this->validateVariant( $variant ) );
1379  }
1380 
1389  public function convertHtml( $text ) {
1390  // @phan-suppress-next-line SecurityCheck-DoubleEscaped convert() is documented to return html
1391  return htmlspecialchars( $this->convert( $text ) );
1392  }
1393 }
LanguageConverter\getVarSeparatorPattern
getVarSeparatorPattern()
Get the cached separator pattern for ConverterRule::parseRules()
Definition: LanguageConverter.php:1317
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:1377
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:415
LanguageConverter\$mMaxDepth
$mMaxDepth
Definition: LanguageConverter.php:76
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
$wgDisabledVariants
$wgDisabledVariants
Disabled variants array of language variant conversion.
Definition: DefaultSettings.php:3621
LanguageConverter\getExtraHashOptions
getExtraHashOptions()
Returns language specific hash options.
Definition: LanguageConverter.php:1043
User\isRegistered
isRegistered()
Get whether the user is registered.
Definition: User.php:2958
LanguageConverter\CACHE_VERSION_KEY
const CACHE_VERSION_KEY
Definition: LanguageConverter.php:79
LanguageConverter\$mUcfirst
$mUcfirst
Definition: LanguageConverter.php:71
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:186
$wgRequest
$wgRequest
Definition: Setup.php:681
LanguageConverter\__construct
__construct( $langobj)
Definition: LanguageConverter.php:84
LanguageConverter\$mLangObj
Language $mLangObj
Definition: LanguageConverter.php:69
LanguageConverter\$mURLVariant
$mURLVariant
Definition: LanguageConverter.php:73
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:1301
LanguageConverter\loadDefaultTables
loadDefaultTables()
Load default conversion tables.
Definition: LanguageConverter.php:1069
LanguageConverter\$mConvRuleTitle
$mConvRuleTitle
Definition: LanguageConverter.php:72
LanguageConverter\$mVarSeparatorPattern
$mVarSeparatorPattern
Definition: LanguageConverter.php:77
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:483
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:974
wfMessage
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Definition: GlobalFunctions.php:1182
User\isSafeToLoad
isSafeToLoad()
Test if it's safe to load this User object.
Definition: User.php:357
LanguageConverter\getDescVarSeparator
getDescVarSeparator()
Get desc var separator.
Definition: LanguageConverter.php:236
LanguageConverter\$mTables
ReplacementArray[] bool[] $mTables
Definition: LanguageConverter.php:64
Parser\MARKER_PREFIX
const MARKER_PREFIX
Definition: Parser.php:146
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:1363
LanguageConverter\$mHeaderVariant
$mHeaderVariant
Definition: LanguageConverter.php:75
NS_MAIN
const NS_MAIN
Definition: Defines.php:64
LanguageConverter\$mUserVariant
$mUserVariant
Definition: LanguageConverter.php:74
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:1126
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
MWException
MediaWiki exception.
Definition: MWException.php:29
deprecatePublicPropertyFallback
deprecatePublicPropertyFallback(string $property, string $version, callable $getter, ?callable $setter=null, $class=null, $component=null)
Mark a removed public property as deprecated and provide fallback getter and setter callables.
Definition: DeprecationHelper.php:132
MediaWiki\Logger\LoggerFactory
PSR-3 logger instance factory.
Definition: LoggerFactory.php:45
LanguageConverter\getVariants
getVariants()
Get all valid variants for current Coverter.
Definition: LanguageConverter.php:256
$wgLanguageConverterCacheType
$wgLanguageConverterCacheType
The cache type for storing language conversion tables, which are used when parsing certain text and i...
Definition: DefaultSettings.php:2795
LanguageConverter\recursiveConvertTopLevel
recursiveConvertTopLevel( $text, $variant, $depth=0)
Recursively convert text on the outside.
Definition: LanguageConverter.php:841
$wgDefaultLanguageVariant
$wgDefaultLanguageVariant
Default variant code, if false, the default will be the language code.
Definition: DefaultSettings.php:3604
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:655
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:896
deprecatePublicProperty
deprecatePublicProperty( $property, $version, $class=null, $component=null)
Mark a property as deprecated.
Definition: DeprecationHelper.php:103
LanguageConverter\getVariantsFallbacks
getVariantsFallbacks()
Get language variants fallbacks.
LanguageConverter\postLoadTables
postLoadTables()
Hook for post processing after conversion tables are loaded.
Definition: LanguageConverter.php:1116
Title\makeTitleSafe
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:706
LanguageConverter\$mTablesLoaded
$mTablesLoaded
Definition: LanguageConverter.php:59
$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
User\getOption
getOption( $oname, $defaultOverride=null, $ignoreHidden=false)
Get the user's current setting for a given option.
Definition: User.php:2551
LanguageConverter\convertCategoryKey
convertCategoryKey( $key)
Convert the sorting key for category links.
Definition: LanguageConverter.php:1291
LanguageConverter\parseCachedTable
parseCachedTable( $code, $subpage='', $recursive=true)
Parse the conversion table stored in the cache.
Definition: LanguageConverter.php:1155
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:639
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:1273
LanguageConverter\autoConvert
autoConvert( $text, $toVariant=false)
Dictionary-based conversion.
Definition: LanguageConverter.php:495
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:725
LanguageConverter\convert
convert( $text)
Convert text to different variants of a language.
Definition: LanguageConverter.php:808
LanguageConverter\loadTables
loadTables( $fromCache=true)
Load conversion tables either from the cache or the disk.
Definition: LanguageConverter.php:1079
LanguageConverter\getUserVariant
getUserVariant(User $user)
Determine if the user has a variant set.
Definition: LanguageConverter.php:406
LanguageConverter\$languagesWithVariants
static array $languagesWithVariants
languages supporting variants
Definition: LanguageConverter.php:44
ConverterRule\getRulesAction
getRulesAction()
Return how deal with conversion rules.
Definition: ConverterRule.php:483
$cache
$cache
Definition: mcc.php:33
LanguageConverter\getAdditionalManualLevel
getAdditionalManualLevel()
Provides additinal 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:687
LanguageConverter\autoConvertToAllVariants
autoConvertToAllVariants( $text)
Call translate() to convert text to all valid variants.
Definition: LanguageConverter.php:671
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:822
$t
$t
Definition: testCompression.php:74
LanguageConverter\getHeaderVariant
getHeaderVariant()
Determine the language variant from the Accept-Language header.
Definition: LanguageConverter.php:438
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:68
LanguageConverter\guessVariant
guessVariant( $text, $variant)
Guess if a text is written in a variant.
Definition: LanguageConverter.php:1059
LanguageConverter
Base class for multi-variant language conversion.
Definition: LanguageConverter.php:36
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:1389
LanguageConverter\convertNamespace
convertNamespace( $index, $variant=null)
Get the namespace display name in the preferred variant.
Definition: LanguageConverter.php:746
LanguageConverter\getAdditionalFlags
getAdditionalFlags()
Provides additinal 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