MediaWiki 1.43.1
CoreParserFunctions.php
Go to the documentation of this file.
1<?php
24namespace MediaWiki\Parser;
25
26use InvalidArgumentException;
41use Wikimedia\Bcp47Code\Bcp47CodeValue;
42use Wikimedia\RemexHtml\Tokenizer\Attributes;
43use Wikimedia\RemexHtml\Tokenizer\PlainAttributes;
44
51 private const MAX_TTS = 900;
52
56 public const REGISTER_OPTIONS = [
57 // See documentation for the corresponding config options
60 ];
61
69 public static function register( Parser $parser, ServiceOptions $options ) {
70 $options->assertRequiredOptions( self::REGISTER_OPTIONS );
71 $allowDisplayTitle = $options->get( MainConfigNames::AllowDisplayTitle );
72 $allowSlowParserFunctions = $options->get( MainConfigNames::AllowSlowParserFunctions );
73
74 # Syntax for arguments (see Parser::setFunctionHook):
75 # "name for lookup in localized magic words array",
76 # function callback,
77 # optional Parser::SFH_NO_HASH to omit the hash from calls (e.g. {{int:...}}
78 # instead of {{#int:...}})
79 $noHashFunctions = [
80 'ns', 'nse', 'urlencode', 'lcfirst', 'ucfirst', 'lc', 'uc',
81 'localurl', 'localurle', 'fullurl', 'fullurle', 'canonicalurl',
82 'canonicalurle', 'formatnum', 'grammar', 'gender', 'plural', 'formal',
83 'bidi', 'numberingroup', 'language',
84 'padleft', 'padright', 'anchorencode', 'defaultsort', 'filepath',
85 'pagesincategory', 'pagesize', 'protectionlevel', 'protectionexpiry',
86 # The following are the "parser function" forms of magic
87 # variables defined in CoreMagicVariables. The no-args form will
88 # go through the magic variable code path (and be cached); the
89 # presence of arguments will cause the parser function form to
90 # be invoked. (Note that the actual implementation will pass
91 # a Parser object as first argument, in addition to the
92 # parser function parameters.)
93
94 # For this group, the first parameter to the parser function is
95 # "page title", and the no-args form (and the magic variable)
96 # defaults to "current page title".
97 'pagename', 'pagenamee',
98 'fullpagename', 'fullpagenamee',
99 'subpagename', 'subpagenamee',
100 'rootpagename', 'rootpagenamee',
101 'basepagename', 'basepagenamee',
102 'talkpagename', 'talkpagenamee',
103 'subjectpagename', 'subjectpagenamee',
104 'pageid', 'revisionid', 'revisionday',
105 'revisionday2', 'revisionmonth', 'revisionmonth1', 'revisionyear',
106 'revisiontimestamp',
107 'revisionuser',
108 'cascadingsources',
109 'namespace', 'namespacee', 'namespacenumber', 'talkspace', 'talkspacee',
110 'subjectspace', 'subjectspacee',
111
112 # More parser functions corresponding to CoreMagicVariables.
113 # For this group, the first parameter to the parser function is
114 # "raw" (uses the 'raw' format if present) and the no-args form
115 # (and the magic variable) defaults to 'not raw'.
116 'numberofarticles', 'numberoffiles',
117 'numberofusers',
118 'numberofactiveusers',
119 'numberofpages',
120 'numberofadmins',
121 'numberofedits',
122
123 # These magic words already contain the hash, and the no-args form
124 # is the same as passing an empty first argument
125 'bcp47',
126 'dir',
127 ];
128 foreach ( $noHashFunctions as $func ) {
129 $parser->setFunctionHook( $func, [ __CLASS__, $func ], Parser::SFH_NO_HASH );
130 }
131
132 $parser->setFunctionHook( 'int', [ __CLASS__, 'intFunction' ], Parser::SFH_NO_HASH );
133 $parser->setFunctionHook( 'special', [ __CLASS__, 'special' ] );
134 $parser->setFunctionHook( 'speciale', [ __CLASS__, 'speciale' ] );
135 $parser->setFunctionHook( 'tag', [ __CLASS__, 'tagObj' ], Parser::SFH_OBJECT_ARGS );
136 $parser->setFunctionHook( 'formatdate', [ __CLASS__, 'formatDate' ] );
137
138 if ( $allowDisplayTitle ) {
139 $parser->setFunctionHook(
140 'displaytitle',
141 [ __CLASS__, 'displaytitle' ],
143 );
144 }
145 if ( $allowSlowParserFunctions ) {
146 $parser->setFunctionHook(
147 'pagesinnamespace',
148 [ __CLASS__, 'pagesinnamespace' ],
150 );
151 }
152 }
153
160 public static function intFunction( $parser, $part1 = '', ...$params ) {
161 if ( strval( $part1 ) !== '' ) {
162 $message = wfMessage( $part1, $params )
163 ->inLanguage( $parser->getOptions()->getUserLangObj() );
164 return [ $message->plain(), 'noparse' => false ];
165 } else {
166 return [ 'found' => false ];
167 }
168 }
169
177 public static function formatDate( $parser, $date, $defaultPref = null ) {
178 $lang = $parser->getTargetLanguage();
179 $df = MediaWikiServices::getInstance()->getDateFormatterFactory()->get( $lang );
180
181 $date = trim( $date );
182
183 $pref = $parser->getOptions()->getDateFormat();
184
185 // Specify a different default date format other than the normal default
186 // if the user has 'default' for their setting
187 if ( $pref == 'default' && $defaultPref ) {
188 $pref = $defaultPref;
189 }
190
191 $date = $df->reformat( $pref, $date, [ 'match-whole' ] );
192 return $date;
193 }
194
195 public static function ns( $parser, $part1 = '' ) {
196 if ( intval( $part1 ) || $part1 == "0" ) {
197 $index = intval( $part1 );
198 } else {
199 $index = $parser->getContentLanguage()->getNsIndex( str_replace( ' ', '_', $part1 ) );
200 }
201 if ( $index !== false ) {
202 return $parser->getContentLanguage()->getFormattedNsText( $index );
203 } else {
204 return [ 'found' => false ];
205 }
206 }
207
208 public static function nse( $parser, $part1 = '' ) {
209 $ret = self::ns( $parser, $part1 );
210 if ( is_string( $ret ) ) {
211 $ret = wfUrlencode( str_replace( ' ', '_', $ret ) );
212 }
213 return $ret;
214 }
215
228 public static function urlencode( $parser, $s = '', $arg = null ) {
229 static $magicWords = null;
230 if ( $magicWords === null ) {
232 $parser->getMagicWordFactory()->newArray( [ 'url_path', 'url_query', 'url_wiki' ] );
233 }
234 switch ( $magicWords->matchStartToEnd( $arg ?? '' ) ) {
235 // Encode as though it's a wiki page, '_' for ' '.
236 case 'url_wiki':
237 $func = 'wfUrlencode';
238 $s = str_replace( ' ', '_', $s );
239 break;
240
241 // Encode for an HTTP Path, '%20' for ' '.
242 case 'url_path':
243 $func = 'rawurlencode';
244 break;
245
246 // Encode for HTTP query, '+' for ' '.
247 case 'url_query':
248 default:
249 $func = 'urlencode';
250 }
251 // See T105242, where the choice to kill markers and various
252 // other options were discussed.
253 return $func( $parser->killMarkers( $s ) );
254 }
255
256 public static function lcfirst( $parser, $s = '' ) {
257 return $parser->getContentLanguage()->lcfirst( $s );
258 }
259
260 public static function ucfirst( $parser, $s = '' ) {
261 return $parser->getContentLanguage()->ucfirst( $s );
262 }
263
269 public static function lc( $parser, $s = '' ) {
270 return $parser->markerSkipCallback( $s, [ $parser->getContentLanguage(), 'lc' ] );
271 }
272
278 public static function uc( $parser, $s = '' ) {
279 return $parser->markerSkipCallback( $s, [ $parser->getContentLanguage(), 'uc' ] );
280 }
281
282 public static function localurl( $parser, $s = '', $arg = null ) {
283 return self::urlFunction( 'getLocalURL', $s, $arg );
284 }
285
286 public static function localurle( $parser, $s = '', $arg = null ) {
287 $temp = self::urlFunction( 'getLocalURL', $s, $arg );
288 if ( !is_string( $temp ) ) {
289 return $temp;
290 } else {
291 return htmlspecialchars( $temp, ENT_COMPAT );
292 }
293 }
294
295 public static function fullurl( $parser, $s = '', $arg = null ) {
296 return self::urlFunction( 'getFullURL', $s, $arg );
297 }
298
299 public static function fullurle( $parser, $s = '', $arg = null ) {
300 $temp = self::urlFunction( 'getFullURL', $s, $arg );
301 if ( !is_string( $temp ) ) {
302 return $temp;
303 } else {
304 return htmlspecialchars( $temp, ENT_COMPAT );
305 }
306 }
307
308 public static function canonicalurl( $parser, $s = '', $arg = null ) {
309 return self::urlFunction( 'getCanonicalURL', $s, $arg );
310 }
311
312 public static function canonicalurle( $parser, $s = '', $arg = null ) {
313 $temp = self::urlFunction( 'getCanonicalURL', $s, $arg );
314 if ( !is_string( $temp ) ) {
315 return $temp;
316 } else {
317 return htmlspecialchars( $temp, ENT_COMPAT );
318 }
319 }
320
321 public static function urlFunction( $func, $s = '', $arg = null ) {
322 # Due to order of execution of a lot of bits, the values might be encoded
323 # before arriving here; if that's true, then the title can't be created
324 # and the variable will fail. If we can't get a decent title from the first
325 # attempt, url-decode and try for a second.
326 $title = Title::newFromText( $s ) ?? Title::newFromURL( urldecode( $s ) );
327 if ( $title !== null ) {
328 # Convert NS_MEDIA -> NS_FILE
329 if ( $title->inNamespace( NS_MEDIA ) ) {
330 $title = Title::makeTitle( NS_FILE, $title->getDBkey() );
331 }
332 if ( $arg !== null ) {
333 $text = $title->$func( $arg );
334 } else {
335 $text = $title->$func();
336 }
337 return $text;
338 } else {
339 return [ 'found' => false ];
340 }
341 }
342
349 public static function formatnum( $parser, $num = '', $arg = null ) {
350 if ( self::matchAgainstMagicword( $parser->getMagicWordFactory(), 'rawsuffix', $arg ) ) {
351 $func = [ $parser->getTargetLanguage(), 'parseFormattedNumber' ];
352 } elseif (
353 self::matchAgainstMagicword( $parser->getMagicWordFactory(), 'nocommafysuffix', $arg )
354 ) {
355 $func = [ $parser->getTargetLanguage(), 'formatNumNoSeparators' ];
356 $func = self::getLegacyFormatNum( $parser, $func );
357 } else {
358 $func = [ $parser->getTargetLanguage(), 'formatNum' ];
359 $func = self::getLegacyFormatNum( $parser, $func );
360 }
361 return $parser->markerSkipCallback( $num, $func );
362 }
363
370 private static function getLegacyFormatNum( $parser, $callback ) {
371 // For historic reasons, the formatNum parser function will
372 // take arguments which are not actually formatted numbers,
373 // which then trigger deprecation warnings in Language::formatNum*.
374 // Instead emit a tracking category instead to allow linting.
375 return static function ( $number ) use ( $parser, $callback ) {
376 $validNumberRe = '(-(?=[\d\.]))?(\d+|(?=\.\d))(\.\d*)?([Ee][-+]?\d+)?';
377 if (
378 !is_numeric( $number ) &&
379 $number !== (string)NAN &&
380 $number !== (string)INF &&
381 $number !== (string)-INF
382 ) {
383 $parser->addTrackingCategory( 'nonnumeric-formatnum' );
384 // Don't split on NAN/INF in the legacy case since they are
385 // likely to be found embedded inside non-numeric text.
386 return preg_replace_callback( "/{$validNumberRe}/", static function ( $m ) use ( $callback ) {
387 return call_user_func( $callback, $m[0] );
388 }, $number );
389 }
390 return call_user_func( $callback, $number );
391 };
392 }
393
400 public static function grammar( $parser, $case = '', $word = '' ) {
401 $word = $parser->killMarkers( $word );
402 return $parser->getTargetLanguage()->convertGrammar( $word, $case );
403 }
404
411 public static function gender( $parser, $username, ...$forms ) {
412 // Some shortcuts to avoid loading user data unnecessarily
413 if ( count( $forms ) === 0 ) {
414 return '';
415 } elseif ( count( $forms ) === 1 ) {
416 return $forms[0];
417 }
418
419 $username = trim( $username );
420
421 $userOptionsLookup = MediaWikiServices::getInstance()->getUserOptionsLookup();
422 $gender = $userOptionsLookup->getDefaultOption( 'gender' );
423
424 // allow prefix and normalize (e.g. "&#42;foo" -> "*foo" ).
425 $title = Title::newFromText( $username, NS_USER );
426
427 if ( $title && $title->inNamespace( NS_USER ) ) {
428 $username = $title->getText();
429 }
430
431 // check parameter, or use the ParserOptions if in interface message
432 $user = User::newFromName( $username );
433 $genderCache = MediaWikiServices::getInstance()->getGenderCache();
434 if ( $user ) {
435 $gender = $genderCache->getGenderOf( $user, __METHOD__ );
436 } elseif ( $username === '' && $parser->getOptions()->getInterfaceMessage() ) {
437 $gender = $genderCache->getGenderOf( $parser->getOptions()->getUserIdentity(), __METHOD__ );
438 }
439 $ret = $parser->getTargetLanguage()->gender( $gender, $forms );
440 return $ret;
441 }
442
449 public static function plural( $parser, $text = '', ...$forms ) {
450 $text = $parser->getTargetLanguage()->parseFormattedNumber( $text );
451 settype( $text, ctype_digit( $text ) ? 'int' : 'float' );
452 // @phan-suppress-next-line PhanTypeMismatchArgument Phan does not handle settype
453 return $parser->getTargetLanguage()->convertPlural( $text, $forms );
454 }
455
456 public static function formal( Parser $parser, string ...$forms ): string {
457 $index = $parser->getTargetLanguage()->getFormalityIndex();
458 return $forms[$index] ?? $forms[0];
459 }
460
466 public static function bidi( $parser, $text = '' ) {
467 return $parser->getTargetLanguage()->embedBidi( $text );
468 }
469
479 public static function displaytitle( $parser, $text = '', $uarg = '' ) {
480 $restrictDisplayTitle = MediaWikiServices::getInstance()->getMainConfig()
482
483 static $magicWords = null;
484 if ( $magicWords === null ) {
485 $magicWords = $parser->getMagicWordFactory()->newArray(
486 [ 'displaytitle_noerror', 'displaytitle_noreplace' ] );
487 }
488 $arg = $magicWords->matchStartToEnd( $uarg );
489
490 // parse a limited subset of wiki markup (just the single quote items)
491 $text = $parser->doQuotes( $text );
492
493 // remove stripped text (e.g. the UNIQ-QINU stuff) that was generated by tag extensions/whatever
494 $text = $parser->killMarkers( $text );
495
496 // See T28547 for rationale for this processing.
497 // list of disallowed tags for DISPLAYTITLE
498 // these will be escaped even though they are allowed in normal wiki text
499 $bad = [ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'div', 'blockquote', 'ol', 'ul', 'li', 'hr',
500 'table', 'tr', 'th', 'td', 'dl', 'dd', 'caption', 'p', 'ruby', 'rb', 'rt', 'rtc', 'rp', 'br' ];
501
502 // disallow some styles that could be used to bypass $wgRestrictDisplayTitle
503 if ( $restrictDisplayTitle ) {
504 // This code is tested with the cases marked T28547 in
505 // parserTests.txt
506 $htmlTagsCallback = static function ( Attributes $attr ): Attributes {
507 $decoded = $attr->getValues();
508
509 if ( isset( $decoded['style'] ) ) {
510 // this is called later anyway, but we need it right now for the regexes below to be safe
511 // calling it twice doesn't hurt
512 $decoded['style'] = Sanitizer::checkCss( $decoded['style'] );
513
514 if ( preg_match( '/(display|user-select|visibility)\s*:/i', $decoded['style'] ) ) {
515 $decoded['style'] = '/* attempt to bypass $wgRestrictDisplayTitle */';
516 }
517 }
518
519 return new PlainAttributes( $decoded );
520 };
521 } else {
522 $htmlTagsCallback = null;
523 }
524
525 // only requested titles that normalize to the actual title are allowed through
526 // if $wgRestrictDisplayTitle is true (it is by default)
527 // mimic the escaping process that occurs in OutputPage::setPageTitle
528 $text = Sanitizer::removeSomeTags( $text, [
529 'attrCallback' => $htmlTagsCallback,
530 'removeTags' => $bad,
531 ] );
532 $title = Title::newFromText( Sanitizer::stripAllTags( $text ) );
533 // Decode entities in $text the same way that Title::newFromText does
534 $filteredText = Sanitizer::decodeCharReferencesAndNormalize( $text );
535
536 if ( !$restrictDisplayTitle ||
537 ( $title instanceof Title
538 && !$title->hasFragment()
539 && $title->equals( $parser->getTitle() ) )
540 ) {
541 $old = $parser->getOutput()->getPageProperty( 'displaytitle' );
542 if ( $old === null || $arg !== 'displaytitle_noreplace' ) {
543 $parser->getOutput()->setDisplayTitle( $text );
544 }
545 if ( $old !== null && $old !== $text && !$arg ) {
546
547 $converter = $parser->getTargetLanguageConverter();
548 return '<span class="error">' .
549 $parser->msg( 'duplicate-displaytitle',
550 // Message should be parsed, but these params should only be escaped.
551 $converter->markNoConversion( wfEscapeWikiText( $old ) ),
552 $converter->markNoConversion( wfEscapeWikiText( $filteredText ) )
553 )->text() .
554 '</span>';
555 } else {
556 return '';
557 }
558 } else {
559 $parser->getOutput()->addWarningMsg(
560 'restricted-displaytitle',
561 // Message should be parsed, but this param should only be escaped.
562 Message::plaintextParam( $filteredText )
563 );
564 $parser->addTrackingCategory( 'restricted-displaytitle-ignored' );
565 }
566 }
567
577 private static function matchAgainstMagicword(
578 MagicWordFactory $magicWordFactory, $magicword, $value
579 ) {
580 $value = trim( strval( $value ) );
581 if ( $value === '' ) {
582 return false;
583 }
584 $mwObject = $magicWordFactory->get( $magicword );
585 return $mwObject->matchStartToEnd( $value );
586 }
587
597 public static function formatRaw(
598 $num, $raw, $language, ?MagicWordFactory $magicWordFactory = null
599 ) {
600 if ( $raw !== null && $raw !== '' ) {
601 if ( !$magicWordFactory ) {
602 $magicWordFactory = MediaWikiServices::getInstance()->getMagicWordFactory();
603 }
604 if ( self::matchAgainstMagicword( $magicWordFactory, 'rawsuffix', $raw ) ) {
605 return (string)$num;
606 }
607 }
608 return $language->formatNum( $num );
609 }
610
611 public static function numberofpages( $parser, $raw = null ) {
612 return self::formatRaw( SiteStats::pages(), $raw, $parser->getTargetLanguage() );
613 }
614
615 public static function numberofusers( $parser, $raw = null ) {
616 return self::formatRaw( SiteStats::users(), $raw, $parser->getTargetLanguage() );
617 }
618
619 public static function numberofactiveusers( $parser, $raw = null ) {
620 return self::formatRaw( SiteStats::activeUsers(), $raw, $parser->getTargetLanguage() );
621 }
622
623 public static function numberofarticles( $parser, $raw = null ) {
624 return self::formatRaw( SiteStats::articles(), $raw, $parser->getTargetLanguage() );
625 }
626
627 public static function numberoffiles( $parser, $raw = null ) {
628 return self::formatRaw( SiteStats::images(), $raw, $parser->getTargetLanguage() );
629 }
630
631 public static function numberofadmins( $parser, $raw = null ) {
632 return self::formatRaw(
633 SiteStats::numberingroup( 'sysop' ),
634 $raw,
635 $parser->getTargetLanguage()
636 );
637 }
638
639 public static function numberofedits( $parser, $raw = null ) {
640 return self::formatRaw( SiteStats::edits(), $raw, $parser->getTargetLanguage() );
641 }
642
643 public static function pagesinnamespace( $parser, $namespace = 0, $raw = null ) {
644 return self::formatRaw(
645 SiteStats::pagesInNs( intval( $namespace ) ),
646 $raw,
647 $parser->getTargetLanguage()
648 );
649 }
650
651 public static function numberingroup( $parser, $name = '', $raw = null ) {
652 return self::formatRaw(
653 SiteStats::numberingroup( strtolower( $name ) ),
654 $raw,
655 $parser->getTargetLanguage()
656 );
657 }
658
667 private static function makeTitle( Parser $parser, ?string $t ) {
668 if ( $t === null ) {
669 // For consistency with magic variable forms
670 $title = $parser->getTitle();
671 } else {
672 $title = Title::newFromText( $t );
673 }
674 return $title;
675 }
676
685 public static function namespace( $parser, $title = null ) {
686 $t = self::makeTitle( $parser, $title );
687 if ( $t === null ) {
688 return '';
689 }
690 return str_replace( '_', ' ', $t->getNsText() );
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 ) || $title->isExternal() ) {
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
1009 public static function language( $parser, $code = '', $inLanguage = '' ) {
1010 if ( $code === '' ) {
1011 $code = $parser->getTargetLanguage()->getCode();
1012 }
1013 if ( $inLanguage === '' ) {
1014 $inLanguage = LanguageNameUtils::AUTONYMS;
1015 }
1017 ->getLanguageNameUtils()
1018 ->getLanguageName( $code, $inLanguage );
1019 return $lang !== '' ? $lang : LanguageCode::bcp47( $code );
1020 }
1021
1033 public static function dir( Parser $parser, string $code = '', string $arg = '' ): string {
1034 static $magicWords = null;
1035 $languageFactory = MediaWikiServices::getInstance()->getLanguageFactory();
1036
1037 if ( $code === '' ) {
1038 $lang = $parser->getTargetLanguage();
1039 } else {
1040 if ( $arg !== '' ) {
1041 if ( $magicWords === null ) {
1042 $magicWords = $parser->getMagicWordFactory()->newArray( [ 'language_option_bcp47' ] );
1043 }
1044 if ( $magicWords->matchStartToEnd( $arg ) === 'language_option_bcp47' ) {
1045 // Prefer the BCP-47 interpretation of this code.
1046 $code = new Bcp47CodeValue( $code );
1047 }
1048 }
1049 try {
1050 $lang = $languageFactory->getLanguage( $code );
1051 } catch ( InvalidArgumentException $ex ) {
1052 $parser->addTrackingCategory( 'bad-language-code-category' );
1053 return 'ltr';
1054 }
1055 }
1056 return $lang->getDir();
1057 }
1058
1067 public static function bcp47( Parser $parser, string $code = '' ): string {
1068 if ( $code === '' ) {
1069 return $parser->getTargetLanguage()->toBcp47Code();
1070 } else {
1071 return LanguageCode::bcp47( $code );
1072 }
1073 }
1074
1084 public static function pad(
1085 $parser, $string, $length, $padding = '0', $direction = STR_PAD_RIGHT
1086 ) {
1087 $padding = $parser->killMarkers( $padding );
1088 $lengthOfPadding = mb_strlen( $padding );
1089 if ( $lengthOfPadding == 0 ) {
1090 return $string;
1091 }
1092
1093 # The remaining length to add counts down to 0 as padding is added
1094 $length = min( (int)$length, 500 ) - mb_strlen( $string );
1095 if ( $length <= 0 ) {
1096 // Nothing to add
1097 return $string;
1098 }
1099
1100 # $finalPadding is just $padding repeated enough times so that
1101 # mb_strlen( $string ) + mb_strlen( $finalPadding ) == $length
1102 $finalPadding = '';
1103 while ( $length > 0 ) {
1104 # If $length < $lengthofPadding, truncate $padding so we get the
1105 # exact length desired.
1106 $finalPadding .= mb_substr( $padding, 0, $length );
1107 $length -= $lengthOfPadding;
1108 }
1109
1110 if ( $direction == STR_PAD_LEFT ) {
1111 return $finalPadding . $string;
1112 } else {
1113 return $string . $finalPadding;
1114 }
1115 }
1116
1117 public static function padleft( $parser, $string = '', $length = 0, $padding = '0' ) {
1118 return self::pad( $parser, $string, $length, $padding, STR_PAD_LEFT );
1119 }
1120
1121 public static function padright( $parser, $string = '', $length = 0, $padding = '0' ) {
1122 return self::pad( $parser, $string, $length, $padding );
1123 }
1124
1130 public static function anchorencode( $parser, $text ) {
1131 $text = $parser->killMarkers( $text );
1132 $section = (string)substr( $parser->guessSectionNameFromWikiText( $text ), 1 );
1133 return Sanitizer::safeEncodeAttribute( $section );
1134 }
1135
1136 public static function special( $parser, $text ) {
1137 [ $page, $subpage ] = MediaWikiServices::getInstance()->getSpecialPageFactory()->
1138 resolveAlias( $text );
1139 if ( $page ) {
1140 $title = SpecialPage::getTitleFor( $page, $subpage );
1141 return $title->getPrefixedText();
1142 } else {
1143 // unknown special page, just use the given text as its title, if at all possible
1144 $title = Title::makeTitleSafe( NS_SPECIAL, $text );
1145 return $title ? $title->getPrefixedText() : self::special( $parser, 'Badtitle' );
1146 }
1147 }
1148
1149 public static function speciale( $parser, $text ) {
1150 return wfUrlencode( str_replace( ' ', '_', self::special( $parser, $text ) ) );
1151 }
1152
1161 public static function defaultsort( $parser, $text, $uarg = '' ) {
1162 static $magicWords = null;
1163 if ( $magicWords === null ) {
1164 $magicWords = $parser->getMagicWordFactory()->newArray(
1165 [ 'defaultsort_noerror', 'defaultsort_noreplace' ] );
1166 }
1167 $arg = $magicWords->matchStartToEnd( $uarg );
1168
1169 $text = trim( $text );
1170 if ( strlen( $text ) == 0 ) {
1171 return '';
1172 }
1173 $old = $parser->getOutput()->getPageProperty( 'defaultsort' );
1174 if ( $old === null || $arg !== 'defaultsort_noreplace' ) {
1175 $parser->getOutput()->setPageProperty( 'defaultsort', $text );
1176 }
1177
1178 if ( $old === null || $old == $text || $arg ) {
1179 return '';
1180 } else {
1181 $converter = $parser->getTargetLanguageConverter();
1182 return '<span class="error">' .
1183 $parser->msg( 'duplicate-defaultsort',
1184 // Message should be parsed, but these params should only be escaped.
1185 $converter->markNoConversion( wfEscapeWikiText( $old ) ),
1186 $converter->markNoConversion( wfEscapeWikiText( $text ) )
1187 )->text() .
1188 '</span>';
1189 }
1190 }
1191
1203 public static function filepath( $parser, $name = '', $argA = '', $argB = '' ) {
1204 $file = MediaWikiServices::getInstance()->getRepoGroup()->findFile( $name );
1205
1206 if ( $argA == 'nowiki' ) {
1207 // {{filepath: | option [| size] }}
1208 $isNowiki = true;
1209 $parsedWidthParam = $parser->parseWidthParam( $argB );
1210 } else {
1211 // {{filepath: [| size [|option]] }}
1212 $parsedWidthParam = $parser->parseWidthParam( $argA );
1213 $isNowiki = ( $argB == 'nowiki' );
1214 }
1215
1216 if ( $file ) {
1217 $url = $file->getFullUrl();
1218
1219 // If a size is requested...
1220 if ( count( $parsedWidthParam ) ) {
1221 $mto = $file->transform( $parsedWidthParam );
1222 // ... and we can
1223 if ( $mto && !$mto->isError() ) {
1224 // ... change the URL to point to a thumbnail.
1225 $urlUtils = MediaWikiServices::getInstance()->getUrlUtils();
1226 $url = $urlUtils->expand( $mto->getUrl(), PROTO_RELATIVE ) ?? false;
1227 }
1228 }
1229 if ( $isNowiki ) {
1230 return [ $url, 'nowiki' => true ];
1231 }
1232 return $url;
1233 } else {
1234 return '';
1235 }
1236 }
1237
1245 public static function tagObj( $parser, $frame, $args ) {
1246 if ( !count( $args ) ) {
1247 return '';
1248 }
1249 $tagName = strtolower( trim( $frame->expand( array_shift( $args ) ) ) );
1250 $processNowiki = $parser->tagNeedsNowikiStrippedInTagPF( $tagName ) ? PPFrame::PROCESS_NOWIKI : 0;
1251
1252 if ( count( $args ) ) {
1253 $inner = $frame->expand( array_shift( $args ), $processNowiki );
1254 } else {
1255 $inner = null;
1256 }
1257
1258 $attributes = [];
1259 foreach ( $args as $arg ) {
1260 $bits = $arg->splitArg();
1261 if ( strval( $bits['index'] ) === '' ) {
1262 $name = trim( $frame->expand( $bits['name'], PPFrame::STRIP_COMMENTS ) );
1263 $value = trim( $frame->expand( $bits['value'] ) );
1264 if ( preg_match( '/^(?:["\'](.+)["\']|""|\'\')$/s', $value, $m ) ) {
1265 $value = $m[1] ?? '';
1266 }
1267 $attributes[$name] = $value;
1268 }
1269 }
1270
1271 $stripList = $parser->getStripList();
1272 if ( !in_array( $tagName, $stripList ) ) {
1273 // we can't handle this tag (at least not now), so just re-emit it as an ordinary tag
1274 $attrText = '';
1275 foreach ( $attributes as $name => $value ) {
1276 $attrText .= ' ' . htmlspecialchars( $name ) .
1277 '="' . htmlspecialchars( $value, ENT_COMPAT ) . '"';
1278 }
1279 if ( $inner === null ) {
1280 return "<$tagName$attrText/>";
1281 }
1282 return "<$tagName$attrText>$inner</$tagName>";
1283 }
1284
1285 $params = [
1286 'name' => $tagName,
1287 'inner' => $inner,
1288 'attributes' => $attributes,
1289 'close' => "</$tagName>",
1290 ];
1291 return $parser->extensionSubstitution( $params, $frame );
1292 }
1293
1307 private static function getCachedRevisionObject( $parser, $title, $vary ) {
1308 if ( !$title ) {
1309 return null;
1310 }
1311
1312 $revisionRecord = null;
1313
1314 $isSelfReferential = $title->equals( $parser->getTitle() );
1315 if ( $isSelfReferential ) {
1316 // Revision is for the same title that is currently being parsed. Only use the last
1317 // saved revision, regardless of Parser::getRevisionId() or fake revision injection
1318 // callbacks against the current title.
1319
1320 // FIXME (T318278): the above is the intention, but doesn't
1321 // describe the actual current behavior of this code, since
1322 // ->isCurrent() for the last saved revision will return
1323 // false so we're going to fall through and end up calling
1324 // ->getCurrentRevisionRecordOfTitle().
1325 $parserRevisionRecord = $parser->getRevisionRecordObject();
1326 if ( $parserRevisionRecord && $parserRevisionRecord->isCurrent() ) {
1327 $revisionRecord = $parserRevisionRecord;
1328 }
1329 }
1330
1331 $parserOutput = $parser->getOutput();
1332 if ( !$revisionRecord ) {
1333 if (
1334 !$parser->isCurrentRevisionOfTitleCached( $title ) &&
1336 ) {
1337 return null; // not allowed
1338 }
1339 // Get the current revision, ignoring Parser::getRevisionId() being null/old
1340 $revisionRecord = $parser->fetchCurrentRevisionRecordOfTitle( $title );
1341 if ( !$revisionRecord ) {
1342 // Convert `false` error return to `null`
1343 $revisionRecord = null;
1344 }
1345 // Register dependency in templatelinks
1346 $parserOutput->addTemplate(
1347 $title,
1348 $revisionRecord ? $revisionRecord->getPageId() : 0,
1349 $revisionRecord ? $revisionRecord->getId() : 0
1350 );
1351 }
1352
1353 if ( $isSelfReferential ) {
1354 wfDebug( __METHOD__ . ": used current revision, setting $vary" );
1355 // Upon page save, the result of the parser function using this might change
1356 $parserOutput->setOutputFlag( $vary );
1357 if ( $vary === ParserOutputFlags::VARY_REVISION_SHA1 && $revisionRecord ) {
1358 try {
1359 $sha1 = $revisionRecord->getSha1();
1360 } catch ( RevisionAccessException $e ) {
1361 $sha1 = null;
1362 }
1363 $parserOutput->setRevisionUsedSha1Base36( $sha1 );
1364 }
1365 }
1366
1367 return $revisionRecord;
1368 }
1369
1377 public static function pageid( $parser, $title = null ) {
1378 $t = self::makeTitle( $parser, $title );
1379 if ( !$t ) {
1380 return '';
1381 } elseif ( !$t->canExist() || $t->isExternal() ) {
1382 return 0; // e.g. special page or interwiki link
1383 }
1384
1385 $parserOutput = $parser->getOutput();
1386
1387 if ( $t->equals( $parser->getTitle() ) ) {
1388 // Revision is for the same title that is currently being parsed.
1389 // Use the title from Parser in case a new page ID was injected into it.
1390 $parserOutput->setOutputFlag( ParserOutputFlags::VARY_PAGE_ID );
1391 $id = $parser->getTitle()->getArticleID();
1392 if ( $id ) {
1393 $parserOutput->setSpeculativePageIdUsed( $id );
1394 }
1395
1396 return $id;
1397 }
1398
1399 // Check the link cache for the title
1400 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
1401 $pdbk = $t->getPrefixedDBkey();
1402 $id = $linkCache->getGoodLinkID( $pdbk );
1403 if ( $id != 0 || $linkCache->isBadLink( $pdbk ) ) {
1404 $parserOutput->addLink( $t, $id );
1405
1406 return $id;
1407 }
1408
1409 // We need to load it from the DB, so mark expensive
1410 if ( $parser->incrementExpensiveFunctionCount() ) {
1411 $id = $t->getArticleID();
1412 $parserOutput->addLink( $t, $id );
1413
1414 return $id;
1415 }
1416
1417 return null;
1418 }
1419
1427 public static function revisionid( $parser, $title = null ) {
1428 $t = self::makeTitle( $parser, $title );
1429 if ( $t === null || $t->isExternal() ) {
1430 return '';
1431 }
1432
1433 $services = MediaWikiServices::getInstance();
1434 if (
1435 $t->equals( $parser->getTitle() ) &&
1436 $services->getMainConfig()->get( MainConfigNames::MiserMode ) &&
1437 !$parser->getOptions()->getInterfaceMessage() &&
1438 // @TODO: disallow this word on all namespaces (T235957)
1439 $services->getNamespaceInfo()->isSubject( $t->getNamespace() )
1440 ) {
1441 // Use a stub result instead of the actual revision ID in order to avoid
1442 // double parses on page save but still allow preview detection (T137900)
1443 if ( $parser->getRevisionId() || $parser->getOptions()->getSpeculativeRevId() ) {
1444 return '-';
1445 } else {
1446 $parser->getOutput()->setOutputFlag( ParserOutputFlags::VARY_REVISION_EXISTS );
1447 return '';
1448 }
1449 }
1450 // Fetch revision from cache/database and return the value.
1451 // Inform the edit saving system that getting the canonical output
1452 // after revision insertion requires a parse that used that exact
1453 // revision ID.
1454 if ( $t->equals( $parser->getTitle() ) && $title === null ) {
1455 // special handling for no-arg case: use speculative rev id
1456 // for current page.
1457 $parser->getOutput()->setOutputFlag( ParserOutputFlags::VARY_REVISION_ID );
1458 $id = $parser->getRevisionId();
1459 if ( $id === 0 ) {
1460 $rev = $parser->getRevisionRecordObject();
1461 if ( $rev ) {
1462 $id = $rev->getId();
1463 }
1464 }
1465 if ( !$id ) {
1466 $id = $parser->getOptions()->getSpeculativeRevId();
1467 if ( $id ) {
1468 $parser->getOutput()->setSpeculativeRevIdUsed( $id );
1469 }
1470 }
1471 return (string)$id;
1472 }
1473 $rev = self::getCachedRevisionObject( $parser, $t, ParserOutputFlags::VARY_REVISION_ID );
1474 return $rev ? $rev->getId() : '';
1475 }
1476
1477 private static function getRevisionTimestampSubstring(
1478 Parser $parser,
1479 Title $title,
1480 int $start,
1481 int $len,
1482 int $mtts
1483 ): string {
1484 // If fetching the revision timestamp of the current page, substitute the
1485 // speculative timestamp to be used when this revision is saved. This
1486 // avoids having to invalidate the cache immediately by assuming the "last
1487 // saved revision" will in fact be this one.
1488 // Don't do this for interface messages (eg, edit notices) however; in that
1489 // case fall through and use the actual timestamp of the last saved revision.
1490 if ( $title->equals( $parser->getTitle() ) && !$parser->getOptions()->getInterfaceMessage() ) {
1491 // Get the timezone-adjusted timestamp to be used for this revision
1492 $resNow = substr( $parser->getRevisionTimestamp(), $start, $len );
1493 // Possibly set vary-revision if there is not yet an associated revision
1494 if ( !$parser->getRevisionRecordObject() ) {
1495 // Get the timezone-adjusted timestamp $mtts seconds in the future.
1496 // This future is relative to the current time and not that of the
1497 // parser options. The rendered timestamp can be compared to that
1498 // of the timestamp specified by the parser options.
1499 $resThen = substr(
1500 $parser->getContentLanguage()->userAdjust( wfTimestamp( TS_MW, time() + $mtts ), '' ),
1501 $start,
1502 $len
1503 );
1504
1505 if ( $resNow !== $resThen ) {
1506 // Inform the edit saving system that getting the canonical output after
1507 // revision insertion requires a parse that used an actual revision timestamp
1508 $parser->getOutput()->setOutputFlag( ParserOutputFlags::VARY_REVISION_TIMESTAMP );
1509 }
1510 }
1511
1512 return $resNow;
1513 } else {
1514 $rev = self::getCachedRevisionObject( $parser, $title, ParserOutputFlags::VARY_REVISION_TIMESTAMP );
1515 if ( !$rev ) {
1516 return '';
1517 }
1518 $resNow = substr(
1519 $parser->getContentLanguage()->userAdjust( $rev->getTimestamp(), '' ), $start, $len
1520 );
1521 return $resNow;
1522 }
1523 }
1524
1532 public static function revisionday( $parser, $title = null ) {
1533 $t = self::makeTitle( $parser, $title );
1534 if ( $t === null || $t->isExternal() ) {
1535 return '';
1536 }
1537 return strval( (int)self::getRevisionTimestampSubstring(
1538 $parser, $t, 6, 2, self::MAX_TTS
1539 ) );
1540 }
1541
1549 public static function revisionday2( $parser, $title = null ) {
1550 $t = self::makeTitle( $parser, $title );
1551 if ( $t === null || $t->isExternal() ) {
1552 return '';
1553 }
1554 return self::getRevisionTimestampSubstring(
1555 $parser, $t, 6, 2, self::MAX_TTS
1556 );
1557 }
1558
1566 public static function revisionmonth( $parser, $title = null ) {
1567 $t = self::makeTitle( $parser, $title );
1568 if ( $t === null || $t->isExternal() ) {
1569 return '';
1570 }
1571 return self::getRevisionTimestampSubstring(
1572 $parser, $t, 4, 2, self::MAX_TTS
1573 );
1574 }
1575
1583 public static function revisionmonth1( $parser, $title = null ) {
1584 $t = self::makeTitle( $parser, $title );
1585 if ( $t === null || $t->isExternal() ) {
1586 return '';
1587 }
1588 return strval( (int)self::getRevisionTimestampSubstring(
1589 $parser, $t, 4, 2, self::MAX_TTS
1590 ) );
1591 }
1592
1600 public static function revisionyear( $parser, $title = null ) {
1601 $t = self::makeTitle( $parser, $title );
1602 if ( $t === null || $t->isExternal() ) {
1603 return '';
1604 }
1605 return self::getRevisionTimestampSubstring(
1606 $parser, $t, 0, 4, self::MAX_TTS
1607 );
1608 }
1609
1617 public static function revisiontimestamp( $parser, $title = null ) {
1618 $t = self::makeTitle( $parser, $title );
1619 if ( $t === null || $t->isExternal() ) {
1620 return '';
1621 }
1622 return self::getRevisionTimestampSubstring(
1623 $parser, $t, 0, 14, self::MAX_TTS
1624 );
1625 }
1626
1634 public static function revisionuser( $parser, $title = null ) {
1635 $t = self::makeTitle( $parser, $title );
1636 if ( $t === null || $t->isExternal() ) {
1637 return '';
1638 }
1639 // VARY_USER informs the edit saving system that getting the canonical
1640 // output after revision insertion requires a parse that used the
1641 // actual user ID.
1642 if ( $t->equals( $parser->getTitle() ) ) {
1643 // Fall back to Parser's "revision user" for the current title
1644 $parser->getOutput()->setOutputFlag( ParserOutputFlags::VARY_USER );
1645 // Note that getRevisionUser() can return null; we need to
1646 // be sure to cast this to (an empty) string, since returning
1647 // null means "magic variable not handled".
1648 return (string)$parser->getRevisionUser();
1649 }
1650 // Fetch revision from cache/database and return the value.
1651 $rev = self::getCachedRevisionObject( $parser, $t, ParserOutputFlags::VARY_USER );
1652 $user = ( $rev !== null ) ? $rev->getUser() : null;
1653 return $user ? $user->getName() : '';
1654 }
1655
1668 public static function cascadingsources( $parser, $title = '' ) {
1669 $titleObject = Title::newFromText( $title ) ?? $parser->getTitle();
1670 $restrictionStore = MediaWikiServices::getInstance()->getRestrictionStore();
1671 if ( $restrictionStore->areCascadeProtectionSourcesLoaded( $titleObject )
1673 ) {
1674 $names = [];
1675 $sources = $restrictionStore->getCascadeProtectionSources( $titleObject );
1676 $titleFormatter = MediaWikiServices::getInstance()->getTitleFormatter();
1677 foreach ( $sources[0] as $sourcePageIdentity ) {
1678 $names[] = $titleFormatter->getPrefixedText( $sourcePageIdentity );
1679 }
1680 return implode( '|', $names );
1681 }
1682 return '';
1683 }
1684}
1685
1687class_alias( CoreParserFunctions::class, 'CoreParserFunctions' );
const NS_USER
Definition Defines.php:67
const NS_FILE
Definition Defines.php:71
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:79
A service that provides utilities to do with language names and codes.
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:156
static plaintextParam( $plaintext)
Definition Message.php:1345
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 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 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:143
getTargetLanguageConverter()
Shorthand for getting a Language Converter for Target language.
Definition Parser.php:1647
markerSkipCallback( $s, callable $callback)
Call a callback function on all regions of the given text that are not inside strip markers,...
Definition Parser.php:6322
tagNeedsNowikiStrippedInTagPF(string $lowerTagName)
Definition Parser.php:3952
getMagicWordFactory()
Get the MagicWordFactory that this Parser is using.
Definition Parser.php:1228
setFunctionHook( $id, callable $callback, $flags=0)
Create a function, e.g.
Definition Parser.php:5027
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:4170
guessSectionNameFromWikiText( $text)
Try to guess the section anchor name based on a wikitext fragment presumably extracted from a heading...
Definition Parser.php:6214
isCurrentRevisionOfTitleCached(LinkTarget $link)
Definition Parser.php:3557
getRevisionId()
Get the ID of the revision we are parsing.
Definition Parser.php:6031
getContentLanguage()
Get the content language that this Parser is using.
Definition Parser.php:1238
parseWidthParam( $value, $parseHeight=true, bool $localized=false)
Parsed a width param of imagelink like 300px or 200x300px.
Definition Parser.php:6370
killMarkers( $text)
Remove any strip markers found in the given text.
Definition Parser.php:6353
getRevisionUser()
Get the name of the user that edited the last revision.
Definition Parser.php:6124
getTargetLanguage()
Get the target language for the content being parsed.
Definition Parser.php:1174
getStripList()
Get a list of strippable XML-like elements.
Definition Parser.php:1334
extensionSubstitution(array $params, PPFrame $frame, bool $processNowiki=false)
Return the text to be used for a given extension tag.
Definition Parser.php:3976
getRevisionRecordObject()
Get the revision record object for $this->mRevisionId.
Definition Parser.php:6041
getRevisionTimestamp()
Get the timestamp associated with the current revision, adjusted for the default server-local timesta...
Definition Parser.php:6096
doQuotes( $text)
Helper function for handleAllQuotes()
Definition Parser.php:1983
fetchCurrentRevisionRecordOfTitle(LinkTarget $link)
Fetch the current revision of a given title as a RevisionRecord.
Definition Parser.php:3527
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 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:94
hasFragment()
Whether the link target has a fragment.