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