MediaWiki  master
CoreParserFunctions.php
Go to the documentation of this file.
1 <?php
35 use Wikimedia\RemexHtml\Tokenizer\Attributes;
36 use Wikimedia\RemexHtml\Tokenizer\PlainAttributes;
37 
44  private const MAX_TTS = 900;
45 
49  public const REGISTER_OPTIONS = [
50  // See documentation for the corresponding config options
51  MainConfigNames::AllowDisplayTitle,
52  MainConfigNames::AllowSlowParserFunctions,
53  ];
54 
63  public static function register( Parser $parser, ServiceOptions $options ) {
64  $options->assertRequiredOptions( self::REGISTER_OPTIONS );
65  $allowDisplayTitle = $options->get( MainConfigNames::AllowDisplayTitle );
66  $allowSlowParserFunctions = $options->get( MainConfigNames::AllowSlowParserFunctions );
67 
68  # Syntax for arguments (see Parser::setFunctionHook):
69  # "name for lookup in localized magic words array",
70  # function callback,
71  # optional Parser::SFH_NO_HASH to omit the hash from calls (e.g. {{int:...}}
72  # instead of {{#int:...}})
73  $noHashFunctions = [
74  'ns', 'nse', 'urlencode', 'lcfirst', 'ucfirst', 'lc', 'uc',
75  'localurl', 'localurle', 'fullurl', 'fullurle', 'canonicalurl',
76  'canonicalurle', 'formatnum', 'grammar', 'gender', 'plural', 'bidi',
77  'numberingroup', 'language',
78  'padleft', 'padright', 'anchorencode', 'defaultsort', 'filepath',
79  'pagesincategory', 'pagesize', 'protectionlevel', 'protectionexpiry',
80  # The following are the "parser function" forms of magic
81  # variables defined in CoreMagicVariables. The no-args form will
82  # go through the magic variable code path (and be cached); the
83  # presence of arguments will cause the parser function form to
84  # be invoked. (Note that the actual implementation will pass
85  # a Parser object as first argument, in addition to the
86  # parser function parameters.)
87 
88  # For this group, the first parameter to the parser function is
89  # "page title", and the no-args form (and the magic variable)
90  # defaults to "current page title".
91  'pagename', 'pagenamee',
92  'fullpagename', 'fullpagenamee',
93  'subpagename', 'subpagenamee',
94  'rootpagename', 'rootpagenamee',
95  'basepagename', 'basepagenamee',
96  'talkpagename', 'talkpagenamee',
97  'subjectpagename', 'subjectpagenamee',
98  'pageid', 'revisionid', 'revisionday',
99  'revisionday2', 'revisionmonth', 'revisionmonth1', 'revisionyear',
100  'revisiontimestamp',
101  'revisionuser',
102  'cascadingsources',
103  'namespace', 'namespacee', 'namespacenumber', 'talkspace', 'talkspacee',
104  'subjectspace', 'subjectspacee',
105 
106  # More parser functions corresponding to CoreMagicVariables.
107  # For this group, the first parameter to the parser function is
108  # "raw" (uses the 'raw' format if present) and the no-args form
109  # (and the magic variable) defaults to 'not raw'.
110  'numberofarticles', 'numberoffiles',
111  'numberofusers',
112  'numberofactiveusers',
113  'numberofpages',
114  'numberofadmins',
115  'numberofedits',
116  ];
117  foreach ( $noHashFunctions as $func ) {
118  $parser->setFunctionHook( $func, [ __CLASS__, $func ], Parser::SFH_NO_HASH );
119  }
120 
121  $parser->setFunctionHook( 'int', [ __CLASS__, 'intFunction' ], Parser::SFH_NO_HASH );
122  $parser->setFunctionHook( 'special', [ __CLASS__, 'special' ] );
123  $parser->setFunctionHook( 'speciale', [ __CLASS__, 'speciale' ] );
124  $parser->setFunctionHook( 'tag', [ __CLASS__, 'tagObj' ], Parser::SFH_OBJECT_ARGS );
125  $parser->setFunctionHook( 'formatdate', [ __CLASS__, 'formatDate' ] );
126 
127  if ( $allowDisplayTitle ) {
128  $parser->setFunctionHook(
129  'displaytitle',
130  [ __CLASS__, 'displaytitle' ],
132  );
133  }
134  if ( $allowSlowParserFunctions ) {
135  $parser->setFunctionHook(
136  'pagesinnamespace',
137  [ __CLASS__, 'pagesinnamespace' ],
139  );
140  }
141  }
142 
149  public static function intFunction( $parser, $part1 = '', ...$params ) {
150  if ( strval( $part1 ) !== '' ) {
151  $message = wfMessage( $part1, $params )
152  ->inLanguage( $parser->getOptions()->getUserLangObj() );
153  return [ $message->plain(), 'noparse' => false ];
154  } else {
155  return [ 'found' => false ];
156  }
157  }
158 
166  public static function formatDate( $parser, $date, $defaultPref = null ) {
167  $lang = $parser->getTargetLanguage();
168  $df = MediaWikiServices::getInstance()->getDateFormatterFactory()->get( $lang );
169 
170  $date = trim( $date );
171 
172  $pref = $parser->getOptions()->getDateFormat();
173 
174  // Specify a different default date format other than the normal default
175  // if the user has 'default' for their setting
176  if ( $pref == 'default' && $defaultPref ) {
177  $pref = $defaultPref;
178  }
179 
180  $date = $df->reformat( $pref, $date, [ 'match-whole' ] );
181  return $date;
182  }
183 
184  public static function ns( $parser, $part1 = '' ) {
185  if ( intval( $part1 ) || $part1 == "0" ) {
186  $index = intval( $part1 );
187  } else {
188  $index = $parser->getContentLanguage()->getNsIndex( str_replace( ' ', '_', $part1 ) );
189  }
190  if ( $index !== false ) {
191  return $parser->getContentLanguage()->getFormattedNsText( $index );
192  } else {
193  return [ 'found' => false ];
194  }
195  }
196 
197  public static function nse( $parser, $part1 = '' ) {
198  $ret = self::ns( $parser, $part1 );
199  if ( is_string( $ret ) ) {
200  $ret = wfUrlencode( str_replace( ' ', '_', $ret ) );
201  }
202  return $ret;
203  }
204 
217  public static function urlencode( $parser, $s = '', $arg = null ) {
218  static $magicWords = null;
219  if ( $magicWords === null ) {
220  $magicWords =
221  $parser->getMagicWordFactory()->newArray( [ 'url_path', 'url_query', 'url_wiki' ] );
222  }
223  switch ( $magicWords->matchStartToEnd( $arg ?? '' ) ) {
224  // Encode as though it's a wiki page, '_' for ' '.
225  case 'url_wiki':
226  $func = 'wfUrlencode';
227  $s = str_replace( ' ', '_', $s );
228  break;
229 
230  // Encode for an HTTP Path, '%20' for ' '.
231  case 'url_path':
232  $func = 'rawurlencode';
233  break;
234 
235  // Encode for HTTP query, '+' for ' '.
236  case 'url_query':
237  default:
238  $func = 'urlencode';
239  }
240  // See T105242, where the choice to kill markers and various
241  // other options were discussed.
242  return $func( $parser->killMarkers( $s ) );
243  }
244 
245  public static function lcfirst( $parser, $s = '' ) {
246  return $parser->getContentLanguage()->lcfirst( $s );
247  }
248 
249  public static function ucfirst( $parser, $s = '' ) {
250  return $parser->getContentLanguage()->ucfirst( $s );
251  }
252 
258  public static function lc( $parser, $s = '' ) {
259  return $parser->markerSkipCallback( $s, [ $parser->getContentLanguage(), 'lc' ] );
260  }
261 
267  public static function uc( $parser, $s = '' ) {
268  return $parser->markerSkipCallback( $s, [ $parser->getContentLanguage(), 'uc' ] );
269  }
270 
271  public static function localurl( $parser, $s = '', $arg = null ) {
272  return self::urlFunction( 'getLocalURL', $s, $arg );
273  }
274 
275  public static function localurle( $parser, $s = '', $arg = null ) {
276  $temp = self::urlFunction( 'getLocalURL', $s, $arg );
277  if ( !is_string( $temp ) ) {
278  return $temp;
279  } else {
280  return htmlspecialchars( $temp, ENT_COMPAT );
281  }
282  }
283 
284  public static function fullurl( $parser, $s = '', $arg = null ) {
285  return self::urlFunction( 'getFullURL', $s, $arg );
286  }
287 
288  public static function fullurle( $parser, $s = '', $arg = null ) {
289  $temp = self::urlFunction( 'getFullURL', $s, $arg );
290  if ( !is_string( $temp ) ) {
291  return $temp;
292  } else {
293  return htmlspecialchars( $temp, ENT_COMPAT );
294  }
295  }
296 
297  public static function canonicalurl( $parser, $s = '', $arg = null ) {
298  return self::urlFunction( 'getCanonicalURL', $s, $arg );
299  }
300 
301  public static function canonicalurle( $parser, $s = '', $arg = null ) {
302  $temp = self::urlFunction( 'getCanonicalURL', $s, $arg );
303  if ( !is_string( $temp ) ) {
304  return $temp;
305  } else {
306  return htmlspecialchars( $temp, ENT_COMPAT );
307  }
308  }
309 
310  public static function urlFunction( $func, $s = '', $arg = null ) {
311  # Due to order of execution of a lot of bits, the values might be encoded
312  # before arriving here; if that's true, then the title can't be created
313  # and the variable will fail. If we can't get a decent title from the first
314  # attempt, url-decode and try for a second.
315  $title = Title::newFromText( $s ) ?? Title::newFromURL( urldecode( $s ) );
316  if ( $title !== null ) {
317  # Convert NS_MEDIA -> NS_FILE
318  if ( $title->inNamespace( NS_MEDIA ) ) {
319  $title = Title::makeTitle( NS_FILE, $title->getDBkey() );
320  }
321  if ( $arg !== null ) {
322  $text = $title->$func( $arg );
323  } else {
324  $text = $title->$func();
325  }
326  return $text;
327  } else {
328  return [ 'found' => false ];
329  }
330  }
331 
338  public static function formatnum( $parser, $num = '', $arg = null ) {
339  if ( self::matchAgainstMagicword( $parser->getMagicWordFactory(), 'rawsuffix', $arg ) ) {
340  $func = [ $parser->getTargetLanguage(), 'parseFormattedNumber' ];
341  } elseif (
342  self::matchAgainstMagicword( $parser->getMagicWordFactory(), 'nocommafysuffix', $arg )
343  ) {
344  $func = [ $parser->getTargetLanguage(), 'formatNumNoSeparators' ];
345  $func = self::getLegacyFormatNum( $parser, $func );
346  } else {
347  $func = [ $parser->getTargetLanguage(), 'formatNum' ];
348  $func = self::getLegacyFormatNum( $parser, $func );
349  }
350  return $parser->markerSkipCallback( $num, $func );
351  }
352 
359  private static function getLegacyFormatNum( $parser, $callback ) {
360  // For historic reasons, the formatNum parser function will
361  // take arguments which are not actually formatted numbers,
362  // which then trigger deprecation warnings in Language::formatNum*.
363  // Instead emit a tracking category instead to allow linting.
364  return static function ( $number ) use ( $parser, $callback ) {
365  $validNumberRe = '(-(?=[\d\.]))?(\d+|(?=\.\d))(\.\d*)?([Ee][-+]?\d+)?';
366  if (
367  !is_numeric( $number ) &&
368  $number !== (string)NAN &&
369  $number !== (string)INF &&
370  $number !== (string)-INF
371  ) {
372  $parser->addTrackingCategory( 'nonnumeric-formatnum' );
373  // Don't split on NAN/INF in the legacy case since they are
374  // likely to be found embedded inside non-numeric text.
375  return preg_replace_callback( "/{$validNumberRe}/", static function ( $m ) use ( $callback ) {
376  return call_user_func( $callback, $m[0] );
377  }, $number );
378  }
379  return call_user_func( $callback, $number );
380  };
381  }
382 
389  public static function grammar( $parser, $case = '', $word = '' ) {
390  $word = $parser->killMarkers( $word );
391  return $parser->getTargetLanguage()->convertGrammar( $word, $case );
392  }
393 
400  public static function gender( $parser, $username, ...$forms ) {
401  // Some shortcuts to avoid loading user data unnecessarily
402  if ( count( $forms ) === 0 ) {
403  return '';
404  } elseif ( count( $forms ) === 1 ) {
405  return $forms[0];
406  }
407 
408  $username = trim( $username );
409 
410  $userOptionsLookup = MediaWikiServices::getInstance()->getUserOptionsLookup();
411  $gender = $userOptionsLookup->getDefaultOption( 'gender' );
412 
413  // allow prefix and normalize (e.g. "&#42;foo" -> "*foo" ).
414  $title = Title::newFromText( $username, NS_USER );
415 
416  if ( $title && $title->inNamespace( NS_USER ) ) {
417  $username = $title->getText();
418  }
419 
420  // check parameter, or use the ParserOptions if in interface message
421  $user = User::newFromName( $username );
422  $genderCache = MediaWikiServices::getInstance()->getGenderCache();
423  if ( $user ) {
424  $gender = $genderCache->getGenderOf( $user, __METHOD__ );
425  } elseif ( $username === '' && $parser->getOptions()->getInterfaceMessage() ) {
426  $gender = $genderCache->getGenderOf( $parser->getOptions()->getUserIdentity(), __METHOD__ );
427  }
428  $ret = $parser->getTargetLanguage()->gender( $gender, $forms );
429  return $ret;
430  }
431 
438  public static function plural( $parser, $text = '', ...$forms ) {
439  $text = $parser->getTargetLanguage()->parseFormattedNumber( $text );
440  settype( $text, ctype_digit( $text ) ? 'int' : 'float' );
441  // @phan-suppress-next-line PhanTypeMismatchArgument Phan does not handle settype
442  return $parser->getTargetLanguage()->convertPlural( $text, $forms );
443  }
444 
450  public static function bidi( $parser, $text = '' ) {
451  return $parser->getTargetLanguage()->embedBidi( $text );
452  }
453 
463  public static function displaytitle( $parser, $text = '', $uarg = '' ) {
464  $restrictDisplayTitle = MediaWikiServices::getInstance()->getMainConfig()
465  ->get( MainConfigNames::RestrictDisplayTitle );
466 
467  static $magicWords = null;
468  if ( $magicWords === null ) {
469  $magicWords = $parser->getMagicWordFactory()->newArray(
470  [ 'displaytitle_noerror', 'displaytitle_noreplace' ] );
471  }
472  $arg = $magicWords->matchStartToEnd( $uarg );
473 
474  // parse a limited subset of wiki markup (just the single quote items)
475  $text = $parser->doQuotes( $text );
476 
477  // remove stripped text (e.g. the UNIQ-QINU stuff) that was generated by tag extensions/whatever
478  $text = $parser->killMarkers( $text );
479 
480  // See T28547 for rationale for this processing.
481  // list of disallowed tags for DISPLAYTITLE
482  // these will be escaped even though they are allowed in normal wiki text
483  $bad = [ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'div', 'blockquote', 'ol', 'ul', 'li', 'hr',
484  'table', 'tr', 'th', 'td', 'dl', 'dd', 'caption', 'p', 'ruby', 'rb', 'rt', 'rtc', 'rp', 'br' ];
485 
486  // disallow some styles that could be used to bypass $wgRestrictDisplayTitle
487  if ( $restrictDisplayTitle ) {
488  // This code is tested with the cases marked T28547 in
489  // parserTests.txt
490  $htmlTagsCallback = static function ( Attributes $attr ): Attributes {
491  $decoded = $attr->getValues();
492 
493  if ( isset( $decoded['style'] ) ) {
494  // this is called later anyway, but we need it right now for the regexes below to be safe
495  // calling it twice doesn't hurt
496  $decoded['style'] = Sanitizer::checkCss( $decoded['style'] );
497 
498  if ( preg_match( '/(display|user-select|visibility)\s*:/i', $decoded['style'] ) ) {
499  $decoded['style'] = '/* attempt to bypass $wgRestrictDisplayTitle */';
500  }
501  }
502 
503  return new PlainAttributes( $decoded );
504  };
505  } else {
506  $htmlTagsCallback = null;
507  }
508 
509  // only requested titles that normalize to the actual title are allowed through
510  // if $wgRestrictDisplayTitle is true (it is by default)
511  // mimic the escaping process that occurs in OutputPage::setPageTitle
512  $text = Sanitizer::removeSomeTags( $text, [
513  'attrCallback' => $htmlTagsCallback,
514  'removeTags' => $bad,
515  ] );
516  $title = Title::newFromText( Sanitizer::stripAllTags( $text ) );
517  // Decode entities in $text the same way that Title::newFromText does
518  $filteredText = Sanitizer::decodeCharReferencesAndNormalize( $text );
519 
520  if ( !$restrictDisplayTitle ||
521  ( $title instanceof Title
522  && !$title->hasFragment()
523  && $title->equals( $parser->getTitle() ) )
524  ) {
525  $old = $parser->getOutput()->getPageProperty( 'displaytitle' );
526  if ( $old === null || $arg !== 'displaytitle_noreplace' ) {
527  $parser->getOutput()->setDisplayTitle( $text );
528  }
529  if ( $old !== null && $old !== $text && !$arg ) {
530 
531  $converter = $parser->getTargetLanguageConverter();
532  return '<span class="error">' .
533  $parser->msg( 'duplicate-displaytitle',
534  // Message should be parsed, but these params should only be escaped.
535  $converter->markNoConversion( wfEscapeWikiText( $old ) ),
536  $converter->markNoConversion( wfEscapeWikiText( $filteredText ) )
537  )->text() .
538  '</span>';
539  } else {
540  return '';
541  }
542  } else {
543  $parser->getOutput()->addWarningMsg(
544  'restricted-displaytitle',
545  // Message should be parsed, but this param should only be escaped.
546  Message::plaintextParam( $filteredText )
547  );
548  $parser->addTrackingCategory( 'restricted-displaytitle-ignored' );
549  }
550  }
551 
561  private static function matchAgainstMagicword(
562  MagicWordFactory $magicWordFactory, $magicword, $value
563  ) {
564  $value = trim( strval( $value ) );
565  if ( $value === '' ) {
566  return false;
567  }
568  $mwObject = $magicWordFactory->get( $magicword );
569  return $mwObject->matchStartToEnd( $value );
570  }
571 
581  public static function formatRaw(
582  $num, $raw, $language, MagicWordFactory $magicWordFactory = null
583  ) {
584  if ( $raw !== null && $raw !== '' ) {
585  if ( !$magicWordFactory ) {
586  $magicWordFactory = MediaWikiServices::getInstance()->getMagicWordFactory();
587  }
588  if ( self::matchAgainstMagicword( $magicWordFactory, 'rawsuffix', $raw ) ) {
589  return (string)$num;
590  }
591  }
592  return $language->formatNum( $num );
593  }
594 
595  public static function numberofpages( $parser, $raw = null ) {
596  return self::formatRaw( SiteStats::pages(), $raw, $parser->getTargetLanguage() );
597  }
598 
599  public static function numberofusers( $parser, $raw = null ) {
600  return self::formatRaw( SiteStats::users(), $raw, $parser->getTargetLanguage() );
601  }
602 
603  public static function numberofactiveusers( $parser, $raw = null ) {
604  return self::formatRaw( SiteStats::activeUsers(), $raw, $parser->getTargetLanguage() );
605  }
606 
607  public static function numberofarticles( $parser, $raw = null ) {
608  return self::formatRaw( SiteStats::articles(), $raw, $parser->getTargetLanguage() );
609  }
610 
611  public static function numberoffiles( $parser, $raw = null ) {
612  return self::formatRaw( SiteStats::images(), $raw, $parser->getTargetLanguage() );
613  }
614 
615  public static function numberofadmins( $parser, $raw = null ) {
616  return self::formatRaw(
617  SiteStats::numberingroup( 'sysop' ),
618  $raw,
619  $parser->getTargetLanguage()
620  );
621  }
622 
623  public static function numberofedits( $parser, $raw = null ) {
624  return self::formatRaw( SiteStats::edits(), $raw, $parser->getTargetLanguage() );
625  }
626 
627  public static function pagesinnamespace( $parser, $namespace = 0, $raw = null ) {
628  return self::formatRaw(
629  SiteStats::pagesInNs( intval( $namespace ) ),
630  $raw,
631  $parser->getTargetLanguage()
632  );
633  }
634 
635  public static function numberingroup( $parser, $name = '', $raw = null ) {
636  return self::formatRaw(
637  SiteStats::numberingroup( strtolower( $name ) ),
638  $raw,
639  $parser->getTargetLanguage()
640  );
641  }
642 
651  private static function makeTitle( Parser $parser, ?string $t ) {
652  if ( $t === null ) {
653  // For consistency with magic variable forms
654  $title = $parser->getTitle();
655  } else {
656  $title = Title::newFromText( $t );
657  }
658  return $title;
659  }
660 
669  public static function namespace( $parser, $title = null ) {
670  $t = self::makeTitle( $parser, $title );
671  if ( $t === null ) {
672  return '';
673  }
674  return str_replace( '_', ' ', $t->getNsText() );
675  }
676 
687  public static function mwnamespace( $parser, $title = null ) {
688  wfDeprecated( __METHOD__, '1.39' );
689  return self::namespace( $parser, $title );
690  }
691 
692  public static function namespacee( $parser, $title = null ) {
693  $t = self::makeTitle( $parser, $title );
694  if ( $t === null ) {
695  return '';
696  }
697  return wfUrlencode( $t->getNsText() );
698  }
699 
700  public static function namespacenumber( $parser, $title = null ) {
701  $t = self::makeTitle( $parser, $title );
702  if ( $t === null ) {
703  return '';
704  }
705  return (string)$t->getNamespace();
706  }
707 
708  public static function talkspace( $parser, $title = null ) {
709  $t = self::makeTitle( $parser, $title );
710  if ( $t === null || !$t->canHaveTalkPage() ) {
711  return '';
712  }
713  return str_replace( '_', ' ', $t->getTalkNsText() );
714  }
715 
716  public static function talkspacee( $parser, $title = null ) {
717  $t = self::makeTitle( $parser, $title );
718  if ( $t === null || !$t->canHaveTalkPage() ) {
719  return '';
720  }
721  return wfUrlencode( $t->getTalkNsText() );
722  }
723 
724  public static function subjectspace( $parser, $title = null ) {
725  $t = self::makeTitle( $parser, $title );
726  if ( $t === null ) {
727  return '';
728  }
729  return str_replace( '_', ' ', $t->getSubjectNsText() );
730  }
731 
732  public static function subjectspacee( $parser, $title = null ) {
733  $t = self::makeTitle( $parser, $title );
734  if ( $t === null ) {
735  return '';
736  }
737  return wfUrlencode( $t->getSubjectNsText() );
738  }
739 
747  public static function pagename( $parser, $title = null ) {
748  $t = self::makeTitle( $parser, $title );
749  if ( $t === null ) {
750  return '';
751  }
752  return wfEscapeWikiText( $t->getText() );
753  }
754 
755  public static function pagenamee( $parser, $title = null ) {
756  $t = self::makeTitle( $parser, $title );
757  if ( $t === null ) {
758  return '';
759  }
760  return wfEscapeWikiText( $t->getPartialURL() );
761  }
762 
763  public static function fullpagename( $parser, $title = null ) {
764  $t = self::makeTitle( $parser, $title );
765  if ( $t === null ) {
766  return '';
767  }
768  return wfEscapeWikiText( $t->getPrefixedText() );
769  }
770 
771  public static function fullpagenamee( $parser, $title = null ) {
772  $t = self::makeTitle( $parser, $title );
773  if ( $t === null ) {
774  return '';
775  }
776  return wfEscapeWikiText( $t->getPrefixedURL() );
777  }
778 
779  public static function subpagename( $parser, $title = null ) {
780  $t = self::makeTitle( $parser, $title );
781  if ( $t === null ) {
782  return '';
783  }
784  return wfEscapeWikiText( $t->getSubpageText() );
785  }
786 
787  public static function subpagenamee( $parser, $title = null ) {
788  $t = self::makeTitle( $parser, $title );
789  if ( $t === null ) {
790  return '';
791  }
792  return wfEscapeWikiText( $t->getSubpageUrlForm() );
793  }
794 
795  public static function rootpagename( $parser, $title = null ) {
796  $t = self::makeTitle( $parser, $title );
797  if ( $t === null ) {
798  return '';
799  }
800  return wfEscapeWikiText( $t->getRootText() );
801  }
802 
803  public static function rootpagenamee( $parser, $title = null ) {
804  $t = self::makeTitle( $parser, $title );
805  if ( $t === null ) {
806  return '';
807  }
808  return wfEscapeWikiText( wfUrlencode( str_replace( ' ', '_', $t->getRootText() ) ) );
809  }
810 
811  public static function basepagename( $parser, $title = null ) {
812  $t = self::makeTitle( $parser, $title );
813  if ( $t === null ) {
814  return '';
815  }
816  return wfEscapeWikiText( $t->getBaseText() );
817  }
818 
819  public static function basepagenamee( $parser, $title = null ) {
820  $t = self::makeTitle( $parser, $title );
821  if ( $t === null ) {
822  return '';
823  }
824  return wfEscapeWikiText( wfUrlencode( str_replace( ' ', '_', $t->getBaseText() ) ) );
825  }
826 
827  public static function talkpagename( $parser, $title = null ) {
828  $t = self::makeTitle( $parser, $title );
829  if ( $t === null || !$t->canHaveTalkPage() ) {
830  return '';
831  }
832  return wfEscapeWikiText( $t->getTalkPage()->getPrefixedText() );
833  }
834 
835  public static function talkpagenamee( $parser, $title = null ) {
836  $t = self::makeTitle( $parser, $title );
837  if ( $t === null || !$t->canHaveTalkPage() ) {
838  return '';
839  }
840  return wfEscapeWikiText( $t->getTalkPage()->getPrefixedURL() );
841  }
842 
843  public static function subjectpagename( $parser, $title = null ) {
844  $t = self::makeTitle( $parser, $title );
845  if ( $t === null ) {
846  return '';
847  }
848  return wfEscapeWikiText( $t->getSubjectPage()->getPrefixedText() );
849  }
850 
851  public static function subjectpagenamee( $parser, $title = null ) {
852  $t = self::makeTitle( $parser, $title );
853  if ( $t === null ) {
854  return '';
855  }
856  return wfEscapeWikiText( $t->getSubjectPage()->getPrefixedURL() );
857  }
858 
869  public static function pagesincategory( $parser, $name = '', $arg1 = '', $arg2 = '' ) {
870  static $magicWords = null;
871  if ( $magicWords === null ) {
872  $magicWords = $parser->getMagicWordFactory()->newArray( [
873  'pagesincategory_all',
874  'pagesincategory_pages',
875  'pagesincategory_subcats',
876  'pagesincategory_files'
877  ] );
878  }
879  static $cache = [];
880 
881  // split the given option to its variable
882  if ( self::matchAgainstMagicword( $parser->getMagicWordFactory(), 'rawsuffix', $arg1 ) ) {
883  // {{pagesincategory:|raw[|type]}}
884  $raw = $arg1;
885  $type = $magicWords->matchStartToEnd( $arg2 );
886  } else {
887  // {{pagesincategory:[|type[|raw]]}}
888  $type = $magicWords->matchStartToEnd( $arg1 );
889  $raw = $arg2;
890  }
891  if ( !$type ) { // backward compatibility
892  $type = 'pagesincategory_all';
893  }
894 
895  $title = Title::makeTitleSafe( NS_CATEGORY, $name );
896  if ( !$title ) { # invalid title
897  return self::formatRaw( 0, $raw, $parser->getTargetLanguage() );
898  }
899  $languageConverter = MediaWikiServices::getInstance()
900  ->getLanguageConverterFactory()
901  ->getLanguageConverter( $parser->getContentLanguage() );
902  $languageConverter->findVariantLink( $name, $title, true );
903 
904  // Normalize name for cache
905  $name = $title->getDBkey();
906 
907  if ( !isset( $cache[$name] ) ) {
908  $category = Category::newFromTitle( $title );
909 
910  $allCount = $subcatCount = $fileCount = $pageCount = 0;
911  if ( $parser->incrementExpensiveFunctionCount() ) {
912  $allCount = $category->getMemberCount();
913  $subcatCount = $category->getSubcatCount();
914  $fileCount = $category->getFileCount();
915  $pageCount = $category->getPageCount( Category::COUNT_CONTENT_PAGES );
916  }
917  $cache[$name]['pagesincategory_all'] = $allCount;
918  $cache[$name]['pagesincategory_pages'] = $pageCount;
919  $cache[$name]['pagesincategory_subcats'] = $subcatCount;
920  $cache[$name]['pagesincategory_files'] = $fileCount;
921  }
922 
923  $count = $cache[$name][$type];
924  return self::formatRaw( $count, $raw, $parser->getTargetLanguage() );
925  }
926 
936  public static function pagesize( $parser, $page = '', $raw = null ) {
937  $title = Title::newFromText( $page );
938 
939  if ( !is_object( $title ) ) {
940  return self::formatRaw( 0, $raw, $parser->getTargetLanguage() );
941  }
942 
943  // fetch revision from cache/database and return the value
944  $rev = self::getCachedRevisionObject( $parser, $title, ParserOutputFlags::VARY_REVISION_SHA1 );
945  $length = $rev ? $rev->getSize() : 0;
946  if ( $length === null ) {
947  // We've had bugs where rev_len was not being recorded for empty pages, see T135414
948  $length = 0;
949  }
950  return self::formatRaw( $length, $raw, $parser->getTargetLanguage() );
951  }
952 
965  public static function protectionlevel( $parser, $type = '', $title = '' ) {
966  $titleObject = Title::newFromText( $title ) ?? $parser->getTitle();
967  $restrictionStore = MediaWikiServices::getInstance()->getRestrictionStore();
968  if ( $restrictionStore->areRestrictionsLoaded( $titleObject ) || $parser->incrementExpensiveFunctionCount() ) {
969  $restrictions = $restrictionStore->getRestrictions( $titleObject, strtolower( $type ) );
970  # RestrictionStore::getRestrictions returns an array, its possible it may have
971  # multiple values in the future
972  return implode( ',', $restrictions );
973  }
974  return '';
975  }
976 
989  public static function protectionexpiry( $parser, $type = '', $title = '' ) {
990  $titleObject = Title::newFromText( $title ) ?? $parser->getTitle();
991  $restrictionStore = MediaWikiServices::getInstance()->getRestrictionStore();
992  if ( $restrictionStore->areRestrictionsLoaded( $titleObject ) || $parser->incrementExpensiveFunctionCount() ) {
993  // getRestrictionExpiry() returns null on invalid type; trying to
994  // match protectionlevel() function that returns empty string instead
995  return $restrictionStore->getRestrictionExpiry( $titleObject, strtolower( $type ) ) ?? '';
996  }
997  return '';
998  }
999 
1007  public static function language( $parser, $code = '', $inLanguage = '' ) {
1008  $code = strtolower( $code );
1009  $inLanguage = strtolower( $inLanguage );
1010  $lang = MediaWikiServices::getInstance()
1011  ->getLanguageNameUtils()
1012  ->getLanguageName( $code, $inLanguage );
1013  return $lang !== '' ? $lang : LanguageCode::bcp47( $code );
1014  }
1015 
1025  public static function pad(
1026  $parser, $string, $length, $padding = '0', $direction = STR_PAD_RIGHT
1027  ) {
1028  $padding = $parser->killMarkers( $padding );
1029  $lengthOfPadding = mb_strlen( $padding );
1030  if ( $lengthOfPadding == 0 ) {
1031  return $string;
1032  }
1033 
1034  # The remaining length to add counts down to 0 as padding is added
1035  $length = min( (int)$length, 500 ) - mb_strlen( $string );
1036  if ( $length <= 0 ) {
1037  // Nothing to add
1038  return $string;
1039  }
1040 
1041  # $finalPadding is just $padding repeated enough times so that
1042  # mb_strlen( $string ) + mb_strlen( $finalPadding ) == $length
1043  $finalPadding = '';
1044  while ( $length > 0 ) {
1045  # If $length < $lengthofPadding, truncate $padding so we get the
1046  # exact length desired.
1047  $finalPadding .= mb_substr( $padding, 0, $length );
1048  $length -= $lengthOfPadding;
1049  }
1050 
1051  if ( $direction == STR_PAD_LEFT ) {
1052  return $finalPadding . $string;
1053  } else {
1054  return $string . $finalPadding;
1055  }
1056  }
1057 
1058  public static function padleft( $parser, $string = '', $length = 0, $padding = '0' ) {
1059  return self::pad( $parser, $string, $length, $padding, STR_PAD_LEFT );
1060  }
1061 
1062  public static function padright( $parser, $string = '', $length = 0, $padding = '0' ) {
1063  return self::pad( $parser, $string, $length, $padding );
1064  }
1065 
1071  public static function anchorencode( $parser, $text ) {
1072  $text = $parser->killMarkers( $text );
1073  $section = (string)substr( $parser->guessSectionNameFromWikiText( $text ), 1 );
1074  return Sanitizer::safeEncodeAttribute( $section );
1075  }
1076 
1077  public static function special( $parser, $text ) {
1078  [ $page, $subpage ] = MediaWikiServices::getInstance()->getSpecialPageFactory()->
1079  resolveAlias( $text );
1080  if ( $page ) {
1081  $title = SpecialPage::getTitleFor( $page, $subpage );
1082  return $title->getPrefixedText();
1083  } else {
1084  // unknown special page, just use the given text as its title, if at all possible
1085  $title = Title::makeTitleSafe( NS_SPECIAL, $text );
1086  return $title ? $title->getPrefixedText() : self::special( $parser, 'Badtitle' );
1087  }
1088  }
1089 
1090  public static function speciale( $parser, $text ) {
1091  return wfUrlencode( str_replace( ' ', '_', self::special( $parser, $text ) ) );
1092  }
1093 
1102  public static function defaultsort( $parser, $text, $uarg = '' ) {
1103  static $magicWords = null;
1104  if ( $magicWords === null ) {
1105  $magicWords = $parser->getMagicWordFactory()->newArray(
1106  [ 'defaultsort_noerror', 'defaultsort_noreplace' ] );
1107  }
1108  $arg = $magicWords->matchStartToEnd( $uarg );
1109 
1110  $text = trim( $text );
1111  if ( strlen( $text ) == 0 ) {
1112  return '';
1113  }
1114  $old = $parser->getOutput()->getPageProperty( 'defaultsort' );
1115  if ( $old === null || $arg !== 'defaultsort_noreplace' ) {
1116  $parser->getOutput()->setPageProperty( 'defaultsort', $text );
1117  }
1118 
1119  if ( $old === null || $old == $text || $arg ) {
1120  return '';
1121  } else {
1122  $converter = $parser->getTargetLanguageConverter();
1123  return '<span class="error">' .
1124  $parser->msg( 'duplicate-defaultsort',
1125  // Message should be parsed, but these params should only be escaped.
1126  $converter->markNoConversion( wfEscapeWikiText( $old ) ),
1127  $converter->markNoConversion( wfEscapeWikiText( $text ) )
1128  )->text() .
1129  '</span>';
1130  }
1131  }
1132 
1144  public static function filepath( $parser, $name = '', $argA = '', $argB = '' ) {
1145  $file = MediaWikiServices::getInstance()->getRepoGroup()->findFile( $name );
1146 
1147  if ( $argA == 'nowiki' ) {
1148  // {{filepath: | option [| size] }}
1149  $isNowiki = true;
1150  $parsedWidthParam = Parser::parseWidthParam( $argB );
1151  } else {
1152  // {{filepath: [| size [|option]] }}
1153  $parsedWidthParam = Parser::parseWidthParam( $argA );
1154  $isNowiki = ( $argB == 'nowiki' );
1155  }
1156 
1157  if ( $file ) {
1158  $url = $file->getFullUrl();
1159 
1160  // If a size is requested...
1161  if ( count( $parsedWidthParam ) ) {
1162  $mto = $file->transform( $parsedWidthParam );
1163  // ... and we can
1164  if ( $mto && !$mto->isError() ) {
1165  // ... change the URL to point to a thumbnail.
1166  $url = wfExpandUrl( $mto->getUrl(), PROTO_RELATIVE );
1167  }
1168  }
1169  if ( $isNowiki ) {
1170  return [ $url, 'nowiki' => true ];
1171  }
1172  return $url;
1173  } else {
1174  return '';
1175  }
1176  }
1177 
1185  public static function tagObj( $parser, $frame, $args ) {
1186  if ( !count( $args ) ) {
1187  return '';
1188  }
1189  $tagName = strtolower( trim( $frame->expand( array_shift( $args ) ) ) );
1190  $processNowiki = $parser->tagNeedsNowikiStrippedInTagPF( $tagName ) ? PPFrame::PROCESS_NOWIKI : 0;
1191 
1192  if ( count( $args ) ) {
1193  $inner = $frame->expand( array_shift( $args ), $processNowiki );
1194  } else {
1195  $inner = null;
1196  }
1197 
1198  $attributes = [];
1199  foreach ( $args as $arg ) {
1200  $bits = $arg->splitArg();
1201  if ( strval( $bits['index'] ) === '' ) {
1202  $name = trim( $frame->expand( $bits['name'], PPFrame::STRIP_COMMENTS ) );
1203  $value = trim( $frame->expand( $bits['value'] ) );
1204  if ( preg_match( '/^(?:["\'](.+)["\']|""|\'\')$/s', $value, $m ) ) {
1205  $value = $m[1] ?? '';
1206  }
1207  $attributes[$name] = $value;
1208  }
1209  }
1210 
1211  $stripList = $parser->getStripList();
1212  if ( !in_array( $tagName, $stripList ) ) {
1213  // we can't handle this tag (at least not now), so just re-emit it as an ordinary tag
1214  $attrText = '';
1215  foreach ( $attributes as $name => $value ) {
1216  $attrText .= ' ' . htmlspecialchars( $name ) .
1217  '="' . htmlspecialchars( $value, ENT_COMPAT ) . '"';
1218  }
1219  if ( $inner === null ) {
1220  return "<$tagName$attrText/>";
1221  }
1222  return "<$tagName$attrText>$inner</$tagName>";
1223  }
1224 
1225  $params = [
1226  'name' => $tagName,
1227  'inner' => $inner,
1228  'attributes' => $attributes,
1229  'close' => "</$tagName>",
1230  ];
1231  return $parser->extensionSubstitution( $params, $frame );
1232  }
1233 
1247  private static function getCachedRevisionObject( $parser, $title, $vary ) {
1248  if ( !$title ) {
1249  return null;
1250  }
1251 
1252  $revisionRecord = null;
1253 
1254  $isSelfReferential = $title->equals( $parser->getTitle() );
1255  if ( $isSelfReferential ) {
1256  // Revision is for the same title that is currently being parsed. Only use the last
1257  // saved revision, regardless of Parser::getRevisionId() or fake revision injection
1258  // callbacks against the current title.
1259 
1260  // FIXME (T318278): the above is the intention, but doesn't
1261  // describe the actual current behavior of this code, since
1262  // ->isCurrent() for the last saved revision will return
1263  // false so we're going to fall through and end up calling
1264  // ->getCurrentRevisionRecordOfTitle().
1265  $parserRevisionRecord = $parser->getRevisionRecordObject();
1266  if ( $parserRevisionRecord && $parserRevisionRecord->isCurrent() ) {
1267  $revisionRecord = $parserRevisionRecord;
1268  }
1269  }
1270 
1271  $parserOutput = $parser->getOutput();
1272  if ( !$revisionRecord ) {
1273  if (
1274  !$parser->isCurrentRevisionOfTitleCached( $title ) &&
1276  ) {
1277  return null; // not allowed
1278  }
1279  // Get the current revision, ignoring Parser::getRevisionId() being null/old
1280  $revisionRecord = $parser->fetchCurrentRevisionRecordOfTitle( $title );
1281  if ( !$revisionRecord ) {
1282  // Convert `false` error return to `null`
1283  $revisionRecord = null;
1284  }
1285  // Register dependency in templatelinks
1286  $parserOutput->addTemplate(
1287  $title,
1288  $revisionRecord ? $revisionRecord->getPageId() : 0,
1289  $revisionRecord ? $revisionRecord->getId() : 0
1290  );
1291  }
1292 
1293  if ( $isSelfReferential ) {
1294  wfDebug( __METHOD__ . ": used current revision, setting $vary" );
1295  // Upon page save, the result of the parser function using this might change
1296  $parserOutput->setOutputFlag( $vary );
1297  if ( $vary === ParserOutputFlags::VARY_REVISION_SHA1 && $revisionRecord ) {
1298  try {
1299  $sha1 = $revisionRecord->getSha1();
1300  } catch ( RevisionAccessException $e ) {
1301  $sha1 = null;
1302  }
1303  $parserOutput->setRevisionUsedSha1Base36( $sha1 );
1304  }
1305  }
1306 
1307  return $revisionRecord;
1308  }
1309 
1317  public static function pageid( $parser, $title = null ) {
1318  $t = self::makeTitle( $parser, $title );
1319  if ( !$t ) {
1320  return '';
1321  } elseif ( !$t->canExist() || $t->isExternal() ) {
1322  return 0; // e.g. special page or interwiki link
1323  }
1324 
1325  $parserOutput = $parser->getOutput();
1326 
1327  if ( $t->equals( $parser->getTitle() ) ) {
1328  // Revision is for the same title that is currently being parsed.
1329  // Use the title from Parser in case a new page ID was injected into it.
1330  $parserOutput->setOutputFlag( ParserOutputFlags::VARY_PAGE_ID );
1331  $id = $parser->getTitle()->getArticleID();
1332  if ( $id ) {
1333  $parserOutput->setSpeculativePageIdUsed( $id );
1334  }
1335 
1336  return $id;
1337  }
1338 
1339  // Check the link cache for the title
1340  $linkCache = MediaWikiServices::getInstance()->getLinkCache();
1341  $pdbk = $t->getPrefixedDBkey();
1342  $id = $linkCache->getGoodLinkID( $pdbk );
1343  if ( $id != 0 || $linkCache->isBadLink( $pdbk ) ) {
1344  $parserOutput->addLink( $t, $id );
1345 
1346  return $id;
1347  }
1348 
1349  // We need to load it from the DB, so mark expensive
1350  if ( $parser->incrementExpensiveFunctionCount() ) {
1351  $id = $t->getArticleID();
1352  $parserOutput->addLink( $t, $id );
1353 
1354  return $id;
1355  }
1356 
1357  return null;
1358  }
1359 
1367  public static function revisionid( $parser, $title = null ) {
1368  $t = self::makeTitle( $parser, $title );
1369  if ( $t === null ) {
1370  return '';
1371  }
1372 
1373  $services = MediaWikiServices::getInstance();
1374  if (
1375  $t->equals( $parser->getTitle() ) &&
1376  $services->getMainConfig()->get( MainConfigNames::MiserMode ) &&
1377  !$parser->getOptions()->getInterfaceMessage() &&
1378  // @TODO: disallow this word on all namespaces (T235957)
1379  $services->getNamespaceInfo()->isSubject( $t->getNamespace() )
1380  ) {
1381  // Use a stub result instead of the actual revision ID in order to avoid
1382  // double parses on page save but still allow preview detection (T137900)
1383  if ( $parser->getRevisionId() || $parser->getOptions()->getSpeculativeRevId() ) {
1384  return '-';
1385  } else {
1386  $parser->getOutput()->setOutputFlag( ParserOutputFlags::VARY_REVISION_EXISTS );
1387  return '';
1388  }
1389  }
1390  // Fetch revision from cache/database and return the value.
1391  // Inform the edit saving system that getting the canonical output
1392  // after revision insertion requires a parse that used that exact
1393  // revision ID.
1394  if ( $t->equals( $parser->getTitle() ) && $title === null ) {
1395  // special handling for no-arg case: use speculative rev id
1396  // for current page.
1397  $parser->getOutput()->setOutputFlag( ParserOutputFlags::VARY_REVISION_ID );
1398  $id = $parser->getRevisionId();
1399  if ( $id === 0 ) {
1400  $rev = $parser->getRevisionRecordObject();
1401  if ( $rev ) {
1402  $id = $rev->getId();
1403  }
1404  }
1405  if ( !$id ) {
1406  $id = $parser->getOptions()->getSpeculativeRevId();
1407  if ( $id ) {
1408  $parser->getOutput()->setSpeculativeRevIdUsed( $id );
1409  }
1410  }
1411  return (string)$id;
1412  }
1413  $rev = self::getCachedRevisionObject( $parser, $t, ParserOutputFlags::VARY_REVISION_ID );
1414  return $rev ? $rev->getId() : '';
1415  }
1416 
1417  private static function getRevisionTimestampSubstring(
1418  Parser $parser,
1419  Title $title,
1420  int $start,
1421  int $len,
1422  int $mtts
1423  ): string {
1424  // If fetching the revision timestamp of the current page, substitute the
1425  // speculative timestamp to be used when this revision is saved. This
1426  // avoids having to invalidate the cache immediately by assuming the "last
1427  // saved revision" will in fact be this one.
1428  // Don't do this for interface messages (eg, edit notices) however; in that
1429  // case fall through and use the actual timestamp of the last saved revision.
1430  if ( $title->equals( $parser->getTitle() ) && !$parser->getOptions()->getInterfaceMessage() ) {
1431  // Get the timezone-adjusted timestamp to be used for this revision
1432  $resNow = substr( $parser->getRevisionTimestamp(), $start, $len );
1433  // Possibly set vary-revision if there is not yet an associated revision
1434  if ( !$parser->getRevisionRecordObject() ) {
1435  // Get the timezone-adjusted timestamp $mtts seconds in the future.
1436  // This future is relative to the current time and not that of the
1437  // parser options. The rendered timestamp can be compared to that
1438  // of the timestamp specified by the parser options.
1439  $resThen = substr(
1440  $parser->getContentLanguage()->userAdjust( wfTimestamp( TS_MW, time() + $mtts ), '' ),
1441  $start,
1442  $len
1443  );
1444 
1445  if ( $resNow !== $resThen ) {
1446  // Inform the edit saving system that getting the canonical output after
1447  // revision insertion requires a parse that used an actual revision timestamp
1448  $parser->getOutput()->setOutputFlag( ParserOutputFlags::VARY_REVISION_TIMESTAMP );
1449  }
1450  }
1451 
1452  return $resNow;
1453  } else {
1454  $rev = self::getCachedRevisionObject( $parser, $title, ParserOutputFlags::VARY_REVISION_TIMESTAMP );
1455  if ( !$rev ) {
1456  return '';
1457  }
1458  $resNow = substr(
1459  $parser->getContentLanguage()->userAdjust( $rev->getTimestamp(), '' ), $start, $len
1460  );
1461  return $resNow;
1462  }
1463  }
1464 
1472  public static function revisionday( $parser, $title = null ) {
1473  $t = self::makeTitle( $parser, $title );
1474  if ( $t === null ) {
1475  return '';
1476  }
1477  return strval( (int)self::getRevisionTimestampSubstring(
1478  $parser, $t, 6, 2, self::MAX_TTS
1479  ) );
1480  }
1481 
1489  public static function revisionday2( $parser, $title = null ) {
1490  $t = self::makeTitle( $parser, $title );
1491  if ( $t === null ) {
1492  return '';
1493  }
1494  return self::getRevisionTimestampSubstring(
1495  $parser, $t, 6, 2, self::MAX_TTS
1496  );
1497  }
1498 
1506  public static function revisionmonth( $parser, $title = null ) {
1507  $t = self::makeTitle( $parser, $title );
1508  if ( $t === null ) {
1509  return '';
1510  }
1511  return self::getRevisionTimestampSubstring(
1512  $parser, $t, 4, 2, self::MAX_TTS
1513  );
1514  }
1515 
1523  public static function revisionmonth1( $parser, $title = null ) {
1524  $t = self::makeTitle( $parser, $title );
1525  if ( $t === null ) {
1526  return '';
1527  }
1528  return strval( (int)self::getRevisionTimestampSubstring(
1529  $parser, $t, 4, 2, self::MAX_TTS
1530  ) );
1531  }
1532 
1540  public static function revisionyear( $parser, $title = null ) {
1541  $t = self::makeTitle( $parser, $title );
1542  if ( $t === null ) {
1543  return '';
1544  }
1545  return self::getRevisionTimestampSubstring(
1546  $parser, $t, 0, 4, self::MAX_TTS
1547  );
1548  }
1549 
1557  public static function revisiontimestamp( $parser, $title = null ) {
1558  $t = self::makeTitle( $parser, $title );
1559  if ( $t === null ) {
1560  return '';
1561  }
1562  return self::getRevisionTimestampSubstring(
1563  $parser, $t, 0, 14, self::MAX_TTS
1564  );
1565  }
1566 
1574  public static function revisionuser( $parser, $title = null ) {
1575  $t = self::makeTitle( $parser, $title );
1576  if ( $t === null ) {
1577  return '';
1578  }
1579  // VARY_USER informs the edit saving system that getting the canonical
1580  // output after revision insertion requires a parse that used the
1581  // actual user ID.
1582  if ( $t->equals( $parser->getTitle() ) ) {
1583  // Fall back to Parser's "revision user" for the current title
1584  $parser->getOutput()->setOutputFlag( ParserOutputFlags::VARY_USER );
1585  // Note that getRevisionUser() can return null; we need to
1586  // be sure to cast this to (an empty) string, since returning
1587  // null means "magic variable not handled".
1588  return (string)$parser->getRevisionUser();
1589  }
1590  // Fetch revision from cache/database and return the value.
1591  $rev = self::getCachedRevisionObject( $parser, $t, ParserOutputFlags::VARY_USER );
1592  $user = ( $rev !== null ) ? $rev->getUser() : null;
1593  return $user ? $user->getName() : '';
1594  }
1595 
1608  public static function cascadingsources( $parser, $title = '' ) {
1609  $titleObject = Title::newFromText( $title ) ?? $parser->getTitle();
1610  $restrictionStore = MediaWikiServices::getInstance()->getRestrictionStore();
1611  if ( $restrictionStore->areCascadeProtectionSourcesLoaded( $titleObject )
1612  || $parser->incrementExpensiveFunctionCount()
1613  ) {
1614  $names = [];
1615  $sources = $restrictionStore->getCascadeProtectionSources( $titleObject );
1616  $titleFormatter = MediaWikiServices::getInstance()->getTitleFormatter();
1617  foreach ( $sources[0] as $sourcePageIdentity ) {
1618  $names[] = $titleFormatter->getPrefixedText( $sourcePageIdentity );
1619  }
1620  return implode( '|', $names );
1621  }
1622  return '';
1623  }
1624 }
const NS_USER
Definition: Defines.php:66
const NS_FILE
Definition: Defines.php:70
const NS_SPECIAL
Definition: Defines.php:53
const NS_MEDIA
Definition: Defines.php:52
const PROTO_RELATIVE
Definition: Defines.php:195
const NS_CATEGORY
Definition: Defines.php:78
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfUrlencode( $s)
We want some things to be included as literal characters in our title URLs for prettiness,...
wfExpandUrl( $url, $defaultProto=PROTO_CURRENT)
Expand a potentially local URL to a fully-qualified URL using $wgServer (or one of its alternatives).
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
$magicWords
@phpcs-require-sorted-array
Definition: MessagesAb.php:70
if(!defined('MW_SETUP_CALLBACK'))
Definition: WebStart.php:88
Various core parser functions, registered in every Parser.
static language( $parser, $code='', $inLanguage='')
Gives language names.
static numberingroup( $parser, $name='', $raw=null)
static rootpagename( $parser, $title=null)
static localurl( $parser, $s='', $arg=null)
static formatnum( $parser, $num='', $arg=null)
static ns( $parser, $part1='')
static protectionexpiry( $parser, $type='', $title='')
Returns the requested protection expiry for the current page.
static pagenamee( $parser, $title=null)
static padleft( $parser, $string='', $length=0, $padding='0')
static fullurle( $parser, $s='', $arg=null)
static revisionmonth( $parser, $title=null)
Get the month with leading zeros from the last revision of a specified page.
static fullpagename( $parser, $title=null)
static revisionid( $parser, $title=null)
Get the id from the last revision of a specified page.
static pagesinnamespace( $parser, $namespace=0, $raw=null)
static numberofusers( $parser, $raw=null)
static special( $parser, $text)
static nse( $parser, $part1='')
static pagesize( $parser, $page='', $raw=null)
Return the size of the given page, or 0 if it's nonexistent.
static canonicalurl( $parser, $s='', $arg=null)
static basepagenamee( $parser, $title=null)
static subjectspacee( $parser, $title=null)
static numberofedits( $parser, $raw=null)
static numberoffiles( $parser, $raw=null)
static ucfirst( $parser, $s='')
static basepagename( $parser, $title=null)
static gender( $parser, $username,... $forms)
static numberofpages( $parser, $raw=null)
static lcfirst( $parser, $s='')
static urlFunction( $func, $s='', $arg=null)
static plural( $parser, $text='',... $forms)
static formatRaw( $num, $raw, $language, MagicWordFactory $magicWordFactory=null)
Formats a number according to a language.
static talkspacee( $parser, $title=null)
static bidi( $parser, $text='')
static revisionuser( $parser, $title=null)
Get the user from the last revision of a specified page.
static anchorencode( $parser, $text)
static subjectpagenamee( $parser, $title=null)
static namespacee( $parser, $title=null)
static subjectpagename( $parser, $title=null)
static filepath( $parser, $name='', $argA='', $argB='')
Usage {{filepath|300}}, {{filepath|nowiki}}, {{filepath|nowiki|300}} or {{filepath|300|nowiki}} or {{...
static padright( $parser, $string='', $length=0, $padding='0')
static pad( $parser, $string, $length, $padding='0', $direction=STR_PAD_RIGHT)
Unicode-safe str_pad with the restriction that $length is forced to be <= 500.
static canonicalurle( $parser, $s='', $arg=null)
static cascadingsources( $parser, $title='')
Returns the sources of any cascading protection acting on a specified page.
static numberofactiveusers( $parser, $raw=null)
static displaytitle( $parser, $text='', $uarg='')
Override the title of the page when viewed, provided we've been given a title which will normalise to...
static pageid( $parser, $title=null)
Get the pageid of a specified page.
static formatDate( $parser, $date, $defaultPref=null)
static urlencode( $parser, $s='', $arg=null)
urlencodes a string according to one of three patterns: (T24474)
static grammar( $parser, $case='', $word='')
static subpagenamee( $parser, $title=null)
static tagObj( $parser, $frame, $args)
Parser function to extension tag adaptor.
static revisionmonth1( $parser, $title=null)
Get the month from the last revision of a specified page.
static rootpagenamee( $parser, $title=null)
static protectionlevel( $parser, $type='', $title='')
Returns the requested protection level for the current page.
static namespacenumber( $parser, $title=null)
static revisionday( $parser, $title=null)
Get the day from the last revision of a specified page.
static subjectspace( $parser, $title=null)
static lc( $parser, $s='')
static pagesincategory( $parser, $name='', $arg1='', $arg2='')
Return the number of pages, files or subcats in the given category, or 0 if it's nonexistent.
static talkpagename( $parser, $title=null)
static fullurl( $parser, $s='', $arg=null)
static mwnamespace( $parser, $title=null)
Given a title, return the namespace name that would be given by the corresponding magic word.
static pagename( $parser, $title=null)
Functions to get and normalize pagenames, corresponding to the magic words of the same names.
static intFunction( $parser, $part1='',... $params)
static numberofadmins( $parser, $raw=null)
static numberofarticles( $parser, $raw=null)
static revisionyear( $parser, $title=null)
Get the year from the last revision of a specified page.
static revisionday2( $parser, $title=null)
Get the day with leading zeros from the last revision of a specified page.
static uc( $parser, $s='')
static talkpagenamee( $parser, $title=null)
static revisiontimestamp( $parser, $title=null)
Get the timestamp from the last revision of a specified page.
static subpagename( $parser, $title=null)
static localurle( $parser, $s='', $arg=null)
static defaultsort( $parser, $text, $uarg='')
static fullpagenamee( $parser, $title=null)
static talkspace( $parser, $title=null)
static speciale( $parser, $text)
static bcp47( $code)
Get the normalised IANA language tag See unit test for examples.
Category objects are immutable, strictly speaking.
Definition: Category.php:41
A class for passing options to services.
A class containing constants representing the names of configuration variables.
Service locator for MediaWiki core services.
A factory that stores information about MagicWords, and creates them on demand with caching.
get( $id)
Factory: creates an object representing an ID.
Exception representing a failure to look up a revision.
Page revision base class.
Static accessor class for site_stats and related things.
Definition: SiteStats.php:36
Stub object for the user language.
Represents a title within MediaWiki.
Definition: Title.php:82
static plaintextParam( $plaintext)
Definition: Message.php:1267
PHP Parser - Processes wiki markup (which uses a more user-friendly syntax, such as "[[link]]" for ma...
Definition: Parser.php:107
addTrackingCategory( $msg)
Definition: Parser.php:4127
getTargetLanguage()
Get the target language for the content being parsed.
Definition: Parser.php:1163
getRevisionTimestamp()
Get the timestamp associated with the current revision, adjusted for the default server-local timesta...
Definition: Parser.php:6044
getMagicWordFactory()
Get the MagicWordFactory that this Parser is using.
Definition: Parser.php:1217
getOptions()
Definition: Parser.php:1103
getRevisionUser()
Get the name of the user that edited the last revision.
Definition: Parser.php:6072
isCurrentRevisionOfTitleCached(LinkTarget $link)
Definition: Parser.php:3538
extensionSubstitution(array $params, PPFrame $frame, bool $processNowiki=false)
Return the text to be used for a given extension tag.
Definition: Parser.php:3952
tagNeedsNowikiStrippedInTagPF(string $lowerTagName)
Definition: Parser.php:3926
fetchCurrentRevisionRecordOfTitle(LinkTarget $link)
Fetch the current revision of a given title as a RevisionRecord.
Definition: Parser.php:3508
setFunctionHook( $id, callable $callback, $flags=0)
Create a function, e.g.
Definition: Parser.php:4971
getTitle()
Definition: Parser.php:1014
getStripList()
Get a list of strippable XML-like elements.
Definition: Parser.php:1323
getTargetLanguageConverter()
Shorthand for getting a Language Converter for Target language.
Definition: Parser.php:1636
getContentLanguage()
Get the content language that this Parser is using.
Definition: Parser.php:1227
getOutput()
Definition: Parser.php:1095
guessSectionNameFromWikiText( $text)
Try to guess the section anchor name based on a wikitext fragment presumably extracted from a heading...
Definition: Parser.php:6189
getRevisionId()
Get the ID of the revision we are parsing.
Definition: Parser.php:5979
static parseWidthParam( $value, $parseHeight=true)
Parsed a width param of imagelink like 300px or 200x300px.
Definition: Parser.php:6397
const SFH_OBJECT_ARGS
Definition: Parser.php:111
doQuotes( $text)
Helper function for handleAllQuotes()
Definition: Parser.php:1975
incrementExpensiveFunctionCount()
Definition: Parser.php:4058
markerSkipCallback( $s, callable $callback)
Call a callback function on all regions of the given text that are not inside strip markers,...
Definition: Parser.php:6352
killMarkers( $text)
Remove any strip markers found in the given text.
Definition: Parser.php:6383
msg(string $msg,... $args)
Helper function to correctly set the target language and title of a message based on the parser conte...
Definition: Parser.php:4146
getRevisionRecordObject()
Get the revision record object for $this->mRevisionId.
Definition: Parser.php:5989
const SFH_NO_HASH
Definition: Parser.php:110
static checkCss( $value)
Pick apart some CSS and check it for forbidden or unsafe structures.
Definition: Sanitizer.php:767
static removeSomeTags(string $text, array $options=[])
Cleans up HTML, removes dangerous tags and attributes, and removes HTML comments; the result will alw...
Definition: Sanitizer.php:393
static stripAllTags( $html)
Take a fragment of (potentially invalid) HTML and return a version with any tags removed,...
Definition: Sanitizer.php:1727
static decodeCharReferencesAndNormalize( $text)
Decode any character references, numeric or named entities, in the next and normalize the resulting s...
Definition: Sanitizer.php:1389
static safeEncodeAttribute( $text)
Encode an attribute value for HTML tags, with extra armoring against further wiki processing.
Definition: Sanitizer.php:901
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
static newFromName( $name, $validate='valid')
Definition: User.php:592
const PROCESS_NOWIKI
Definition: PPFrame.php:37
const STRIP_COMMENTS
Definition: PPFrame.php:33
return true
Definition: router.php:90
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition: router.php:42
if(!isset( $args[0])) $lang