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