MediaWiki master
CoreParserFunctions.php
Go to the documentation of this file.
1<?php
24namespace MediaWiki\Parser;
25
26use InvalidArgumentException;
43use Wikimedia\Bcp47Code\Bcp47CodeValue;
44use Wikimedia\RemexHtml\Tokenizer\Attributes;
45use Wikimedia\RemexHtml\Tokenizer\PlainAttributes;
46
53 private const MAX_TTS = 900;
54
58 public const REGISTER_OPTIONS = [
59 // See documentation for the corresponding config options
62 ];
63
71 public static function register( Parser $parser, ServiceOptions $options ) {
72 $options->assertRequiredOptions( self::REGISTER_OPTIONS );
73 $allowDisplayTitle = $options->get( MainConfigNames::AllowDisplayTitle );
74 $allowSlowParserFunctions = $options->get( MainConfigNames::AllowSlowParserFunctions );
75
76 # Syntax for arguments (see Parser::setFunctionHook):
77 # "name for lookup in localized magic words array",
78 # function callback,
79 # optional Parser::SFH_NO_HASH to omit the hash from calls (e.g. {{int:...}}
80 # instead of {{#int:...}})
81 $noHashFunctions = [
82 'ns', 'nse', 'urlencode', 'lcfirst', 'ucfirst', 'lc', 'uc',
83 'localurl', 'localurle', 'fullurl', 'fullurle', 'canonicalurl',
84 'canonicalurle', 'formatnum', 'grammar', 'gender', 'plural', 'formal',
85 'bidi', 'numberingroup', 'language',
86 'padleft', 'padright', 'anchorencode', 'defaultsort', 'filepath',
87 'pagesincategory', 'pagesize', 'protectionlevel', 'protectionexpiry',
88 # The following are the "parser function" forms of magic
89 # variables defined in CoreMagicVariables. The no-args form will
90 # go through the magic variable code path (and be cached); the
91 # presence of arguments will cause the parser function form to
92 # be invoked. (Note that the actual implementation will pass
93 # a Parser object as first argument, in addition to the
94 # parser function parameters.)
95
96 # For this group, the first parameter to the parser function is
97 # "page title", and the no-args form (and the magic variable)
98 # defaults to "current page title".
99 'pagename', 'pagenamee',
100 'fullpagename', 'fullpagenamee',
101 'subpagename', 'subpagenamee',
102 'rootpagename', 'rootpagenamee',
103 'basepagename', 'basepagenamee',
104 'talkpagename', 'talkpagenamee',
105 'subjectpagename', 'subjectpagenamee',
106 'pageid', 'revisionid', 'revisionday',
107 'revisionday2', 'revisionmonth', 'revisionmonth1', 'revisionyear',
108 'revisiontimestamp',
109 'revisionuser',
110 'cascadingsources',
111 'namespace', 'namespacee', 'namespacenumber', 'talkspace', 'talkspacee',
112 'subjectspace', 'subjectspacee',
113
114 # More parser functions corresponding to CoreMagicVariables.
115 # For this group, the first parameter to the parser function is
116 # "raw" (uses the 'raw' format if present) and the no-args form
117 # (and the magic variable) defaults to 'not raw'.
118 'numberofarticles', 'numberoffiles',
119 'numberofusers',
120 'numberofactiveusers',
121 'numberofpages',
122 'numberofadmins',
123 'numberofedits',
124
125 # These magic words already contain the hash, and the no-args form
126 # is the same as passing an empty first argument
127 'bcp47',
128 'dir',
129 'interwikilink',
130 'interlanguagelink',
131 ];
132 foreach ( $noHashFunctions as $func ) {
133 $parser->setFunctionHook( $func, [ __CLASS__, $func ], Parser::SFH_NO_HASH );
134 }
135
136 $parser->setFunctionHook( 'int', [ __CLASS__, 'intFunction' ], Parser::SFH_NO_HASH );
137 $parser->setFunctionHook( 'special', [ __CLASS__, 'special' ] );
138 $parser->setFunctionHook( 'speciale', [ __CLASS__, 'speciale' ] );
139 $parser->setFunctionHook( 'tag', [ __CLASS__, 'tagObj' ], Parser::SFH_OBJECT_ARGS );
140 $parser->setFunctionHook( 'formatdate', [ __CLASS__, 'formatDate' ] );
141
142 if ( $allowDisplayTitle ) {
143 $parser->setFunctionHook(
144 'displaytitle',
145 [ __CLASS__, 'displaytitle' ],
147 );
148 }
149 if ( $allowSlowParserFunctions ) {
150 $parser->setFunctionHook(
151 'pagesinnamespace',
152 [ __CLASS__, 'pagesinnamespace' ],
154 );
155 }
156 }
157
164 public static function intFunction( $parser, $part1 = '', ...$params ) {
165 if ( strval( $part1 ) !== '' ) {
166 $message = wfMessage( $part1, $params )
167 ->inLanguage( $parser->getOptions()->getUserLangObj() );
168 return [ $message->plain(), 'noparse' => false ];
169 } else {
170 return [ 'found' => false ];
171 }
172 }
173
181 public static function formatDate( $parser, $date, $defaultPref = null ) {
182 $lang = $parser->getTargetLanguage();
183 $df = MediaWikiServices::getInstance()->getDateFormatterFactory()->get( $lang );
184
185 $date = trim( $date );
186
187 $pref = $parser->getOptions()->getDateFormat();
188
189 // Specify a different default date format other than the normal default
190 // if the user has 'default' for their setting
191 if ( $pref == 'default' && $defaultPref ) {
192 $pref = $defaultPref;
193 }
194
195 $date = $df->reformat( $pref, $date, [ 'match-whole' ] );
196 return $date;
197 }
198
199 public static function ns( $parser, $part1 = '' ) {
200 if ( intval( $part1 ) || $part1 == "0" ) {
201 $index = intval( $part1 );
202 } else {
203 $index = $parser->getContentLanguage()->getNsIndex( str_replace( ' ', '_', $part1 ) );
204 }
205 if ( $index !== false ) {
206 return $parser->getContentLanguage()->getFormattedNsText( $index );
207 } else {
208 return [ 'found' => false ];
209 }
210 }
211
212 public static function nse( $parser, $part1 = '' ) {
213 $ret = self::ns( $parser, $part1 );
214 if ( is_string( $ret ) ) {
215 $ret = wfUrlencode( str_replace( ' ', '_', $ret ) );
216 }
217 return $ret;
218 }
219
232 public static function urlencode( $parser, $s = '', $arg = null ) {
233 static $magicWords = null;
234 if ( $magicWords === null ) {
236 $parser->getMagicWordFactory()->newArray( [ 'url_path', 'url_query', 'url_wiki' ] );
237 }
238 switch ( $magicWords->matchStartToEnd( $arg ?? '' ) ) {
239 // Encode as though it's a wiki page, '_' for ' '.
240 case 'url_wiki':
241 $func = 'wfUrlencode';
242 $s = str_replace( ' ', '_', $s );
243 break;
244
245 // Encode for an HTTP Path, '%20' for ' '.
246 case 'url_path':
247 $func = 'rawurlencode';
248 break;
249
250 // Encode for HTTP query, '+' for ' '.
251 case 'url_query':
252 default:
253 $func = 'urlencode';
254 }
255 // See T105242, where the choice to kill markers and various
256 // other options were discussed.
257 return $func( $parser->killMarkers( $s ) );
258 }
259
260 public static function lcfirst( $parser, $s = '' ) {
261 return $parser->getContentLanguage()->lcfirst( $s );
262 }
263
264 public static function ucfirst( $parser, $s = '' ) {
265 return $parser->getContentLanguage()->ucfirst( $s );
266 }
267
273 public static function lc( $parser, $s = '' ) {
274 return $parser->markerSkipCallback( $s, [ $parser->getContentLanguage(), 'lc' ] );
275 }
276
282 public static function uc( $parser, $s = '' ) {
283 return $parser->markerSkipCallback( $s, [ $parser->getContentLanguage(), 'uc' ] );
284 }
285
286 public static function localurl( $parser, $s = '', $arg = null ) {
287 return self::urlFunction( 'getLocalURL', $s, $arg );
288 }
289
290 public static function localurle( $parser, $s = '', $arg = null ) {
291 $temp = self::urlFunction( 'getLocalURL', $s, $arg );
292 if ( !is_string( $temp ) ) {
293 return $temp;
294 } else {
295 return htmlspecialchars( $temp, ENT_COMPAT );
296 }
297 }
298
299 public static function fullurl( $parser, $s = '', $arg = null ) {
300 return self::urlFunction( 'getFullURL', $s, $arg );
301 }
302
303 public static function fullurle( $parser, $s = '', $arg = null ) {
304 $temp = self::urlFunction( 'getFullURL', $s, $arg );
305 if ( !is_string( $temp ) ) {
306 return $temp;
307 } else {
308 return htmlspecialchars( $temp, ENT_COMPAT );
309 }
310 }
311
312 public static function canonicalurl( $parser, $s = '', $arg = null ) {
313 return self::urlFunction( 'getCanonicalURL', $s, $arg );
314 }
315
316 public static function canonicalurle( $parser, $s = '', $arg = null ) {
317 $temp = self::urlFunction( 'getCanonicalURL', $s, $arg );
318 if ( !is_string( $temp ) ) {
319 return $temp;
320 } else {
321 return htmlspecialchars( $temp, ENT_COMPAT );
322 }
323 }
324
325 public static function urlFunction( $func, $s = '', $arg = null ) {
326 # Due to order of execution of a lot of bits, the values might be encoded
327 # before arriving here; if that's true, then the title can't be created
328 # and the variable will fail. If we can't get a decent title from the first
329 # attempt, url-decode and try for a second.
330 $title = Title::newFromText( $s ) ?? Title::newFromURL( urldecode( $s ) );
331 if ( $title !== null ) {
332 # Convert NS_MEDIA -> NS_FILE
333 if ( $title->inNamespace( NS_MEDIA ) ) {
334 $title = Title::makeTitle( NS_FILE, $title->getDBkey() );
335 }
336 if ( $arg !== null ) {
337 $text = $title->$func( $arg );
338 } else {
339 $text = $title->$func();
340 }
341 return $text;
342 } else {
343 return [ 'found' => false ];
344 }
345 }
346
353 public static function formatnum( $parser, $num = '', $arg = null ) {
354 if ( self::matchAgainstMagicword( $parser->getMagicWordFactory(), 'rawsuffix', $arg ) ) {
355 $func = [ $parser->getTargetLanguage(), 'parseFormattedNumber' ];
356 } elseif (
357 self::matchAgainstMagicword( $parser->getMagicWordFactory(), 'nocommafysuffix', $arg )
358 ) {
359 $func = [ $parser->getTargetLanguage(), 'formatNumNoSeparators' ];
360 $func = self::getLegacyFormatNum( $parser, $func );
361 } else {
362 $func = [ $parser->getTargetLanguage(), 'formatNum' ];
363 $func = self::getLegacyFormatNum( $parser, $func );
364 }
365 return $parser->markerSkipCallback( $num, $func );
366 }
367
374 private static function getLegacyFormatNum( $parser, $callback ) {
375 // For historic reasons, the formatNum parser function will
376 // take arguments which are not actually formatted numbers,
377 // which then trigger deprecation warnings in Language::formatNum*.
378 // Instead emit a tracking category instead to allow linting.
379 return static function ( $number ) use ( $parser, $callback ) {
380 $validNumberRe = '(-(?=[\d\.]))?(\d+|(?=\.\d))(\.\d*)?([Ee][-+]?\d+)?';
381 if (
382 !is_numeric( $number ) &&
383 $number !== (string)NAN &&
384 $number !== (string)INF &&
385 $number !== (string)-INF
386 ) {
387 $parser->addTrackingCategory( 'nonnumeric-formatnum' );
388 // Don't split on NAN/INF in the legacy case since they are
389 // likely to be found embedded inside non-numeric text.
390 return preg_replace_callback( "/{$validNumberRe}/", static function ( $m ) use ( $callback ) {
391 return call_user_func( $callback, $m[0] );
392 }, $number );
393 }
394 return call_user_func( $callback, $number );
395 };
396 }
397
404 public static function grammar( $parser, $case = '', $word = '' ) {
405 $word = $parser->killMarkers( $word );
406 return $parser->getTargetLanguage()->convertGrammar( $word, $case );
407 }
408
415 public static function gender( $parser, $username, ...$forms ) {
416 // Some shortcuts to avoid loading user data unnecessarily
417 if ( count( $forms ) === 0 ) {
418 return '';
419 } elseif ( count( $forms ) === 1 ) {
420 return $forms[0];
421 }
422
423 $username = trim( $username );
424
425 $userOptionsLookup = MediaWikiServices::getInstance()->getUserOptionsLookup();
426 $gender = $userOptionsLookup->getDefaultOption( 'gender' );
427
428 // allow prefix and normalize (e.g. "&#42;foo" -> "*foo" ).
429 $title = Title::newFromText( $username, NS_USER );
430
431 if ( $title && $title->inNamespace( NS_USER ) ) {
432 $username = $title->getText();
433 }
434
435 // check parameter, or use the ParserOptions if in interface message
436 $user = User::newFromName( $username );
437 $genderCache = MediaWikiServices::getInstance()->getGenderCache();
438 if ( $user ) {
439 $gender = $genderCache->getGenderOf( $user, __METHOD__ );
440 } elseif ( $username === '' && $parser->getOptions()->getInterfaceMessage() ) {
441 $gender = $genderCache->getGenderOf( $parser->getOptions()->getUserIdentity(), __METHOD__ );
442 }
443 $ret = $parser->getTargetLanguage()->gender( $gender, $forms );
444 return $ret;
445 }
446
453 public static function plural( $parser, $text = '', ...$forms ) {
454 $text = $parser->getTargetLanguage()->parseFormattedNumber( $text );
455 settype( $text, ctype_digit( $text ) ? 'int' : 'float' );
456 // @phan-suppress-next-line PhanTypeMismatchArgument Phan does not handle settype
457 return $parser->getTargetLanguage()->convertPlural( $text, $forms );
458 }
459
460 public static function formal( Parser $parser, string ...$forms ): string {
461 $index = $parser->getTargetLanguage()->getFormalityIndex();
462 return $forms[$index] ?? $forms[0];
463 }
464
470 public static function bidi( $parser, $text = '' ) {
471 return $parser->getTargetLanguage()->embedBidi( $text );
472 }
473
483 public static function displaytitle( $parser, $text = '', $uarg = '' ) {
484 $restrictDisplayTitle = MediaWikiServices::getInstance()->getMainConfig()
486
487 static $magicWords = null;
488 if ( $magicWords === null ) {
489 $magicWords = $parser->getMagicWordFactory()->newArray(
490 [ 'displaytitle_noerror', 'displaytitle_noreplace' ] );
491 }
492 $arg = $magicWords->matchStartToEnd( $uarg );
493
494 // parse a limited subset of wiki markup (just the single quote items)
495 $text = $parser->doQuotes( $text );
496
497 // remove stripped text (e.g. the UNIQ-QINU stuff) that was generated by tag extensions/whatever
498 $text = $parser->killMarkers( $text );
499
500 // See T28547 for rationale for this processing.
501 // list of disallowed tags for DISPLAYTITLE
502 // these will be escaped even though they are allowed in normal wiki text
503 $bad = [ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'div', 'blockquote', 'ol', 'ul', 'li', 'hr',
504 'table', 'tr', 'th', 'td', 'dl', 'dd', 'caption', 'p', 'ruby', 'rb', 'rt', 'rtc', 'rp', 'br' ];
505
506 // disallow some styles that could be used to bypass $wgRestrictDisplayTitle
507 if ( $restrictDisplayTitle ) {
508 // This code is tested with the cases marked T28547 in
509 // parserTests.txt
510 $htmlTagsCallback = static function ( Attributes $attr ): Attributes {
511 $decoded = $attr->getValues();
512
513 if ( isset( $decoded['style'] ) ) {
514 // this is called later anyway, but we need it right now for the regexes below to be safe
515 // calling it twice doesn't hurt
516 $decoded['style'] = Sanitizer::checkCss( $decoded['style'] );
517
518 if ( preg_match( '/(display|user-select|visibility)\s*:/i', $decoded['style'] ) ) {
519 $decoded['style'] = '/* attempt to bypass $wgRestrictDisplayTitle */';
520 }
521 }
522
523 return new PlainAttributes( $decoded );
524 };
525 } else {
526 $htmlTagsCallback = null;
527 }
528
529 // only requested titles that normalize to the actual title are allowed through
530 // if $wgRestrictDisplayTitle is true (it is by default)
531 // mimic the escaping process that occurs in OutputPage::setPageTitle
532 $text = Sanitizer::removeSomeTags( $text, [
533 'attrCallback' => $htmlTagsCallback,
534 'removeTags' => $bad,
535 ] );
536 $title = Title::newFromText( Sanitizer::stripAllTags( $text ) );
537 // Decode entities in $text the same way that Title::newFromText does
538 $filteredText = Sanitizer::decodeCharReferencesAndNormalize( $text );
539
540 if ( !$restrictDisplayTitle ||
541 ( $title instanceof Title
542 && !$title->hasFragment()
543 && $title->equals( $parser->getTitle() ) )
544 ) {
545 $old = $parser->getOutput()->getPageProperty( 'displaytitle' );
546 if ( $old === null || $arg !== 'displaytitle_noreplace' ) {
547 $parser->getOutput()->setDisplayTitle( $text );
548 }
549 if ( $old !== null && $old !== $text && !$arg ) {
550
551 $converter = $parser->getTargetLanguageConverter();
552 return '<span class="error">' .
553 $parser->msg( 'duplicate-displaytitle',
554 // Message should be parsed, but these params should only be escaped.
555 $converter->markNoConversion( wfEscapeWikiText( $old ) ),
556 $converter->markNoConversion( wfEscapeWikiText( $filteredText ) )
557 )->text() .
558 '</span>';
559 } else {
560 return '';
561 }
562 } else {
563 $parser->getOutput()->addWarningMsg(
564 'restricted-displaytitle',
565 // Message should be parsed, but this param should only be escaped.
566 Message::plaintextParam( $filteredText )
567 );
568 $parser->addTrackingCategory( 'restricted-displaytitle-ignored' );
569 }
570 }
571
581 private static function matchAgainstMagicword(
582 MagicWordFactory $magicWordFactory, $magicword, $value
583 ) {
584 $value = trim( strval( $value ) );
585 if ( $value === '' ) {
586 return false;
587 }
588 $mwObject = $magicWordFactory->get( $magicword );
589 return $mwObject->matchStartToEnd( $value );
590 }
591
601 public static function formatRaw(
602 $num, $raw, $language, ?MagicWordFactory $magicWordFactory = null
603 ) {
604 if ( $raw !== null && $raw !== '' ) {
605 if ( !$magicWordFactory ) {
606 $magicWordFactory = MediaWikiServices::getInstance()->getMagicWordFactory();
607 }
608 if ( self::matchAgainstMagicword( $magicWordFactory, 'rawsuffix', $raw ) ) {
609 return (string)$num;
610 }
611 }
612 return $language->formatNum( $num );
613 }
614
615 public static function numberofpages( $parser, $raw = null ) {
616 return self::formatRaw( SiteStats::pages(), $raw, $parser->getTargetLanguage() );
617 }
618
619 public static function numberofusers( $parser, $raw = null ) {
620 return self::formatRaw( SiteStats::users(), $raw, $parser->getTargetLanguage() );
621 }
622
623 public static function numberofactiveusers( $parser, $raw = null ) {
624 return self::formatRaw( SiteStats::activeUsers(), $raw, $parser->getTargetLanguage() );
625 }
626
627 public static function numberofarticles( $parser, $raw = null ) {
628 return self::formatRaw( SiteStats::articles(), $raw, $parser->getTargetLanguage() );
629 }
630
631 public static function numberoffiles( $parser, $raw = null ) {
632 return self::formatRaw( SiteStats::images(), $raw, $parser->getTargetLanguage() );
633 }
634
635 public static function numberofadmins( $parser, $raw = null ) {
636 return self::formatRaw(
637 SiteStats::numberingroup( 'sysop' ),
638 $raw,
639 $parser->getTargetLanguage()
640 );
641 }
642
643 public static function numberofedits( $parser, $raw = null ) {
644 return self::formatRaw( SiteStats::edits(), $raw, $parser->getTargetLanguage() );
645 }
646
647 public static function pagesinnamespace( $parser, $namespace = 0, $raw = null ) {
648 return self::formatRaw(
649 SiteStats::pagesInNs( intval( $namespace ) ),
650 $raw,
651 $parser->getTargetLanguage()
652 );
653 }
654
655 public static function numberingroup( $parser, $name = '', $raw = null ) {
656 return self::formatRaw(
657 SiteStats::numberingroup( strtolower( $name ) ),
658 $raw,
659 $parser->getTargetLanguage()
660 );
661 }
662
671 private static function makeTitle( Parser $parser, ?string $t ) {
672 if ( $t === null ) {
673 // For consistency with magic variable forms
674 $title = $parser->getTitle();
675 } else {
676 $title = Title::newFromText( $t );
677 }
678 return $title;
679 }
680
689 public static function namespace( $parser, $title = null ) {
690 $t = self::makeTitle( $parser, $title );
691 if ( $t === null ) {
692 return '';
693 }
694 return str_replace( '_', ' ', $t->getNsText() );
695 }
696
697 public static function namespacee( $parser, $title = null ) {
698 $t = self::makeTitle( $parser, $title );
699 if ( $t === null ) {
700 return '';
701 }
702 return wfUrlencode( $t->getNsText() );
703 }
704
705 public static function namespacenumber( $parser, $title = null ) {
706 $t = self::makeTitle( $parser, $title );
707 if ( $t === null ) {
708 return '';
709 }
710 return (string)$t->getNamespace();
711 }
712
713 public static function talkspace( $parser, $title = null ) {
714 $t = self::makeTitle( $parser, $title );
715 if ( $t === null || !$t->canHaveTalkPage() ) {
716 return '';
717 }
718 return str_replace( '_', ' ', $t->getTalkNsText() );
719 }
720
721 public static function talkspacee( $parser, $title = null ) {
722 $t = self::makeTitle( $parser, $title );
723 if ( $t === null || !$t->canHaveTalkPage() ) {
724 return '';
725 }
726 return wfUrlencode( $t->getTalkNsText() );
727 }
728
729 public static function subjectspace( $parser, $title = null ) {
730 $t = self::makeTitle( $parser, $title );
731 if ( $t === null ) {
732 return '';
733 }
734 return str_replace( '_', ' ', $t->getSubjectNsText() );
735 }
736
737 public static function subjectspacee( $parser, $title = null ) {
738 $t = self::makeTitle( $parser, $title );
739 if ( $t === null ) {
740 return '';
741 }
742 return wfUrlencode( $t->getSubjectNsText() );
743 }
744
752 public static function pagename( $parser, $title = null ) {
753 $t = self::makeTitle( $parser, $title );
754 if ( $t === null ) {
755 return '';
756 }
757 return wfEscapeWikiText( $t->getText() );
758 }
759
760 public static function pagenamee( $parser, $title = null ) {
761 $t = self::makeTitle( $parser, $title );
762 if ( $t === null ) {
763 return '';
764 }
765 return wfEscapeWikiText( $t->getPartialURL() );
766 }
767
768 public static function fullpagename( $parser, $title = null ) {
769 $t = self::makeTitle( $parser, $title );
770 if ( $t === null ) {
771 return '';
772 }
773 return wfEscapeWikiText( $t->getPrefixedText() );
774 }
775
776 public static function fullpagenamee( $parser, $title = null ) {
777 $t = self::makeTitle( $parser, $title );
778 if ( $t === null ) {
779 return '';
780 }
781 return wfEscapeWikiText( $t->getPrefixedURL() );
782 }
783
784 public static function subpagename( $parser, $title = null ) {
785 $t = self::makeTitle( $parser, $title );
786 if ( $t === null ) {
787 return '';
788 }
789 return wfEscapeWikiText( $t->getSubpageText() );
790 }
791
792 public static function subpagenamee( $parser, $title = null ) {
793 $t = self::makeTitle( $parser, $title );
794 if ( $t === null ) {
795 return '';
796 }
797 return wfEscapeWikiText( $t->getSubpageUrlForm() );
798 }
799
800 public static function rootpagename( $parser, $title = null ) {
801 $t = self::makeTitle( $parser, $title );
802 if ( $t === null ) {
803 return '';
804 }
805 return wfEscapeWikiText( $t->getRootText() );
806 }
807
808 public static function rootpagenamee( $parser, $title = null ) {
809 $t = self::makeTitle( $parser, $title );
810 if ( $t === null ) {
811 return '';
812 }
813 return wfEscapeWikiText( wfUrlencode( str_replace( ' ', '_', $t->getRootText() ) ) );
814 }
815
816 public static function basepagename( $parser, $title = null ) {
817 $t = self::makeTitle( $parser, $title );
818 if ( $t === null ) {
819 return '';
820 }
821 return wfEscapeWikiText( $t->getBaseText() );
822 }
823
824 public static function basepagenamee( $parser, $title = null ) {
825 $t = self::makeTitle( $parser, $title );
826 if ( $t === null ) {
827 return '';
828 }
829 return wfEscapeWikiText( wfUrlencode( str_replace( ' ', '_', $t->getBaseText() ) ) );
830 }
831
832 public static function talkpagename( $parser, $title = null ) {
833 $t = self::makeTitle( $parser, $title );
834 if ( $t === null || !$t->canHaveTalkPage() ) {
835 return '';
836 }
837 return wfEscapeWikiText( $t->getTalkPage()->getPrefixedText() );
838 }
839
840 public static function talkpagenamee( $parser, $title = null ) {
841 $t = self::makeTitle( $parser, $title );
842 if ( $t === null || !$t->canHaveTalkPage() ) {
843 return '';
844 }
845 return wfEscapeWikiText( $t->getTalkPage()->getPrefixedURL() );
846 }
847
848 public static function subjectpagename( $parser, $title = null ) {
849 $t = self::makeTitle( $parser, $title );
850 if ( $t === null ) {
851 return '';
852 }
853 return wfEscapeWikiText( $t->getSubjectPage()->getPrefixedText() );
854 }
855
856 public static function subjectpagenamee( $parser, $title = null ) {
857 $t = self::makeTitle( $parser, $title );
858 if ( $t === null ) {
859 return '';
860 }
861 return wfEscapeWikiText( $t->getSubjectPage()->getPrefixedURL() );
862 }
863
874 public static function pagesincategory( $parser, $name = '', $arg1 = '', $arg2 = '' ) {
875 static $magicWords = null;
876 if ( $magicWords === null ) {
877 $magicWords = $parser->getMagicWordFactory()->newArray( [
878 'pagesincategory_all',
879 'pagesincategory_pages',
880 'pagesincategory_subcats',
881 'pagesincategory_files'
882 ] );
883 }
884 static $cache = [];
885
886 // split the given option to its variable
887 if ( self::matchAgainstMagicword( $parser->getMagicWordFactory(), 'rawsuffix', $arg1 ) ) {
888 // {{pagesincategory:|raw[|type]}}
889 $raw = $arg1;
890 $type = $magicWords->matchStartToEnd( $arg2 );
891 } else {
892 // {{pagesincategory:[|type[|raw]]}}
893 $type = $magicWords->matchStartToEnd( $arg1 );
894 $raw = $arg2;
895 }
896 if ( !$type ) { // backward compatibility
897 $type = 'pagesincategory_all';
898 }
899
900 $title = Title::makeTitleSafe( NS_CATEGORY, $name );
901 if ( !$title ) { # invalid title
902 return self::formatRaw( 0, $raw, $parser->getTargetLanguage() );
903 }
904 $languageConverter = MediaWikiServices::getInstance()
905 ->getLanguageConverterFactory()
906 ->getLanguageConverter( $parser->getContentLanguage() );
907 $languageConverter->findVariantLink( $name, $title, true );
908
909 // Normalize name for cache
910 $name = $title->getDBkey();
911
912 if ( !isset( $cache[$name] ) ) {
913 $category = Category::newFromTitle( $title );
914
915 $allCount = $subcatCount = $fileCount = $pageCount = 0;
916 if ( $parser->incrementExpensiveFunctionCount() ) {
917 $allCount = $category->getMemberCount();
918 $subcatCount = $category->getSubcatCount();
919 $fileCount = $category->getFileCount();
920 $pageCount = $category->getPageCount( Category::COUNT_CONTENT_PAGES );
921 }
922 $cache[$name]['pagesincategory_all'] = $allCount;
923 $cache[$name]['pagesincategory_pages'] = $pageCount;
924 $cache[$name]['pagesincategory_subcats'] = $subcatCount;
925 $cache[$name]['pagesincategory_files'] = $fileCount;
926 }
927
928 $count = $cache[$name][$type];
929 return self::formatRaw( $count, $raw, $parser->getTargetLanguage() );
930 }
931
941 public static function pagesize( $parser, $page = '', $raw = null ) {
942 $title = Title::newFromText( $page );
943
944 if ( !is_object( $title ) || $title->isExternal() ) {
945 return self::formatRaw( 0, $raw, $parser->getTargetLanguage() );
946 }
947
948 // fetch revision from cache/database and return the value
949 $rev = self::getCachedRevisionObject( $parser, $title, ParserOutputFlags::VARY_REVISION_SHA1 );
950 $length = $rev ? $rev->getSize() : 0;
951 if ( $length === null ) {
952 // We've had bugs where rev_len was not being recorded for empty pages, see T135414
953 $length = 0;
954 }
955 return self::formatRaw( $length, $raw, $parser->getTargetLanguage() );
956 }
957
970 public static function protectionlevel( $parser, $type = '', $title = '' ) {
971 $titleObject = Title::newFromText( $title ) ?? $parser->getTitle();
972 $restrictionStore = MediaWikiServices::getInstance()->getRestrictionStore();
973 if ( $restrictionStore->areRestrictionsLoaded( $titleObject ) || $parser->incrementExpensiveFunctionCount() ) {
974 $restrictions = $restrictionStore->getRestrictions( $titleObject, strtolower( $type ) );
975 # RestrictionStore::getRestrictions returns an array, its possible it may have
976 # multiple values in the future
977 return implode( ',', $restrictions );
978 }
979 return '';
980 }
981
994 public static function protectionexpiry( $parser, $type = '', $title = '' ) {
995 $titleObject = Title::newFromText( $title ) ?? $parser->getTitle();
996 $restrictionStore = MediaWikiServices::getInstance()->getRestrictionStore();
997 if ( $restrictionStore->areRestrictionsLoaded( $titleObject ) || $parser->incrementExpensiveFunctionCount() ) {
998 // getRestrictionExpiry() returns null on invalid type; trying to
999 // match protectionlevel() function that returns empty string instead
1000 return $restrictionStore->getRestrictionExpiry( $titleObject, strtolower( $type ) ) ?? '';
1001 }
1002 return '';
1003 }
1004
1013 public static function language( $parser, $code = '', $inLanguage = '' ) {
1014 if ( $code === '' ) {
1015 $code = $parser->getTargetLanguage()->getCode();
1016 }
1017 if ( $inLanguage === '' ) {
1018 $inLanguage = LanguageNameUtils::AUTONYMS;
1019 }
1021 ->getLanguageNameUtils()
1022 ->getLanguageName( $code, $inLanguage );
1023 return $lang !== '' ? $lang : LanguageCode::bcp47( $code );
1024 }
1025
1037 public static function dir( Parser $parser, string $code = '', string $arg = '' ): string {
1038 static $magicWords = null;
1039 $languageFactory = MediaWikiServices::getInstance()->getLanguageFactory();
1040
1041 if ( $code === '' ) {
1042 $lang = $parser->getTargetLanguage();
1043 } else {
1044 if ( $arg !== '' ) {
1045 if ( $magicWords === null ) {
1046 $magicWords = $parser->getMagicWordFactory()->newArray( [ 'language_option_bcp47' ] );
1047 }
1048 if ( $magicWords->matchStartToEnd( $arg ) === 'language_option_bcp47' ) {
1049 // Prefer the BCP-47 interpretation of this code.
1050 $code = new Bcp47CodeValue( $code );
1051 }
1052 }
1053 try {
1054 $lang = $languageFactory->getLanguage( $code );
1055 } catch ( InvalidArgumentException $ex ) {
1056 $parser->addTrackingCategory( 'bad-language-code-category' );
1057 return 'ltr';
1058 }
1059 }
1060 return $lang->getDir();
1061 }
1062
1071 public static function bcp47( Parser $parser, string $code = '' ): string {
1072 if ( $code === '' ) {
1073 return $parser->getTargetLanguage()->toBcp47Code();
1074 } else {
1075 return LanguageCode::bcp47( $code );
1076 }
1077 }
1078
1088 public static function pad(
1089 $parser, $string, $length, $padding = '0', $direction = STR_PAD_RIGHT
1090 ) {
1091 $padding = $parser->killMarkers( $padding );
1092 $lengthOfPadding = mb_strlen( $padding );
1093 if ( $lengthOfPadding == 0 ) {
1094 return $string;
1095 }
1096
1097 # The remaining length to add counts down to 0 as padding is added
1098 $length = min( (int)$length, 500 ) - mb_strlen( $string );
1099 if ( $length <= 0 ) {
1100 // Nothing to add
1101 return $string;
1102 }
1103
1104 # $finalPadding is just $padding repeated enough times so that
1105 # mb_strlen( $string ) + mb_strlen( $finalPadding ) == $length
1106 $finalPadding = '';
1107 while ( $length > 0 ) {
1108 # If $length < $lengthofPadding, truncate $padding so we get the
1109 # exact length desired.
1110 $finalPadding .= mb_substr( $padding, 0, $length );
1111 $length -= $lengthOfPadding;
1112 }
1113
1114 if ( $direction == STR_PAD_LEFT ) {
1115 return $finalPadding . $string;
1116 } else {
1117 return $string . $finalPadding;
1118 }
1119 }
1120
1121 public static function padleft( $parser, $string = '', $length = 0, $padding = '0' ) {
1122 return self::pad( $parser, $string, $length, $padding, STR_PAD_LEFT );
1123 }
1124
1125 public static function padright( $parser, $string = '', $length = 0, $padding = '0' ) {
1126 return self::pad( $parser, $string, $length, $padding );
1127 }
1128
1134 public static function anchorencode( $parser, $text ) {
1135 $text = $parser->killMarkers( $text );
1136 $section = (string)substr( $parser->guessSectionNameFromWikiText( $text ), 1 );
1137 return Sanitizer::safeEncodeAttribute( $section );
1138 }
1139
1140 public static function special( $parser, $text ) {
1141 [ $page, $subpage ] = MediaWikiServices::getInstance()->getSpecialPageFactory()->
1142 resolveAlias( $text );
1143 if ( $page ) {
1144 $title = SpecialPage::getTitleFor( $page, $subpage );
1145 return $title->getPrefixedText();
1146 } else {
1147 // unknown special page, just use the given text as its title, if at all possible
1148 $title = Title::makeTitleSafe( NS_SPECIAL, $text );
1149 return $title ? $title->getPrefixedText() : self::special( $parser, 'Badtitle' );
1150 }
1151 }
1152
1153 public static function speciale( $parser, $text ) {
1154 return wfUrlencode( str_replace( ' ', '_', self::special( $parser, $text ) ) );
1155 }
1156
1165 public static function defaultsort( $parser, $text, $uarg = '' ) {
1166 static $magicWords = null;
1167 if ( $magicWords === null ) {
1168 $magicWords = $parser->getMagicWordFactory()->newArray(
1169 [ 'defaultsort_noerror', 'defaultsort_noreplace' ] );
1170 }
1171 $arg = $magicWords->matchStartToEnd( $uarg );
1172
1173 $text = trim( $text );
1174 if ( strlen( $text ) == 0 ) {
1175 return '';
1176 }
1177 $old = $parser->getOutput()->getPageProperty( 'defaultsort' );
1178 if ( $old === null || $arg !== 'defaultsort_noreplace' ) {
1179 $parser->getOutput()->setPageProperty( 'defaultsort', $text );
1180 }
1181
1182 if ( $old === null || $old == $text || $arg ) {
1183 return '';
1184 } else {
1185 $converter = $parser->getTargetLanguageConverter();
1186 return '<span class="error">' .
1187 $parser->msg( 'duplicate-defaultsort',
1188 // Message should be parsed, but these params should only be escaped.
1189 $converter->markNoConversion( wfEscapeWikiText( $old ) ),
1190 $converter->markNoConversion( wfEscapeWikiText( $text ) )
1191 )->text() .
1192 '</span>';
1193 }
1194 }
1195
1207 public static function filepath( $parser, $name = '', $argA = '', $argB = '' ) {
1208 $file = MediaWikiServices::getInstance()->getRepoGroup()->findFile( $name );
1209
1210 if ( $argA == 'nowiki' ) {
1211 // {{filepath: | option [| size] }}
1212 $isNowiki = true;
1213 $parsedWidthParam = $parser->parseWidthParam( $argB );
1214 } else {
1215 // {{filepath: [| size [|option]] }}
1216 $parsedWidthParam = $parser->parseWidthParam( $argA );
1217 $isNowiki = ( $argB == 'nowiki' );
1218 }
1219
1220 if ( $file ) {
1221 $url = $file->getFullUrl();
1222
1223 // If a size is requested...
1224 if ( count( $parsedWidthParam ) ) {
1225 $mto = $file->transform( $parsedWidthParam );
1226 // ... and we can
1227 if ( $mto && !$mto->isError() ) {
1228 // ... change the URL to point to a thumbnail.
1229 $urlUtils = MediaWikiServices::getInstance()->getUrlUtils();
1230 $url = $urlUtils->expand( $mto->getUrl(), PROTO_RELATIVE ) ?? false;
1231 }
1232 }
1233 if ( $isNowiki ) {
1234 return [ $url, 'nowiki' => true ];
1235 }
1236 return $url;
1237 } else {
1238 return '';
1239 }
1240 }
1241
1249 public static function tagObj( $parser, $frame, $args ) {
1250 if ( !count( $args ) ) {
1251 return '';
1252 }
1253 $tagName = strtolower( trim( $frame->expand( array_shift( $args ) ) ) );
1254 $processNowiki = $parser->tagNeedsNowikiStrippedInTagPF( $tagName ) ? PPFrame::PROCESS_NOWIKI : 0;
1255
1256 if ( count( $args ) ) {
1257 $inner = $frame->expand( array_shift( $args ), $processNowiki );
1258 } else {
1259 $inner = null;
1260 }
1261
1262 $attributes = [];
1263 foreach ( $args as $arg ) {
1264 $bits = $arg->splitArg();
1265 if ( strval( $bits['index'] ) === '' ) {
1266 $name = trim( $frame->expand( $bits['name'], PPFrame::STRIP_COMMENTS ) );
1267 $value = trim( $frame->expand( $bits['value'] ) );
1268 if ( preg_match( '/^(?:["\'](.+)["\']|""|\'\')$/s', $value, $m ) ) {
1269 $value = $m[1] ?? '';
1270 }
1271 $attributes[$name] = $value;
1272 }
1273 }
1274
1275 $stripList = $parser->getStripList();
1276 if ( !in_array( $tagName, $stripList ) ) {
1277 // we can't handle this tag (at least not now), so just re-emit it as an ordinary tag
1278 $attrText = '';
1279 foreach ( $attributes as $name => $value ) {
1280 $attrText .= ' ' . htmlspecialchars( $name ) .
1281 '="' . htmlspecialchars( $value, ENT_COMPAT ) . '"';
1282 }
1283 if ( $inner === null ) {
1284 return "<$tagName$attrText/>";
1285 }
1286 return "<$tagName$attrText>$inner</$tagName>";
1287 }
1288
1289 $params = [
1290 'name' => $tagName,
1291 'inner' => $inner,
1292 'attributes' => $attributes,
1293 'close' => "</$tagName>",
1294 ];
1295 return $parser->extensionSubstitution( $params, $frame );
1296 }
1297
1311 private static function getCachedRevisionObject( $parser, $title, $vary ) {
1312 if ( !$title ) {
1313 return null;
1314 }
1315
1316 $revisionRecord = null;
1317
1318 $isSelfReferential = $title->equals( $parser->getTitle() );
1319 if ( $isSelfReferential ) {
1320 // Revision is for the same title that is currently being parsed. Only use the last
1321 // saved revision, regardless of Parser::getRevisionId() or fake revision injection
1322 // callbacks against the current title.
1323
1324 // FIXME (T318278): the above is the intention, but doesn't
1325 // describe the actual current behavior of this code, since
1326 // ->isCurrent() for the last saved revision will return
1327 // false so we're going to fall through and end up calling
1328 // ->getCurrentRevisionRecordOfTitle().
1329 $parserRevisionRecord = $parser->getRevisionRecordObject();
1330 if ( $parserRevisionRecord && $parserRevisionRecord->isCurrent() ) {
1331 $revisionRecord = $parserRevisionRecord;
1332 }
1333 }
1334
1335 $parserOutput = $parser->getOutput();
1336 if ( !$revisionRecord ) {
1337 if (
1338 !$parser->isCurrentRevisionOfTitleCached( $title ) &&
1340 ) {
1341 return null; // not allowed
1342 }
1343 // Get the current revision, ignoring Parser::getRevisionId() being null/old
1344 $revisionRecord = $parser->fetchCurrentRevisionRecordOfTitle( $title );
1345 if ( !$revisionRecord ) {
1346 // Convert `false` error return to `null`
1347 $revisionRecord = null;
1348 }
1349 // Register dependency in templatelinks
1350 $parserOutput->addTemplate(
1351 $title,
1352 $revisionRecord ? $revisionRecord->getPageId() : 0,
1353 $revisionRecord ? $revisionRecord->getId() : 0
1354 );
1355 }
1356
1357 if ( $isSelfReferential ) {
1358 wfDebug( __METHOD__ . ": used current revision, setting $vary" );
1359 // Upon page save, the result of the parser function using this might change
1360 $parserOutput->setOutputFlag( $vary );
1361 if ( $vary === ParserOutputFlags::VARY_REVISION_SHA1 && $revisionRecord ) {
1362 try {
1363 $sha1 = $revisionRecord->getSha1();
1364 } catch ( RevisionAccessException $e ) {
1365 $sha1 = null;
1366 }
1367 $parserOutput->setRevisionUsedSha1Base36( $sha1 );
1368 }
1369 }
1370
1371 return $revisionRecord;
1372 }
1373
1381 public static function pageid( $parser, $title = null ) {
1382 $t = self::makeTitle( $parser, $title );
1383 if ( !$t ) {
1384 return '';
1385 } elseif ( !$t->canExist() || $t->isExternal() ) {
1386 return 0; // e.g. special page or interwiki link
1387 }
1388
1389 $parserOutput = $parser->getOutput();
1390
1391 if ( $t->equals( $parser->getTitle() ) ) {
1392 // Revision is for the same title that is currently being parsed.
1393 // Use the title from Parser in case a new page ID was injected into it.
1394 $parserOutput->setOutputFlag( ParserOutputFlags::VARY_PAGE_ID );
1395 $id = $parser->getTitle()->getArticleID();
1396 if ( $id ) {
1397 $parserOutput->setSpeculativePageIdUsed( $id );
1398 }
1399
1400 return $id;
1401 }
1402
1403 // Check the link cache for the title
1404 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
1405 $pdbk = $t->getPrefixedDBkey();
1406 $id = $linkCache->getGoodLinkID( $pdbk );
1407 if ( $id != 0 || $linkCache->isBadLink( $pdbk ) ) {
1408 $parserOutput->addLink( $t, $id );
1409
1410 return $id;
1411 }
1412
1413 // We need to load it from the DB, so mark expensive
1414 if ( $parser->incrementExpensiveFunctionCount() ) {
1415 $id = $t->getArticleID();
1416 $parserOutput->addLink( $t, $id );
1417
1418 return $id;
1419 }
1420
1421 return null;
1422 }
1423
1431 public static function revisionid( $parser, $title = null ) {
1432 $t = self::makeTitle( $parser, $title );
1433 if ( $t === null || $t->isExternal() ) {
1434 return '';
1435 }
1436
1437 $services = MediaWikiServices::getInstance();
1438 if (
1439 $t->equals( $parser->getTitle() ) &&
1440 $services->getMainConfig()->get( MainConfigNames::MiserMode ) &&
1441 !$parser->getOptions()->getInterfaceMessage() &&
1442 // @TODO: disallow this word on all namespaces (T235957)
1443 $services->getNamespaceInfo()->isSubject( $t->getNamespace() )
1444 ) {
1445 // Use a stub result instead of the actual revision ID in order to avoid
1446 // double parses on page save but still allow preview detection (T137900)
1447 if ( $parser->getRevisionId() || $parser->getOptions()->getSpeculativeRevId() ) {
1448 return '-';
1449 } else {
1450 $parser->getOutput()->setOutputFlag( ParserOutputFlags::VARY_REVISION_EXISTS );
1451 return '';
1452 }
1453 }
1454 // Fetch revision from cache/database and return the value.
1455 // Inform the edit saving system that getting the canonical output
1456 // after revision insertion requires a parse that used that exact
1457 // revision ID.
1458 if ( $t->equals( $parser->getTitle() ) && $title === null ) {
1459 // special handling for no-arg case: use speculative rev id
1460 // for current page.
1461 $parser->getOutput()->setOutputFlag( ParserOutputFlags::VARY_REVISION_ID );
1462 $id = $parser->getRevisionId();
1463 if ( $id === 0 ) {
1464 $rev = $parser->getRevisionRecordObject();
1465 if ( $rev ) {
1466 $id = $rev->getId();
1467 }
1468 }
1469 if ( !$id ) {
1470 $id = $parser->getOptions()->getSpeculativeRevId();
1471 if ( $id ) {
1472 $parser->getOutput()->setSpeculativeRevIdUsed( $id );
1473 }
1474 }
1475 return (string)$id;
1476 }
1477 $rev = self::getCachedRevisionObject( $parser, $t, ParserOutputFlags::VARY_REVISION_ID );
1478 return $rev ? $rev->getId() : '';
1479 }
1480
1481 private static function getRevisionTimestampSubstring(
1482 Parser $parser,
1483 Title $title,
1484 int $start,
1485 int $len,
1486 int $mtts
1487 ): string {
1488 // If fetching the revision timestamp of the current page, substitute the
1489 // speculative timestamp to be used when this revision is saved. This
1490 // avoids having to invalidate the cache immediately by assuming the "last
1491 // saved revision" will in fact be this one.
1492 // Don't do this for interface messages (eg, edit notices) however; in that
1493 // case fall through and use the actual timestamp of the last saved revision.
1494 if ( $title->equals( $parser->getTitle() ) && !$parser->getOptions()->getInterfaceMessage() ) {
1495 // Get the timezone-adjusted timestamp to be used for this revision
1496 $resNow = substr( $parser->getRevisionTimestamp(), $start, $len );
1497 // Possibly set vary-revision if there is not yet an associated revision
1498 if ( !$parser->getRevisionRecordObject() ) {
1499 // Get the timezone-adjusted timestamp $mtts seconds in the future.
1500 // This future is relative to the current time and not that of the
1501 // parser options. The rendered timestamp can be compared to that
1502 // of the timestamp specified by the parser options.
1503 $resThen = substr(
1504 $parser->getContentLanguage()->userAdjust( wfTimestamp( TS_MW, time() + $mtts ), '' ),
1505 $start,
1506 $len
1507 );
1508
1509 if ( $resNow !== $resThen ) {
1510 // Inform the edit saving system that getting the canonical output after
1511 // revision insertion requires a parse that used an actual revision timestamp
1512 $parser->getOutput()->setOutputFlag( ParserOutputFlags::VARY_REVISION_TIMESTAMP );
1513 }
1514 }
1515
1516 return $resNow;
1517 } else {
1518 $rev = self::getCachedRevisionObject( $parser, $title, ParserOutputFlags::VARY_REVISION_TIMESTAMP );
1519 if ( !$rev ) {
1520 return '';
1521 }
1522 $resNow = substr(
1523 $parser->getContentLanguage()->userAdjust( $rev->getTimestamp(), '' ), $start, $len
1524 );
1525 return $resNow;
1526 }
1527 }
1528
1536 public static function revisionday( $parser, $title = null ) {
1537 $t = self::makeTitle( $parser, $title );
1538 if ( $t === null || $t->isExternal() ) {
1539 return '';
1540 }
1541 return strval( (int)self::getRevisionTimestampSubstring(
1542 $parser, $t, 6, 2, self::MAX_TTS
1543 ) );
1544 }
1545
1553 public static function revisionday2( $parser, $title = null ) {
1554 $t = self::makeTitle( $parser, $title );
1555 if ( $t === null || $t->isExternal() ) {
1556 return '';
1557 }
1558 return self::getRevisionTimestampSubstring(
1559 $parser, $t, 6, 2, self::MAX_TTS
1560 );
1561 }
1562
1570 public static function revisionmonth( $parser, $title = null ) {
1571 $t = self::makeTitle( $parser, $title );
1572 if ( $t === null || $t->isExternal() ) {
1573 return '';
1574 }
1575 return self::getRevisionTimestampSubstring(
1576 $parser, $t, 4, 2, self::MAX_TTS
1577 );
1578 }
1579
1587 public static function revisionmonth1( $parser, $title = null ) {
1588 $t = self::makeTitle( $parser, $title );
1589 if ( $t === null || $t->isExternal() ) {
1590 return '';
1591 }
1592 return strval( (int)self::getRevisionTimestampSubstring(
1593 $parser, $t, 4, 2, self::MAX_TTS
1594 ) );
1595 }
1596
1604 public static function revisionyear( $parser, $title = null ) {
1605 $t = self::makeTitle( $parser, $title );
1606 if ( $t === null || $t->isExternal() ) {
1607 return '';
1608 }
1609 return self::getRevisionTimestampSubstring(
1610 $parser, $t, 0, 4, self::MAX_TTS
1611 );
1612 }
1613
1621 public static function revisiontimestamp( $parser, $title = null ) {
1622 $t = self::makeTitle( $parser, $title );
1623 if ( $t === null || $t->isExternal() ) {
1624 return '';
1625 }
1626 return self::getRevisionTimestampSubstring(
1627 $parser, $t, 0, 14, self::MAX_TTS
1628 );
1629 }
1630
1638 public static function revisionuser( $parser, $title = null ) {
1639 $t = self::makeTitle( $parser, $title );
1640 if ( $t === null || $t->isExternal() ) {
1641 return '';
1642 }
1643 // VARY_USER informs the edit saving system that getting the canonical
1644 // output after revision insertion requires a parse that used the
1645 // actual user ID.
1646 if ( $t->equals( $parser->getTitle() ) ) {
1647 // Fall back to Parser's "revision user" for the current title
1648 $parser->getOutput()->setOutputFlag( ParserOutputFlags::VARY_USER );
1649 // Note that getRevisionUser() can return null; we need to
1650 // be sure to cast this to (an empty) string, since returning
1651 // null means "magic variable not handled".
1652 return (string)$parser->getRevisionUser();
1653 }
1654 // Fetch revision from cache/database and return the value.
1655 $rev = self::getCachedRevisionObject( $parser, $t, ParserOutputFlags::VARY_USER );
1656 $user = ( $rev !== null ) ? $rev->getUser() : null;
1657 return $user ? $user->getName() : '';
1658 }
1659
1672 public static function cascadingsources( $parser, $title = '' ) {
1673 $titleObject = Title::newFromText( $title ) ?? $parser->getTitle();
1674 $restrictionStore = MediaWikiServices::getInstance()->getRestrictionStore();
1675 if ( $restrictionStore->areCascadeProtectionSourcesLoaded( $titleObject )
1677 ) {
1678 $names = [];
1679 $sources = $restrictionStore->getCascadeProtectionSources( $titleObject );
1680 $titleFormatter = MediaWikiServices::getInstance()->getTitleFormatter();
1681 foreach ( $sources[0] as $sourcePageIdentity ) {
1682 $names[] = $titleFormatter->getPrefixedText( $sourcePageIdentity );
1683 }
1684 return implode( '|', $names );
1685 }
1686 return '';
1687 }
1688
1689 public static function interwikilink( $parser, $prefix = '', $title = '', $linkText = null ) {
1690 $services = MediaWikiServices::getInstance();
1691 if (
1692 $prefix !== '' &&
1693 $services->getInterwikiLookup()->isValidInterwiki( $prefix )
1694 ) {
1695 if ( $linkText !== null ) {
1696 $linkText = Parser::stripOuterParagraph(
1697 $parser->recursiveTagParseFully( $linkText )
1698 );
1699 }
1700 [ $title, $frag ] = array_pad( explode( '#', $title, 2 ), 2, '' );
1701 $target = new TitleValue( NS_MAIN, $title, $frag, $prefix );
1702 $parser->getOutput()->addInterwikiLink( $target );
1703 return [
1704 'text' => Linker::link( $target, $linkText ),
1705 'isHTML' => true,
1706 ];
1707 }
1708 // Invalid interwiki link, render as plain text
1709 return [ 'found' => false ];
1710 }
1711
1712 public static function interlanguagelink( $parser, $prefix = '', $title = '', $linkText = null ) {
1713 $services = MediaWikiServices::getInstance();
1714 $extraInterlanguageLinkPrefixes = $services->getMainConfig()->get(
1715 MainConfigNames::ExtraInterlanguageLinkPrefixes
1716 );
1717 if (
1718 $prefix !== '' &&
1719 $services->getInterwikiLookup()->isValidInterwiki( $prefix ) &&
1720 (
1721 $services->getLanguageNameUtils()->getLanguageName(
1722 $prefix, LanguageNameUtils::AUTONYMS, LanguageNameUtils::DEFINED
1723 ) || in_array( $prefix, $extraInterlanguageLinkPrefixes, true )
1724 )
1725 ) {
1726 // $linkText is ignored for language links, but fragment is kept
1727 [ $title, $frag ] = array_pad( explode( '#', $title, 2 ), 2, '' );
1728 $parser->getOutput()->addLanguageLink(
1729 new TitleValue(
1730 NS_MAIN, $title, $frag, $prefix
1731 )
1732 );
1733 return '';
1734 }
1735 // Invalid language link, render as plain text
1736 return [ 'found' => false ];
1737 }
1738}
1739
1741class_alias( CoreParserFunctions::class, 'CoreParserFunctions' );
const NS_USER
Definition Defines.php:67
const NS_FILE
Definition Defines.php:71
const NS_MAIN
Definition Defines.php:65
const NS_SPECIAL
Definition Defines.php:54
const NS_MEDIA
Definition Defines.php:53
const PROTO_RELATIVE
Definition Defines.php:206
const NS_CATEGORY
Definition Defines.php:79
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,...
wfEscapeWikiText( $input)
Escapes the given text so that it may be output using addWikiText() without any linking,...
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.
$magicWords
@phpcs-require-sorted-array
array $params
The job parameters.
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:81
Category objects are immutable, strictly speaking.
Definition Category.php:42
A class for passing options to services.
Methods for dealing with language codes.
Base class for language-specific code.
Definition Language.php:80
A service that provides utilities to do with language names and codes.
Some internal bits split of from Skin.php.
Definition Linker.php:63
A class containing constants representing the names of configuration variables.
const AllowSlowParserFunctions
Name constant for the AllowSlowParserFunctions setting, for use with Config::get()
const AllowDisplayTitle
Name constant for the AllowDisplayTitle setting, for use with Config::get()
const RestrictDisplayTitle
Name constant for the RestrictDisplayTitle setting, for use with Config::get()
Service locator for MediaWiki core services.
static getInstance()
Returns the global default instance of the top level service locator.
The Message class deals with fetching and processing of interface message into a variety of formats.
Definition Message.php:155
static plaintextParam( $plaintext)
Definition Message.php:1310
Various core parser functions, registered in every Parser.
static pagesize( $parser, $page='', $raw=null)
Return the size of the given page, or 0 if it's nonexistent.
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 language( $parser, $code='', $inLanguage='')
Gives language names.
static bcp47(Parser $parser, string $code='')
Gives the BCP-47 code for a language given the mediawiki internal language code.
static numberofedits( $parser, $raw=null)
static namespacenumber( $parser, $title=null)
static revisiontimestamp( $parser, $title=null)
Get the timestamp from the last revision of a specified page.
static defaultsort( $parser, $text, $uarg='')
static basepagename( $parser, $title=null)
static formal(Parser $parser, string ... $forms)
static namespacee( $parser, $title=null)
static numberofpages( $parser, $raw=null)
static dir(Parser $parser, string $code='', string $arg='')
Gives direction of script of a language given a language code.
static pagesinnamespace( $parser, $namespace=0, $raw=null)
static numberingroup( $parser, $name='', $raw=null)
static interlanguagelink( $parser, $prefix='', $title='', $linkText=null)
static fullurle( $parser, $s='', $arg=null)
static pageid( $parser, $title=null)
Get the pageid of a specified page.
static intFunction( $parser, $part1='',... $params)
static tagObj( $parser, $frame, $args)
Parser function to extension tag adaptor.
static numberofusers( $parser, $raw=null)
static formatDate( $parser, $date, $defaultPref=null)
static talkspacee( $parser, $title=null)
static subjectpagenamee( $parser, $title=null)
static rootpagename( $parser, $title=null)
static cascadingsources( $parser, $title='')
Returns the sources of any cascading protection acting on a specified page.
static fullpagename( $parser, $title=null)
static formatRaw( $num, $raw, $language, ?MagicWordFactory $magicWordFactory=null)
Formats a number according to a language.
static plural( $parser, $text='',... $forms)
static subpagenamee( $parser, $title=null)
static fullpagenamee( $parser, $title=null)
static padright( $parser, $string='', $length=0, $padding='0')
static urlFunction( $func, $s='', $arg=null)
static talkpagenamee( $parser, $title=null)
static canonicalurl( $parser, $s='', $arg=null)
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 revisionday2( $parser, $title=null)
Get the day with leading zeros from the last revision of a specified page.
static protectionexpiry( $parser, $type='', $title='')
Returns the requested protection expiry for the current page.
static talkspace( $parser, $title=null)
static numberofactiveusers( $parser, $raw=null)
static subjectspacee( $parser, $title=null)
static pagenamee( $parser, $title=null)
static grammar( $parser, $case='', $word='')
static revisionuser( $parser, $title=null)
Get the user from the last revision of a specified page.
static padleft( $parser, $string='', $length=0, $padding='0')
static localurl( $parser, $s='', $arg=null)
static urlencode( $parser, $s='', $arg=null)
urlencodes a string according to one of three patterns: (T24474)
static localurle( $parser, $s='', $arg=null)
static rootpagenamee( $parser, $title=null)
static formatnum( $parser, $num='', $arg=null)
static protectionlevel( $parser, $type='', $title='')
Returns the requested protection level for the current page.
static subpagename( $parser, $title=null)
static subjectpagename( $parser, $title=null)
static interwikilink( $parser, $prefix='', $title='', $linkText=null)
static numberofadmins( $parser, $raw=null)
static revisionday( $parser, $title=null)
Get the day from the last revision of a specified page.
static canonicalurle( $parser, $s='', $arg=null)
static subjectspace( $parser, $title=null)
static revisionmonth( $parser, $title=null)
Get the month with leading zeros from the last revision of a specified page.
static basepagenamee( $parser, $title=null)
static gender( $parser, $username,... $forms)
static revisionyear( $parser, $title=null)
Get the year from the last revision of a specified page.
static pagename( $parser, $title=null)
Functions to get and normalize pagenames, corresponding to the magic words of the same names.
static fullurl( $parser, $s='', $arg=null)
static numberofarticles( $parser, $raw=null)
static revisionmonth1( $parser, $title=null)
Get the month from the last revision of a specified page.
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 talkpagename( $parser, $title=null)
static revisionid( $parser, $title=null)
Get the id from the last revision of a specified page.
static numberoffiles( $parser, $raw=null)
static filepath( $parser, $name='', $argA='', $argB='')
Usage {{filepath|300}}, {{filepath|nowiki}}, {{filepath|nowiki|300}} or {{filepath|300|nowiki}} or {{...
Store information about magic words, and create/cache MagicWord objects.
get( $id)
Get a MagicWord object for a given internal ID.
PHP Parser - Processes wiki markup (which uses a more user-friendly syntax, such as "[[link]]" for ma...
Definition Parser.php:145
getTargetLanguageConverter()
Shorthand for getting a Language Converter for Target language.
Definition Parser.php:1651
markerSkipCallback( $s, callable $callback)
Call a callback function on all regions of the given text that are not inside strip markers,...
Definition Parser.php:6335
tagNeedsNowikiStrippedInTagPF(string $lowerTagName)
Definition Parser.php:3960
getMagicWordFactory()
Get the MagicWordFactory that this Parser is using.
Definition Parser.php:1232
setFunctionHook( $id, callable $callback, $flags=0)
Create a function, e.g.
Definition Parser.php:5040
guessSectionNameFromWikiText( $text)
Try to guess the section anchor name based on a wikitext fragment presumably extracted from a heading...
Definition Parser.php:6227
isCurrentRevisionOfTitleCached(LinkTarget $link)
Definition Parser.php:3565
getRevisionId()
Get the ID of the revision we are parsing.
Definition Parser.php:6044
getContentLanguage()
Get the content language that this Parser is using.
Definition Parser.php:1242
parseWidthParam( $value, $parseHeight=true, bool $localized=false)
Parsed a width param of imagelink like 300px or 200x300px.
Definition Parser.php:6383
killMarkers( $text)
Remove any strip markers found in the given text.
Definition Parser.php:6366
getRevisionUser()
Get the name of the user that edited the last revision.
Definition Parser.php:6137
getTargetLanguage()
Get the target language for the content being parsed.
Definition Parser.php:1178
msg(string $msg,... $params)
Helper function to correctly set the target language and title of a message based on the parser conte...
Definition Parser.php:4183
getStripList()
Get a list of strippable XML-like elements.
Definition Parser.php:1338
extensionSubstitution(array $params, PPFrame $frame, bool $processNowiki=false)
Return the text to be used for a given extension tag.
Definition Parser.php:3984
getRevisionRecordObject()
Get the revision record object for $this->mRevisionId.
Definition Parser.php:6054
getRevisionTimestamp()
Get the timestamp associated with the current revision, adjusted for the default server-local timesta...
Definition Parser.php:6109
doQuotes( $text)
Helper function for handleAllQuotes()
Definition Parser.php:1987
recursiveTagParseFully( $text, $frame=false)
Fully parse wikitext to fully parsed HTML.
Definition Parser.php:884
fetchCurrentRevisionRecordOfTitle(LinkTarget $link)
Fetch the current revision of a given title as a RevisionRecord.
Definition Parser.php:3535
static stripAllTags(string $html)
Take a fragment of (potentially invalid) HTML and return a version with any tags removed,...
static checkCss( $value)
Pick apart some CSS and check it for forbidden or unsafe structures.
static decodeCharReferencesAndNormalize(string $text)
Decode any character references, numeric or named entities, in the next and normalize the resulting s...
static removeSomeTags(string $text, array $options=[])
Cleans up HTML, removes dangerous tags and attributes, and removes HTML comments; the result will alw...
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.
Represents the target of a wiki link.
Represents a title within MediaWiki.
Definition Title.php:78
inNamespace(int $ns)
Returns true if the title is inside the specified namespace.
Definition Title.php:1301
equals(object $other)
Compares with another Title.
Definition Title.php:3095
getDBkey()
Get the main part with underscores.
Definition Title.php:1034
getText()
Get the text form (spaces not underscores) of the main part.
Definition Title.php:1016
getDefaultOption(string $opt, ?UserIdentity $userIdentity=null)
Get a given default option value.
internal since 1.36
Definition User.php:93
hasFragment()
Whether the link target has a fragment.