MediaWiki master
CoreParserFunctions.php
Go to the documentation of this file.
1<?php
40use Wikimedia\Bcp47Code\Bcp47CodeValue;
41use Wikimedia\RemexHtml\Tokenizer\Attributes;
42use Wikimedia\RemexHtml\Tokenizer\PlainAttributes;
43
50 private const MAX_TTS = 900;
51
55 public const REGISTER_OPTIONS = [
56 // See documentation for the corresponding config options
57 MainConfigNames::AllowDisplayTitle,
58 MainConfigNames::AllowSlowParserFunctions,
59 ];
60
68 public static function register( Parser $parser, ServiceOptions $options ) {
69 $options->assertRequiredOptions( self::REGISTER_OPTIONS );
70 $allowDisplayTitle = $options->get( MainConfigNames::AllowDisplayTitle );
71 $allowSlowParserFunctions = $options->get( MainConfigNames::AllowSlowParserFunctions );
72
73 # Syntax for arguments (see Parser::setFunctionHook):
74 # "name for lookup in localized magic words array",
75 # function callback,
76 # optional Parser::SFH_NO_HASH to omit the hash from calls (e.g. {{int:...}}
77 # instead of {{#int:...}})
78 $noHashFunctions = [
79 'ns', 'nse', 'urlencode', 'lcfirst', 'ucfirst', 'lc', 'uc',
80 'localurl', 'localurle', 'fullurl', 'fullurle', 'canonicalurl',
81 'canonicalurle', 'formatnum', 'grammar', 'gender', 'plural', 'formal',
82 'bidi', 'numberingroup', 'language',
83 'padleft', 'padright', 'anchorencode', 'defaultsort', 'filepath',
84 'pagesincategory', 'pagesize', 'protectionlevel', 'protectionexpiry',
85 # The following are the "parser function" forms of magic
86 # variables defined in CoreMagicVariables. The no-args form will
87 # go through the magic variable code path (and be cached); the
88 # presence of arguments will cause the parser function form to
89 # be invoked. (Note that the actual implementation will pass
90 # a Parser object as first argument, in addition to the
91 # parser function parameters.)
92
93 # For this group, the first parameter to the parser function is
94 # "page title", and the no-args form (and the magic variable)
95 # defaults to "current page title".
96 'pagename', 'pagenamee',
97 'fullpagename', 'fullpagenamee',
98 'subpagename', 'subpagenamee',
99 'rootpagename', 'rootpagenamee',
100 'basepagename', 'basepagenamee',
101 'talkpagename', 'talkpagenamee',
102 'subjectpagename', 'subjectpagenamee',
103 'pageid', 'revisionid', 'revisionday',
104 'revisionday2', 'revisionmonth', 'revisionmonth1', 'revisionyear',
105 'revisiontimestamp',
106 'revisionuser',
107 'cascadingsources',
108 'namespace', 'namespacee', 'namespacenumber', 'talkspace', 'talkspacee',
109 'subjectspace', 'subjectspacee',
110
111 # More parser functions corresponding to CoreMagicVariables.
112 # For this group, the first parameter to the parser function is
113 # "raw" (uses the 'raw' format if present) and the no-args form
114 # (and the magic variable) defaults to 'not raw'.
115 'numberofarticles', 'numberoffiles',
116 'numberofusers',
117 'numberofactiveusers',
118 'numberofpages',
119 'numberofadmins',
120 'numberofedits',
121
122 # These magic words already contain the hash, and the no-args form
123 # is the same as passing an empty first argument
124 'bcp47',
125 'dir',
126 ];
127 foreach ( $noHashFunctions as $func ) {
128 $parser->setFunctionHook( $func, [ __CLASS__, $func ], Parser::SFH_NO_HASH );
129 }
130
131 $parser->setFunctionHook( 'int', [ __CLASS__, 'intFunction' ], Parser::SFH_NO_HASH );
132 $parser->setFunctionHook( 'special', [ __CLASS__, 'special' ] );
133 $parser->setFunctionHook( 'speciale', [ __CLASS__, 'speciale' ] );
134 $parser->setFunctionHook( 'tag', [ __CLASS__, 'tagObj' ], Parser::SFH_OBJECT_ARGS );
135 $parser->setFunctionHook( 'formatdate', [ __CLASS__, 'formatDate' ] );
136
137 if ( $allowDisplayTitle ) {
138 $parser->setFunctionHook(
139 'displaytitle',
140 [ __CLASS__, 'displaytitle' ],
141 Parser::SFH_NO_HASH
142 );
143 }
144 if ( $allowSlowParserFunctions ) {
145 $parser->setFunctionHook(
146 'pagesinnamespace',
147 [ __CLASS__, 'pagesinnamespace' ],
148 Parser::SFH_NO_HASH
149 );
150 }
151 }
152
159 public static function intFunction( $parser, $part1 = '', ...$params ) {
160 if ( strval( $part1 ) !== '' ) {
161 $message = wfMessage( $part1, $params )
162 ->inLanguage( $parser->getOptions()->getUserLangObj() );
163 return [ $message->plain(), 'noparse' => false ];
164 } else {
165 return [ 'found' => false ];
166 }
167 }
168
176 public static function formatDate( $parser, $date, $defaultPref = null ) {
177 $lang = $parser->getTargetLanguage();
178 $df = MediaWikiServices::getInstance()->getDateFormatterFactory()->get( $lang );
179
180 $date = trim( $date );
181
182 $pref = $parser->getOptions()->getDateFormat();
183
184 // Specify a different default date format other than the normal default
185 // if the user has 'default' for their setting
186 if ( $pref == 'default' && $defaultPref ) {
187 $pref = $defaultPref;
188 }
189
190 $date = $df->reformat( $pref, $date, [ 'match-whole' ] );
191 return $date;
192 }
193
194 public static function ns( $parser, $part1 = '' ) {
195 if ( intval( $part1 ) || $part1 == "0" ) {
196 $index = intval( $part1 );
197 } else {
198 $index = $parser->getContentLanguage()->getNsIndex( str_replace( ' ', '_', $part1 ) );
199 }
200 if ( $index !== false ) {
201 return $parser->getContentLanguage()->getFormattedNsText( $index );
202 } else {
203 return [ 'found' => false ];
204 }
205 }
206
207 public static function nse( $parser, $part1 = '' ) {
208 $ret = self::ns( $parser, $part1 );
209 if ( is_string( $ret ) ) {
210 $ret = wfUrlencode( str_replace( ' ', '_', $ret ) );
211 }
212 return $ret;
213 }
214
227 public static function urlencode( $parser, $s = '', $arg = null ) {
228 static $magicWords = null;
229 if ( $magicWords === null ) {
231 $parser->getMagicWordFactory()->newArray( [ 'url_path', 'url_query', 'url_wiki' ] );
232 }
233 switch ( $magicWords->matchStartToEnd( $arg ?? '' ) ) {
234 // Encode as though it's a wiki page, '_' for ' '.
235 case 'url_wiki':
236 $func = 'wfUrlencode';
237 $s = str_replace( ' ', '_', $s );
238 break;
239
240 // Encode for an HTTP Path, '%20' for ' '.
241 case 'url_path':
242 $func = 'rawurlencode';
243 break;
244
245 // Encode for HTTP query, '+' for ' '.
246 case 'url_query':
247 default:
248 $func = 'urlencode';
249 }
250 // See T105242, where the choice to kill markers and various
251 // other options were discussed.
252 return $func( $parser->killMarkers( $s ) );
253 }
254
255 public static function lcfirst( $parser, $s = '' ) {
256 return $parser->getContentLanguage()->lcfirst( $s );
257 }
258
259 public static function ucfirst( $parser, $s = '' ) {
260 return $parser->getContentLanguage()->ucfirst( $s );
261 }
262
268 public static function lc( $parser, $s = '' ) {
269 return $parser->markerSkipCallback( $s, [ $parser->getContentLanguage(), 'lc' ] );
270 }
271
277 public static function uc( $parser, $s = '' ) {
278 return $parser->markerSkipCallback( $s, [ $parser->getContentLanguage(), 'uc' ] );
279 }
280
281 public static function localurl( $parser, $s = '', $arg = null ) {
282 return self::urlFunction( 'getLocalURL', $s, $arg );
283 }
284
285 public static function localurle( $parser, $s = '', $arg = null ) {
286 $temp = self::urlFunction( 'getLocalURL', $s, $arg );
287 if ( !is_string( $temp ) ) {
288 return $temp;
289 } else {
290 return htmlspecialchars( $temp, ENT_COMPAT );
291 }
292 }
293
294 public static function fullurl( $parser, $s = '', $arg = null ) {
295 return self::urlFunction( 'getFullURL', $s, $arg );
296 }
297
298 public static function fullurle( $parser, $s = '', $arg = null ) {
299 $temp = self::urlFunction( 'getFullURL', $s, $arg );
300 if ( !is_string( $temp ) ) {
301 return $temp;
302 } else {
303 return htmlspecialchars( $temp, ENT_COMPAT );
304 }
305 }
306
307 public static function canonicalurl( $parser, $s = '', $arg = null ) {
308 return self::urlFunction( 'getCanonicalURL', $s, $arg );
309 }
310
311 public static function canonicalurle( $parser, $s = '', $arg = null ) {
312 $temp = self::urlFunction( 'getCanonicalURL', $s, $arg );
313 if ( !is_string( $temp ) ) {
314 return $temp;
315 } else {
316 return htmlspecialchars( $temp, ENT_COMPAT );
317 }
318 }
319
320 public static function urlFunction( $func, $s = '', $arg = null ) {
321 # Due to order of execution of a lot of bits, the values might be encoded
322 # before arriving here; if that's true, then the title can't be created
323 # and the variable will fail. If we can't get a decent title from the first
324 # attempt, url-decode and try for a second.
325 $title = Title::newFromText( $s ) ?? Title::newFromURL( urldecode( $s ) );
326 if ( $title !== null ) {
327 # Convert NS_MEDIA -> NS_FILE
328 if ( $title->inNamespace( NS_MEDIA ) ) {
329 $title = Title::makeTitle( NS_FILE, $title->getDBkey() );
330 }
331 if ( $arg !== null ) {
332 $text = $title->$func( $arg );
333 } else {
334 $text = $title->$func();
335 }
336 return $text;
337 } else {
338 return [ 'found' => false ];
339 }
340 }
341
348 public static function formatnum( $parser, $num = '', $arg = null ) {
349 if ( self::matchAgainstMagicword( $parser->getMagicWordFactory(), 'rawsuffix', $arg ) ) {
350 $func = [ $parser->getTargetLanguage(), 'parseFormattedNumber' ];
351 } elseif (
352 self::matchAgainstMagicword( $parser->getMagicWordFactory(), 'nocommafysuffix', $arg )
353 ) {
354 $func = [ $parser->getTargetLanguage(), 'formatNumNoSeparators' ];
355 $func = self::getLegacyFormatNum( $parser, $func );
356 } else {
357 $func = [ $parser->getTargetLanguage(), 'formatNum' ];
358 $func = self::getLegacyFormatNum( $parser, $func );
359 }
360 return $parser->markerSkipCallback( $num, $func );
361 }
362
369 private static function getLegacyFormatNum( $parser, $callback ) {
370 // For historic reasons, the formatNum parser function will
371 // take arguments which are not actually formatted numbers,
372 // which then trigger deprecation warnings in Language::formatNum*.
373 // Instead emit a tracking category instead to allow linting.
374 return static function ( $number ) use ( $parser, $callback ) {
375 $validNumberRe = '(-(?=[\d\.]))?(\d+|(?=\.\d))(\.\d*)?([Ee][-+]?\d+)?';
376 if (
377 !is_numeric( $number ) &&
378 $number !== (string)NAN &&
379 $number !== (string)INF &&
380 $number !== (string)-INF
381 ) {
382 $parser->addTrackingCategory( 'nonnumeric-formatnum' );
383 // Don't split on NAN/INF in the legacy case since they are
384 // likely to be found embedded inside non-numeric text.
385 return preg_replace_callback( "/{$validNumberRe}/", static function ( $m ) use ( $callback ) {
386 return call_user_func( $callback, $m[0] );
387 }, $number );
388 }
389 return call_user_func( $callback, $number );
390 };
391 }
392
399 public static function grammar( $parser, $case = '', $word = '' ) {
400 $word = $parser->killMarkers( $word );
401 return $parser->getTargetLanguage()->convertGrammar( $word, $case );
402 }
403
410 public static function gender( $parser, $username, ...$forms ) {
411 // Some shortcuts to avoid loading user data unnecessarily
412 if ( count( $forms ) === 0 ) {
413 return '';
414 } elseif ( count( $forms ) === 1 ) {
415 return $forms[0];
416 }
417
418 $username = trim( $username );
419
420 $userOptionsLookup = MediaWikiServices::getInstance()->getUserOptionsLookup();
421 $gender = $userOptionsLookup->getDefaultOption( 'gender' );
422
423 // allow prefix and normalize (e.g. "&#42;foo" -> "*foo" ).
424 $title = Title::newFromText( $username, NS_USER );
425
426 if ( $title && $title->inNamespace( NS_USER ) ) {
427 $username = $title->getText();
428 }
429
430 // check parameter, or use the ParserOptions if in interface message
431 $user = User::newFromName( $username );
432 $genderCache = MediaWikiServices::getInstance()->getGenderCache();
433 if ( $user ) {
434 $gender = $genderCache->getGenderOf( $user, __METHOD__ );
435 } elseif ( $username === '' && $parser->getOptions()->getInterfaceMessage() ) {
436 $gender = $genderCache->getGenderOf( $parser->getOptions()->getUserIdentity(), __METHOD__ );
437 }
438 $ret = $parser->getTargetLanguage()->gender( $gender, $forms );
439 return $ret;
440 }
441
448 public static function plural( $parser, $text = '', ...$forms ) {
449 $text = $parser->getTargetLanguage()->parseFormattedNumber( $text );
450 settype( $text, ctype_digit( $text ) ? 'int' : 'float' );
451 // @phan-suppress-next-line PhanTypeMismatchArgument Phan does not handle settype
452 return $parser->getTargetLanguage()->convertPlural( $text, $forms );
453 }
454
455 public static function formal( Parser $parser, string ...$forms ): string {
456 $index = $parser->getTargetLanguage()->getFormalityIndex();
457 return $forms[$index] ?? $forms[0];
458 }
459
465 public static function bidi( $parser, $text = '' ) {
466 return $parser->getTargetLanguage()->embedBidi( $text );
467 }
468
478 public static function displaytitle( $parser, $text = '', $uarg = '' ) {
479 $restrictDisplayTitle = MediaWikiServices::getInstance()->getMainConfig()
480 ->get( MainConfigNames::RestrictDisplayTitle );
481
482 static $magicWords = null;
483 if ( $magicWords === null ) {
484 $magicWords = $parser->getMagicWordFactory()->newArray(
485 [ 'displaytitle_noerror', 'displaytitle_noreplace' ] );
486 }
487 $arg = $magicWords->matchStartToEnd( $uarg );
488
489 // parse a limited subset of wiki markup (just the single quote items)
490 $text = $parser->doQuotes( $text );
491
492 // remove stripped text (e.g. the UNIQ-QINU stuff) that was generated by tag extensions/whatever
493 $text = $parser->killMarkers( $text );
494
495 // See T28547 for rationale for this processing.
496 // list of disallowed tags for DISPLAYTITLE
497 // these will be escaped even though they are allowed in normal wiki text
498 $bad = [ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'div', 'blockquote', 'ol', 'ul', 'li', 'hr',
499 'table', 'tr', 'th', 'td', 'dl', 'dd', 'caption', 'p', 'ruby', 'rb', 'rt', 'rtc', 'rp', 'br' ];
500
501 // disallow some styles that could be used to bypass $wgRestrictDisplayTitle
502 if ( $restrictDisplayTitle ) {
503 // This code is tested with the cases marked T28547 in
504 // parserTests.txt
505 $htmlTagsCallback = static function ( Attributes $attr ): Attributes {
506 $decoded = $attr->getValues();
507
508 if ( isset( $decoded['style'] ) ) {
509 // this is called later anyway, but we need it right now for the regexes below to be safe
510 // calling it twice doesn't hurt
511 $decoded['style'] = Sanitizer::checkCss( $decoded['style'] );
512
513 if ( preg_match( '/(display|user-select|visibility)\s*:/i', $decoded['style'] ) ) {
514 $decoded['style'] = '/* attempt to bypass $wgRestrictDisplayTitle */';
515 }
516 }
517
518 return new PlainAttributes( $decoded );
519 };
520 } else {
521 $htmlTagsCallback = null;
522 }
523
524 // only requested titles that normalize to the actual title are allowed through
525 // if $wgRestrictDisplayTitle is true (it is by default)
526 // mimic the escaping process that occurs in OutputPage::setPageTitle
527 $text = Sanitizer::removeSomeTags( $text, [
528 'attrCallback' => $htmlTagsCallback,
529 'removeTags' => $bad,
530 ] );
531 $title = Title::newFromText( Sanitizer::stripAllTags( $text ) );
532 // Decode entities in $text the same way that Title::newFromText does
533 $filteredText = Sanitizer::decodeCharReferencesAndNormalize( $text );
534
535 if ( !$restrictDisplayTitle ||
536 ( $title instanceof Title
537 && !$title->hasFragment()
538 && $title->equals( $parser->getTitle() ) )
539 ) {
540 $old = $parser->getOutput()->getPageProperty( 'displaytitle' );
541 if ( $old === null || $arg !== 'displaytitle_noreplace' ) {
542 $parser->getOutput()->setDisplayTitle( $text );
543 }
544 if ( $old !== null && $old !== $text && !$arg ) {
545
546 $converter = $parser->getTargetLanguageConverter();
547 return '<span class="error">' .
548 $parser->msg( 'duplicate-displaytitle',
549 // Message should be parsed, but these params should only be escaped.
550 $converter->markNoConversion( wfEscapeWikiText( $old ) ),
551 $converter->markNoConversion( wfEscapeWikiText( $filteredText ) )
552 )->text() .
553 '</span>';
554 } else {
555 return '';
556 }
557 } else {
558 $parser->getOutput()->addWarningMsg(
559 'restricted-displaytitle',
560 // Message should be parsed, but this param should only be escaped.
561 Message::plaintextParam( $filteredText )
562 );
563 $parser->addTrackingCategory( 'restricted-displaytitle-ignored' );
564 }
565 }
566
576 private static function matchAgainstMagicword(
577 MagicWordFactory $magicWordFactory, $magicword, $value
578 ) {
579 $value = trim( strval( $value ) );
580 if ( $value === '' ) {
581 return false;
582 }
583 $mwObject = $magicWordFactory->get( $magicword );
584 return $mwObject->matchStartToEnd( $value );
585 }
586
596 public static function formatRaw(
597 $num, $raw, $language, MagicWordFactory $magicWordFactory = null
598 ) {
599 if ( $raw !== null && $raw !== '' ) {
600 if ( !$magicWordFactory ) {
601 $magicWordFactory = MediaWikiServices::getInstance()->getMagicWordFactory();
602 }
603 if ( self::matchAgainstMagicword( $magicWordFactory, 'rawsuffix', $raw ) ) {
604 return (string)$num;
605 }
606 }
607 return $language->formatNum( $num );
608 }
609
610 public static function numberofpages( $parser, $raw = null ) {
611 return self::formatRaw( SiteStats::pages(), $raw, $parser->getTargetLanguage() );
612 }
613
614 public static function numberofusers( $parser, $raw = null ) {
615 return self::formatRaw( SiteStats::users(), $raw, $parser->getTargetLanguage() );
616 }
617
618 public static function numberofactiveusers( $parser, $raw = null ) {
619 return self::formatRaw( SiteStats::activeUsers(), $raw, $parser->getTargetLanguage() );
620 }
621
622 public static function numberofarticles( $parser, $raw = null ) {
623 return self::formatRaw( SiteStats::articles(), $raw, $parser->getTargetLanguage() );
624 }
625
626 public static function numberoffiles( $parser, $raw = null ) {
627 return self::formatRaw( SiteStats::images(), $raw, $parser->getTargetLanguage() );
628 }
629
630 public static function numberofadmins( $parser, $raw = null ) {
631 return self::formatRaw(
632 SiteStats::numberingroup( 'sysop' ),
633 $raw,
634 $parser->getTargetLanguage()
635 );
636 }
637
638 public static function numberofedits( $parser, $raw = null ) {
639 return self::formatRaw( SiteStats::edits(), $raw, $parser->getTargetLanguage() );
640 }
641
642 public static function pagesinnamespace( $parser, $namespace = 0, $raw = null ) {
643 return self::formatRaw(
644 SiteStats::pagesInNs( intval( $namespace ) ),
645 $raw,
646 $parser->getTargetLanguage()
647 );
648 }
649
650 public static function numberingroup( $parser, $name = '', $raw = null ) {
651 return self::formatRaw(
652 SiteStats::numberingroup( strtolower( $name ) ),
653 $raw,
654 $parser->getTargetLanguage()
655 );
656 }
657
666 private static function makeTitle( Parser $parser, ?string $t ) {
667 if ( $t === null ) {
668 // For consistency with magic variable forms
669 $title = $parser->getTitle();
670 } else {
671 $title = Title::newFromText( $t );
672 }
673 return $title;
674 }
675
684 public static function namespace( $parser, $title = null ) {
685 $t = self::makeTitle( $parser, $title );
686 if ( $t === null ) {
687 return '';
688 }
689 return str_replace( '_', ' ', $t->getNsText() );
690 }
691
692 public static function namespacee( $parser, $title = null ) {
693 $t = self::makeTitle( $parser, $title );
694 if ( $t === null ) {
695 return '';
696 }
697 return wfUrlencode( $t->getNsText() );
698 }
699
700 public static function namespacenumber( $parser, $title = null ) {
701 $t = self::makeTitle( $parser, $title );
702 if ( $t === null ) {
703 return '';
704 }
705 return (string)$t->getNamespace();
706 }
707
708 public static function talkspace( $parser, $title = null ) {
709 $t = self::makeTitle( $parser, $title );
710 if ( $t === null || !$t->canHaveTalkPage() ) {
711 return '';
712 }
713 return str_replace( '_', ' ', $t->getTalkNsText() );
714 }
715
716 public static function talkspacee( $parser, $title = null ) {
717 $t = self::makeTitle( $parser, $title );
718 if ( $t === null || !$t->canHaveTalkPage() ) {
719 return '';
720 }
721 return wfUrlencode( $t->getTalkNsText() );
722 }
723
724 public static function subjectspace( $parser, $title = null ) {
725 $t = self::makeTitle( $parser, $title );
726 if ( $t === null ) {
727 return '';
728 }
729 return str_replace( '_', ' ', $t->getSubjectNsText() );
730 }
731
732 public static function subjectspacee( $parser, $title = null ) {
733 $t = self::makeTitle( $parser, $title );
734 if ( $t === null ) {
735 return '';
736 }
737 return wfUrlencode( $t->getSubjectNsText() );
738 }
739
747 public static function pagename( $parser, $title = null ) {
748 $t = self::makeTitle( $parser, $title );
749 if ( $t === null ) {
750 return '';
751 }
752 return wfEscapeWikiText( $t->getText() );
753 }
754
755 public static function pagenamee( $parser, $title = null ) {
756 $t = self::makeTitle( $parser, $title );
757 if ( $t === null ) {
758 return '';
759 }
760 return wfEscapeWikiText( $t->getPartialURL() );
761 }
762
763 public static function fullpagename( $parser, $title = null ) {
764 $t = self::makeTitle( $parser, $title );
765 if ( $t === null ) {
766 return '';
767 }
768 return wfEscapeWikiText( $t->getPrefixedText() );
769 }
770
771 public static function fullpagenamee( $parser, $title = null ) {
772 $t = self::makeTitle( $parser, $title );
773 if ( $t === null ) {
774 return '';
775 }
776 return wfEscapeWikiText( $t->getPrefixedURL() );
777 }
778
779 public static function subpagename( $parser, $title = null ) {
780 $t = self::makeTitle( $parser, $title );
781 if ( $t === null ) {
782 return '';
783 }
784 return wfEscapeWikiText( $t->getSubpageText() );
785 }
786
787 public static function subpagenamee( $parser, $title = null ) {
788 $t = self::makeTitle( $parser, $title );
789 if ( $t === null ) {
790 return '';
791 }
792 return wfEscapeWikiText( $t->getSubpageUrlForm() );
793 }
794
795 public static function rootpagename( $parser, $title = null ) {
796 $t = self::makeTitle( $parser, $title );
797 if ( $t === null ) {
798 return '';
799 }
800 return wfEscapeWikiText( $t->getRootText() );
801 }
802
803 public static function rootpagenamee( $parser, $title = null ) {
804 $t = self::makeTitle( $parser, $title );
805 if ( $t === null ) {
806 return '';
807 }
808 return wfEscapeWikiText( wfUrlencode( str_replace( ' ', '_', $t->getRootText() ) ) );
809 }
810
811 public static function basepagename( $parser, $title = null ) {
812 $t = self::makeTitle( $parser, $title );
813 if ( $t === null ) {
814 return '';
815 }
816 return wfEscapeWikiText( $t->getBaseText() );
817 }
818
819 public static function basepagenamee( $parser, $title = null ) {
820 $t = self::makeTitle( $parser, $title );
821 if ( $t === null ) {
822 return '';
823 }
824 return wfEscapeWikiText( wfUrlencode( str_replace( ' ', '_', $t->getBaseText() ) ) );
825 }
826
827 public static function talkpagename( $parser, $title = null ) {
828 $t = self::makeTitle( $parser, $title );
829 if ( $t === null || !$t->canHaveTalkPage() ) {
830 return '';
831 }
832 return wfEscapeWikiText( $t->getTalkPage()->getPrefixedText() );
833 }
834
835 public static function talkpagenamee( $parser, $title = null ) {
836 $t = self::makeTitle( $parser, $title );
837 if ( $t === null || !$t->canHaveTalkPage() ) {
838 return '';
839 }
840 return wfEscapeWikiText( $t->getTalkPage()->getPrefixedURL() );
841 }
842
843 public static function subjectpagename( $parser, $title = null ) {
844 $t = self::makeTitle( $parser, $title );
845 if ( $t === null ) {
846 return '';
847 }
848 return wfEscapeWikiText( $t->getSubjectPage()->getPrefixedText() );
849 }
850
851 public static function subjectpagenamee( $parser, $title = null ) {
852 $t = self::makeTitle( $parser, $title );
853 if ( $t === null ) {
854 return '';
855 }
856 return wfEscapeWikiText( $t->getSubjectPage()->getPrefixedURL() );
857 }
858
869 public static function pagesincategory( $parser, $name = '', $arg1 = '', $arg2 = '' ) {
870 static $magicWords = null;
871 if ( $magicWords === null ) {
872 $magicWords = $parser->getMagicWordFactory()->newArray( [
873 'pagesincategory_all',
874 'pagesincategory_pages',
875 'pagesincategory_subcats',
876 'pagesincategory_files'
877 ] );
878 }
879 static $cache = [];
880
881 // split the given option to its variable
882 if ( self::matchAgainstMagicword( $parser->getMagicWordFactory(), 'rawsuffix', $arg1 ) ) {
883 // {{pagesincategory:|raw[|type]}}
884 $raw = $arg1;
885 $type = $magicWords->matchStartToEnd( $arg2 );
886 } else {
887 // {{pagesincategory:[|type[|raw]]}}
888 $type = $magicWords->matchStartToEnd( $arg1 );
889 $raw = $arg2;
890 }
891 if ( !$type ) { // backward compatibility
892 $type = 'pagesincategory_all';
893 }
894
895 $title = Title::makeTitleSafe( NS_CATEGORY, $name );
896 if ( !$title ) { # invalid title
897 return self::formatRaw( 0, $raw, $parser->getTargetLanguage() );
898 }
899 $languageConverter = MediaWikiServices::getInstance()
900 ->getLanguageConverterFactory()
901 ->getLanguageConverter( $parser->getContentLanguage() );
902 $languageConverter->findVariantLink( $name, $title, true );
903
904 // Normalize name for cache
905 $name = $title->getDBkey();
906
907 if ( !isset( $cache[$name] ) ) {
908 $category = Category::newFromTitle( $title );
909
910 $allCount = $subcatCount = $fileCount = $pageCount = 0;
911 if ( $parser->incrementExpensiveFunctionCount() ) {
912 $allCount = $category->getMemberCount();
913 $subcatCount = $category->getSubcatCount();
914 $fileCount = $category->getFileCount();
915 $pageCount = $category->getPageCount( Category::COUNT_CONTENT_PAGES );
916 }
917 $cache[$name]['pagesincategory_all'] = $allCount;
918 $cache[$name]['pagesincategory_pages'] = $pageCount;
919 $cache[$name]['pagesincategory_subcats'] = $subcatCount;
920 $cache[$name]['pagesincategory_files'] = $fileCount;
921 }
922
923 $count = $cache[$name][$type];
924 return self::formatRaw( $count, $raw, $parser->getTargetLanguage() );
925 }
926
936 public static function pagesize( $parser, $page = '', $raw = null ) {
937 $title = Title::newFromText( $page );
938
939 if ( !is_object( $title ) || $title->isExternal() ) {
940 return self::formatRaw( 0, $raw, $parser->getTargetLanguage() );
941 }
942
943 // fetch revision from cache/database and return the value
944 $rev = self::getCachedRevisionObject( $parser, $title, ParserOutputFlags::VARY_REVISION_SHA1 );
945 $length = $rev ? $rev->getSize() : 0;
946 if ( $length === null ) {
947 // We've had bugs where rev_len was not being recorded for empty pages, see T135414
948 $length = 0;
949 }
950 return self::formatRaw( $length, $raw, $parser->getTargetLanguage() );
951 }
952
965 public static function protectionlevel( $parser, $type = '', $title = '' ) {
966 $titleObject = Title::newFromText( $title ) ?? $parser->getTitle();
967 $restrictionStore = MediaWikiServices::getInstance()->getRestrictionStore();
968 if ( $restrictionStore->areRestrictionsLoaded( $titleObject ) || $parser->incrementExpensiveFunctionCount() ) {
969 $restrictions = $restrictionStore->getRestrictions( $titleObject, strtolower( $type ) );
970 # RestrictionStore::getRestrictions returns an array, its possible it may have
971 # multiple values in the future
972 return implode( ',', $restrictions );
973 }
974 return '';
975 }
976
989 public static function protectionexpiry( $parser, $type = '', $title = '' ) {
990 $titleObject = Title::newFromText( $title ) ?? $parser->getTitle();
991 $restrictionStore = MediaWikiServices::getInstance()->getRestrictionStore();
992 if ( $restrictionStore->areRestrictionsLoaded( $titleObject ) || $parser->incrementExpensiveFunctionCount() ) {
993 // getRestrictionExpiry() returns null on invalid type; trying to
994 // match protectionlevel() function that returns empty string instead
995 return $restrictionStore->getRestrictionExpiry( $titleObject, strtolower( $type ) ) ?? '';
996 }
997 return '';
998 }
999
1008 public static function language( $parser, $code = '', $inLanguage = '' ) {
1009 if ( $code === '' ) {
1010 $code = $parser->getTargetLanguage()->getCode();
1011 }
1012 if ( $inLanguage === '' ) {
1013 $inLanguage = LanguageNameUtils::AUTONYMS;
1014 }
1015 $lang = MediaWikiServices::getInstance()
1016 ->getLanguageNameUtils()
1017 ->getLanguageName( $code, $inLanguage );
1018 return $lang !== '' ? $lang : LanguageCode::bcp47( $code );
1019 }
1020
1032 public static function dir( Parser $parser, string $code = '', string $arg = '' ): string {
1033 static $magicWords = null;
1034 $languageFactory = MediaWikiServices::getInstance()->getLanguageFactory();
1035
1036 if ( $code === '' ) {
1037 $lang = $parser->getTargetLanguage();
1038 } else {
1039 if ( $arg !== '' ) {
1040 if ( $magicWords === null ) {
1041 $magicWords = $parser->getMagicWordFactory()->newArray( [ 'language_option_bcp47' ] );
1042 }
1043 if ( $magicWords->matchStartToEnd( $arg ) === 'language_option_bcp47' ) {
1044 // Prefer the BCP-47 interpretation of this code.
1045 $code = new Bcp47CodeValue( $code );
1046 }
1047 }
1048 try {
1049 $lang = $languageFactory->getLanguage( $code );
1050 } catch ( InvalidArgumentException $ex ) {
1051 $parser->addTrackingCategory( 'bad-language-code-category' );
1052 return 'ltr';
1053 }
1054 }
1055 return $lang->getDir();
1056 }
1057
1066 public static function bcp47( Parser $parser, string $code = '' ): string {
1067 if ( $code === '' ) {
1068 return $parser->getTargetLanguage()->toBcp47Code();
1069 } else {
1070 return LanguageCode::bcp47( $code );
1071 }
1072 }
1073
1083 public static function pad(
1084 $parser, $string, $length, $padding = '0', $direction = STR_PAD_RIGHT
1085 ) {
1086 $padding = $parser->killMarkers( $padding );
1087 $lengthOfPadding = mb_strlen( $padding );
1088 if ( $lengthOfPadding == 0 ) {
1089 return $string;
1090 }
1091
1092 # The remaining length to add counts down to 0 as padding is added
1093 $length = min( (int)$length, 500 ) - mb_strlen( $string );
1094 if ( $length <= 0 ) {
1095 // Nothing to add
1096 return $string;
1097 }
1098
1099 # $finalPadding is just $padding repeated enough times so that
1100 # mb_strlen( $string ) + mb_strlen( $finalPadding ) == $length
1101 $finalPadding = '';
1102 while ( $length > 0 ) {
1103 # If $length < $lengthofPadding, truncate $padding so we get the
1104 # exact length desired.
1105 $finalPadding .= mb_substr( $padding, 0, $length );
1106 $length -= $lengthOfPadding;
1107 }
1108
1109 if ( $direction == STR_PAD_LEFT ) {
1110 return $finalPadding . $string;
1111 } else {
1112 return $string . $finalPadding;
1113 }
1114 }
1115
1116 public static function padleft( $parser, $string = '', $length = 0, $padding = '0' ) {
1117 return self::pad( $parser, $string, $length, $padding, STR_PAD_LEFT );
1118 }
1119
1120 public static function padright( $parser, $string = '', $length = 0, $padding = '0' ) {
1121 return self::pad( $parser, $string, $length, $padding );
1122 }
1123
1129 public static function anchorencode( $parser, $text ) {
1130 $text = $parser->killMarkers( $text );
1131 $section = (string)substr( $parser->guessSectionNameFromWikiText( $text ), 1 );
1132 return Sanitizer::safeEncodeAttribute( $section );
1133 }
1134
1135 public static function special( $parser, $text ) {
1136 [ $page, $subpage ] = MediaWikiServices::getInstance()->getSpecialPageFactory()->
1137 resolveAlias( $text );
1138 if ( $page ) {
1139 $title = SpecialPage::getTitleFor( $page, $subpage );
1140 return $title->getPrefixedText();
1141 } else {
1142 // unknown special page, just use the given text as its title, if at all possible
1143 $title = Title::makeTitleSafe( NS_SPECIAL, $text );
1144 return $title ? $title->getPrefixedText() : self::special( $parser, 'Badtitle' );
1145 }
1146 }
1147
1148 public static function speciale( $parser, $text ) {
1149 return wfUrlencode( str_replace( ' ', '_', self::special( $parser, $text ) ) );
1150 }
1151
1160 public static function defaultsort( $parser, $text, $uarg = '' ) {
1161 static $magicWords = null;
1162 if ( $magicWords === null ) {
1163 $magicWords = $parser->getMagicWordFactory()->newArray(
1164 [ 'defaultsort_noerror', 'defaultsort_noreplace' ] );
1165 }
1166 $arg = $magicWords->matchStartToEnd( $uarg );
1167
1168 $text = trim( $text );
1169 if ( strlen( $text ) == 0 ) {
1170 return '';
1171 }
1172 $old = $parser->getOutput()->getPageProperty( 'defaultsort' );
1173 if ( $old === null || $arg !== 'defaultsort_noreplace' ) {
1174 $parser->getOutput()->setPageProperty( 'defaultsort', $text );
1175 }
1176
1177 if ( $old === null || $old == $text || $arg ) {
1178 return '';
1179 } else {
1180 $converter = $parser->getTargetLanguageConverter();
1181 return '<span class="error">' .
1182 $parser->msg( 'duplicate-defaultsort',
1183 // Message should be parsed, but these params should only be escaped.
1184 $converter->markNoConversion( wfEscapeWikiText( $old ) ),
1185 $converter->markNoConversion( wfEscapeWikiText( $text ) )
1186 )->text() .
1187 '</span>';
1188 }
1189 }
1190
1202 public static function filepath( $parser, $name = '', $argA = '', $argB = '' ) {
1203 $file = MediaWikiServices::getInstance()->getRepoGroup()->findFile( $name );
1204
1205 if ( $argA == 'nowiki' ) {
1206 // {{filepath: | option [| size] }}
1207 $isNowiki = true;
1208 $parsedWidthParam = $parser->parseWidthParam( $argB );
1209 } else {
1210 // {{filepath: [| size [|option]] }}
1211 $parsedWidthParam = $parser->parseWidthParam( $argA );
1212 $isNowiki = ( $argB == 'nowiki' );
1213 }
1214
1215 if ( $file ) {
1216 $url = $file->getFullUrl();
1217
1218 // If a size is requested...
1219 if ( count( $parsedWidthParam ) ) {
1220 $mto = $file->transform( $parsedWidthParam );
1221 // ... and we can
1222 if ( $mto && !$mto->isError() ) {
1223 // ... change the URL to point to a thumbnail.
1224 $url = wfExpandUrl( $mto->getUrl(), PROTO_RELATIVE );
1225 }
1226 }
1227 if ( $isNowiki ) {
1228 return [ $url, 'nowiki' => true ];
1229 }
1230 return $url;
1231 } else {
1232 return '';
1233 }
1234 }
1235
1243 public static function tagObj( $parser, $frame, $args ) {
1244 if ( !count( $args ) ) {
1245 return '';
1246 }
1247 $tagName = strtolower( trim( $frame->expand( array_shift( $args ) ) ) );
1248 $processNowiki = $parser->tagNeedsNowikiStrippedInTagPF( $tagName ) ? PPFrame::PROCESS_NOWIKI : 0;
1249
1250 if ( count( $args ) ) {
1251 $inner = $frame->expand( array_shift( $args ), $processNowiki );
1252 } else {
1253 $inner = null;
1254 }
1255
1256 $attributes = [];
1257 foreach ( $args as $arg ) {
1258 $bits = $arg->splitArg();
1259 if ( strval( $bits['index'] ) === '' ) {
1260 $name = trim( $frame->expand( $bits['name'], PPFrame::STRIP_COMMENTS ) );
1261 $value = trim( $frame->expand( $bits['value'] ) );
1262 if ( preg_match( '/^(?:["\'](.+)["\']|""|\'\')$/s', $value, $m ) ) {
1263 $value = $m[1] ?? '';
1264 }
1265 $attributes[$name] = $value;
1266 }
1267 }
1268
1269 $stripList = $parser->getStripList();
1270 if ( !in_array( $tagName, $stripList ) ) {
1271 // we can't handle this tag (at least not now), so just re-emit it as an ordinary tag
1272 $attrText = '';
1273 foreach ( $attributes as $name => $value ) {
1274 $attrText .= ' ' . htmlspecialchars( $name ) .
1275 '="' . htmlspecialchars( $value, ENT_COMPAT ) . '"';
1276 }
1277 if ( $inner === null ) {
1278 return "<$tagName$attrText/>";
1279 }
1280 return "<$tagName$attrText>$inner</$tagName>";
1281 }
1282
1283 $params = [
1284 'name' => $tagName,
1285 'inner' => $inner,
1286 'attributes' => $attributes,
1287 'close' => "</$tagName>",
1288 ];
1289 return $parser->extensionSubstitution( $params, $frame );
1290 }
1291
1305 private static function getCachedRevisionObject( $parser, $title, $vary ) {
1306 if ( !$title ) {
1307 return null;
1308 }
1309
1310 $revisionRecord = null;
1311
1312 $isSelfReferential = $title->equals( $parser->getTitle() );
1313 if ( $isSelfReferential ) {
1314 // Revision is for the same title that is currently being parsed. Only use the last
1315 // saved revision, regardless of Parser::getRevisionId() or fake revision injection
1316 // callbacks against the current title.
1317
1318 // FIXME (T318278): the above is the intention, but doesn't
1319 // describe the actual current behavior of this code, since
1320 // ->isCurrent() for the last saved revision will return
1321 // false so we're going to fall through and end up calling
1322 // ->getCurrentRevisionRecordOfTitle().
1323 $parserRevisionRecord = $parser->getRevisionRecordObject();
1324 if ( $parserRevisionRecord && $parserRevisionRecord->isCurrent() ) {
1325 $revisionRecord = $parserRevisionRecord;
1326 }
1327 }
1328
1329 $parserOutput = $parser->getOutput();
1330 if ( !$revisionRecord ) {
1331 if (
1332 !$parser->isCurrentRevisionOfTitleCached( $title ) &&
1334 ) {
1335 return null; // not allowed
1336 }
1337 // Get the current revision, ignoring Parser::getRevisionId() being null/old
1338 $revisionRecord = $parser->fetchCurrentRevisionRecordOfTitle( $title );
1339 if ( !$revisionRecord ) {
1340 // Convert `false` error return to `null`
1341 $revisionRecord = null;
1342 }
1343 // Register dependency in templatelinks
1344 $parserOutput->addTemplate(
1345 $title,
1346 $revisionRecord ? $revisionRecord->getPageId() : 0,
1347 $revisionRecord ? $revisionRecord->getId() : 0
1348 );
1349 }
1350
1351 if ( $isSelfReferential ) {
1352 wfDebug( __METHOD__ . ": used current revision, setting $vary" );
1353 // Upon page save, the result of the parser function using this might change
1354 $parserOutput->setOutputFlag( $vary );
1355 if ( $vary === ParserOutputFlags::VARY_REVISION_SHA1 && $revisionRecord ) {
1356 try {
1357 $sha1 = $revisionRecord->getSha1();
1358 } catch ( RevisionAccessException $e ) {
1359 $sha1 = null;
1360 }
1361 $parserOutput->setRevisionUsedSha1Base36( $sha1 );
1362 }
1363 }
1364
1365 return $revisionRecord;
1366 }
1367
1375 public static function pageid( $parser, $title = null ) {
1376 $t = self::makeTitle( $parser, $title );
1377 if ( !$t ) {
1378 return '';
1379 } elseif ( !$t->canExist() || $t->isExternal() ) {
1380 return 0; // e.g. special page or interwiki link
1381 }
1382
1383 $parserOutput = $parser->getOutput();
1384
1385 if ( $t->equals( $parser->getTitle() ) ) {
1386 // Revision is for the same title that is currently being parsed.
1387 // Use the title from Parser in case a new page ID was injected into it.
1388 $parserOutput->setOutputFlag( ParserOutputFlags::VARY_PAGE_ID );
1389 $id = $parser->getTitle()->getArticleID();
1390 if ( $id ) {
1391 $parserOutput->setSpeculativePageIdUsed( $id );
1392 }
1393
1394 return $id;
1395 }
1396
1397 // Check the link cache for the title
1398 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
1399 $pdbk = $t->getPrefixedDBkey();
1400 $id = $linkCache->getGoodLinkID( $pdbk );
1401 if ( $id != 0 || $linkCache->isBadLink( $pdbk ) ) {
1402 $parserOutput->addLink( $t, $id );
1403
1404 return $id;
1405 }
1406
1407 // We need to load it from the DB, so mark expensive
1408 if ( $parser->incrementExpensiveFunctionCount() ) {
1409 $id = $t->getArticleID();
1410 $parserOutput->addLink( $t, $id );
1411
1412 return $id;
1413 }
1414
1415 return null;
1416 }
1417
1425 public static function revisionid( $parser, $title = null ) {
1426 $t = self::makeTitle( $parser, $title );
1427 if ( $t === null || $t->isExternal() ) {
1428 return '';
1429 }
1430
1431 $services = MediaWikiServices::getInstance();
1432 if (
1433 $t->equals( $parser->getTitle() ) &&
1434 $services->getMainConfig()->get( MainConfigNames::MiserMode ) &&
1435 !$parser->getOptions()->getInterfaceMessage() &&
1436 // @TODO: disallow this word on all namespaces (T235957)
1437 $services->getNamespaceInfo()->isSubject( $t->getNamespace() )
1438 ) {
1439 // Use a stub result instead of the actual revision ID in order to avoid
1440 // double parses on page save but still allow preview detection (T137900)
1441 if ( $parser->getRevisionId() || $parser->getOptions()->getSpeculativeRevId() ) {
1442 return '-';
1443 } else {
1444 $parser->getOutput()->setOutputFlag( ParserOutputFlags::VARY_REVISION_EXISTS );
1445 return '';
1446 }
1447 }
1448 // Fetch revision from cache/database and return the value.
1449 // Inform the edit saving system that getting the canonical output
1450 // after revision insertion requires a parse that used that exact
1451 // revision ID.
1452 if ( $t->equals( $parser->getTitle() ) && $title === null ) {
1453 // special handling for no-arg case: use speculative rev id
1454 // for current page.
1455 $parser->getOutput()->setOutputFlag( ParserOutputFlags::VARY_REVISION_ID );
1456 $id = $parser->getRevisionId();
1457 if ( $id === 0 ) {
1458 $rev = $parser->getRevisionRecordObject();
1459 if ( $rev ) {
1460 $id = $rev->getId();
1461 }
1462 }
1463 if ( !$id ) {
1464 $id = $parser->getOptions()->getSpeculativeRevId();
1465 if ( $id ) {
1466 $parser->getOutput()->setSpeculativeRevIdUsed( $id );
1467 }
1468 }
1469 return (string)$id;
1470 }
1471 $rev = self::getCachedRevisionObject( $parser, $t, ParserOutputFlags::VARY_REVISION_ID );
1472 return $rev ? $rev->getId() : '';
1473 }
1474
1475 private static function getRevisionTimestampSubstring(
1476 Parser $parser,
1477 Title $title,
1478 int $start,
1479 int $len,
1480 int $mtts
1481 ): string {
1482 // If fetching the revision timestamp of the current page, substitute the
1483 // speculative timestamp to be used when this revision is saved. This
1484 // avoids having to invalidate the cache immediately by assuming the "last
1485 // saved revision" will in fact be this one.
1486 // Don't do this for interface messages (eg, edit notices) however; in that
1487 // case fall through and use the actual timestamp of the last saved revision.
1488 if ( $title->equals( $parser->getTitle() ) && !$parser->getOptions()->getInterfaceMessage() ) {
1489 // Get the timezone-adjusted timestamp to be used for this revision
1490 $resNow = substr( $parser->getRevisionTimestamp(), $start, $len );
1491 // Possibly set vary-revision if there is not yet an associated revision
1492 if ( !$parser->getRevisionRecordObject() ) {
1493 // Get the timezone-adjusted timestamp $mtts seconds in the future.
1494 // This future is relative to the current time and not that of the
1495 // parser options. The rendered timestamp can be compared to that
1496 // of the timestamp specified by the parser options.
1497 $resThen = substr(
1498 $parser->getContentLanguage()->userAdjust( wfTimestamp( TS_MW, time() + $mtts ), '' ),
1499 $start,
1500 $len
1501 );
1502
1503 if ( $resNow !== $resThen ) {
1504 // Inform the edit saving system that getting the canonical output after
1505 // revision insertion requires a parse that used an actual revision timestamp
1506 $parser->getOutput()->setOutputFlag( ParserOutputFlags::VARY_REVISION_TIMESTAMP );
1507 }
1508 }
1509
1510 return $resNow;
1511 } else {
1512 $rev = self::getCachedRevisionObject( $parser, $title, ParserOutputFlags::VARY_REVISION_TIMESTAMP );
1513 if ( !$rev ) {
1514 return '';
1515 }
1516 $resNow = substr(
1517 $parser->getContentLanguage()->userAdjust( $rev->getTimestamp(), '' ), $start, $len
1518 );
1519 return $resNow;
1520 }
1521 }
1522
1530 public static function revisionday( $parser, $title = null ) {
1531 $t = self::makeTitle( $parser, $title );
1532 if ( $t === null || $t->isExternal() ) {
1533 return '';
1534 }
1535 return strval( (int)self::getRevisionTimestampSubstring(
1536 $parser, $t, 6, 2, self::MAX_TTS
1537 ) );
1538 }
1539
1547 public static function revisionday2( $parser, $title = null ) {
1548 $t = self::makeTitle( $parser, $title );
1549 if ( $t === null || $t->isExternal() ) {
1550 return '';
1551 }
1552 return self::getRevisionTimestampSubstring(
1553 $parser, $t, 6, 2, self::MAX_TTS
1554 );
1555 }
1556
1564 public static function revisionmonth( $parser, $title = null ) {
1565 $t = self::makeTitle( $parser, $title );
1566 if ( $t === null || $t->isExternal() ) {
1567 return '';
1568 }
1569 return self::getRevisionTimestampSubstring(
1570 $parser, $t, 4, 2, self::MAX_TTS
1571 );
1572 }
1573
1581 public static function revisionmonth1( $parser, $title = null ) {
1582 $t = self::makeTitle( $parser, $title );
1583 if ( $t === null || $t->isExternal() ) {
1584 return '';
1585 }
1586 return strval( (int)self::getRevisionTimestampSubstring(
1587 $parser, $t, 4, 2, self::MAX_TTS
1588 ) );
1589 }
1590
1598 public static function revisionyear( $parser, $title = null ) {
1599 $t = self::makeTitle( $parser, $title );
1600 if ( $t === null || $t->isExternal() ) {
1601 return '';
1602 }
1603 return self::getRevisionTimestampSubstring(
1604 $parser, $t, 0, 4, self::MAX_TTS
1605 );
1606 }
1607
1615 public static function revisiontimestamp( $parser, $title = null ) {
1616 $t = self::makeTitle( $parser, $title );
1617 if ( $t === null || $t->isExternal() ) {
1618 return '';
1619 }
1620 return self::getRevisionTimestampSubstring(
1621 $parser, $t, 0, 14, self::MAX_TTS
1622 );
1623 }
1624
1632 public static function revisionuser( $parser, $title = null ) {
1633 $t = self::makeTitle( $parser, $title );
1634 if ( $t === null || $t->isExternal() ) {
1635 return '';
1636 }
1637 // VARY_USER informs the edit saving system that getting the canonical
1638 // output after revision insertion requires a parse that used the
1639 // actual user ID.
1640 if ( $t->equals( $parser->getTitle() ) ) {
1641 // Fall back to Parser's "revision user" for the current title
1642 $parser->getOutput()->setOutputFlag( ParserOutputFlags::VARY_USER );
1643 // Note that getRevisionUser() can return null; we need to
1644 // be sure to cast this to (an empty) string, since returning
1645 // null means "magic variable not handled".
1646 return (string)$parser->getRevisionUser();
1647 }
1648 // Fetch revision from cache/database and return the value.
1649 $rev = self::getCachedRevisionObject( $parser, $t, ParserOutputFlags::VARY_USER );
1650 $user = ( $rev !== null ) ? $rev->getUser() : null;
1651 return $user ? $user->getName() : '';
1652 }
1653
1666 public static function cascadingsources( $parser, $title = '' ) {
1667 $titleObject = Title::newFromText( $title ) ?? $parser->getTitle();
1668 $restrictionStore = MediaWikiServices::getInstance()->getRestrictionStore();
1669 if ( $restrictionStore->areCascadeProtectionSourcesLoaded( $titleObject )
1671 ) {
1672 $names = [];
1673 $sources = $restrictionStore->getCascadeProtectionSources( $titleObject );
1674 $titleFormatter = MediaWikiServices::getInstance()->getTitleFormatter();
1675 foreach ( $sources[0] as $sourcePageIdentity ) {
1676 $names[] = $titleFormatter->getPrefixedText( $sourcePageIdentity );
1677 }
1678 return implode( '|', $names );
1679 }
1680 return '';
1681 }
1682}
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,...
wfExpandUrl( $url, $defaultProto=PROTO_CURRENT)
Expand a potentially local URL to a fully-qualified URL using $wgServer (or one of its alternatives).
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
$magicWords
@phpcs-require-sorted-array
array $params
The job parameters.
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:81
Various core parser functions, registered in every Parser.
static language( $parser, $code='', $inLanguage='')
Gives language names.
static numberingroup( $parser, $name='', $raw=null)
static rootpagename( $parser, $title=null)
static localurl( $parser, $s='', $arg=null)
static formatnum( $parser, $num='', $arg=null)
static ns( $parser, $part1='')
static protectionexpiry( $parser, $type='', $title='')
Returns the requested protection expiry for the current page.
static pagenamee( $parser, $title=null)
static padleft( $parser, $string='', $length=0, $padding='0')
static fullurle( $parser, $s='', $arg=null)
static revisionmonth( $parser, $title=null)
Get the month with leading zeros from the last revision of a specified page.
static fullpagename( $parser, $title=null)
static revisionid( $parser, $title=null)
Get the id from the last revision of a specified page.
static pagesinnamespace( $parser, $namespace=0, $raw=null)
static numberofusers( $parser, $raw=null)
static special( $parser, $text)
static formal(Parser $parser, string ... $forms)
static nse( $parser, $part1='')
static pagesize( $parser, $page='', $raw=null)
Return the size of the given page, or 0 if it's nonexistent.
static canonicalurl( $parser, $s='', $arg=null)
static basepagenamee( $parser, $title=null)
static subjectspacee( $parser, $title=null)
static numberofedits( $parser, $raw=null)
static numberoffiles( $parser, $raw=null)
static ucfirst( $parser, $s='')
static basepagename( $parser, $title=null)
static gender( $parser, $username,... $forms)
static numberofpages( $parser, $raw=null)
static lcfirst( $parser, $s='')
static urlFunction( $func, $s='', $arg=null)
static bcp47(Parser $parser, string $code='')
Gives the BCP-47 code for a language given the mediawiki internal language code.
static plural( $parser, $text='',... $forms)
static formatRaw( $num, $raw, $language, MagicWordFactory $magicWordFactory=null)
Formats a number according to a language.
static talkspacee( $parser, $title=null)
static bidi( $parser, $text='')
static revisionuser( $parser, $title=null)
Get the user from the last revision of a specified page.
static anchorencode( $parser, $text)
static subjectpagenamee( $parser, $title=null)
static namespacee( $parser, $title=null)
static subjectpagename( $parser, $title=null)
static filepath( $parser, $name='', $argA='', $argB='')
Usage {{filepath|300}}, {{filepath|nowiki}}, {{filepath|nowiki|300}} or {{filepath|300|nowiki}} or {{...
static padright( $parser, $string='', $length=0, $padding='0')
static pad( $parser, $string, $length, $padding='0', $direction=STR_PAD_RIGHT)
Unicode-safe str_pad with the restriction that $length is forced to be <= 500.
static canonicalurle( $parser, $s='', $arg=null)
static cascadingsources( $parser, $title='')
Returns the sources of any cascading protection acting on a specified page.
static numberofactiveusers( $parser, $raw=null)
static displaytitle( $parser, $text='', $uarg='')
Override the title of the page when viewed, provided we've been given a title which will normalise to...
static pageid( $parser, $title=null)
Get the pageid of a specified page.
static formatDate( $parser, $date, $defaultPref=null)
static urlencode( $parser, $s='', $arg=null)
urlencodes a string according to one of three patterns: (T24474)
static grammar( $parser, $case='', $word='')
static subpagenamee( $parser, $title=null)
static tagObj( $parser, $frame, $args)
Parser function to extension tag adaptor.
static revisionmonth1( $parser, $title=null)
Get the month from the last revision of a specified page.
static rootpagenamee( $parser, $title=null)
static protectionlevel( $parser, $type='', $title='')
Returns the requested protection level for the current page.
static namespacenumber( $parser, $title=null)
static revisionday( $parser, $title=null)
Get the day from the last revision of a specified page.
static subjectspace( $parser, $title=null)
static lc( $parser, $s='')
static pagesincategory( $parser, $name='', $arg1='', $arg2='')
Return the number of pages, files or subcats in the given category, or 0 if it's nonexistent.
static talkpagename( $parser, $title=null)
static fullurl( $parser, $s='', $arg=null)
static pagename( $parser, $title=null)
Functions to get and normalize pagenames, corresponding to the magic words of the same names.
static intFunction( $parser, $part1='',... $params)
static numberofadmins( $parser, $raw=null)
static numberofarticles( $parser, $raw=null)
static revisionyear( $parser, $title=null)
Get the year from the last revision of a specified page.
static dir(Parser $parser, string $code='', string $arg='')
Gives direction of script of a language given a language code.
static revisionday2( $parser, $title=null)
Get the day with leading zeros from the last revision of a specified page.
static uc( $parser, $s='')
static talkpagenamee( $parser, $title=null)
static revisiontimestamp( $parser, $title=null)
Get the timestamp from the last revision of a specified page.
static subpagename( $parser, $title=null)
static localurle( $parser, $s='', $arg=null)
static defaultsort( $parser, $text, $uarg='')
static fullpagenamee( $parser, $title=null)
static talkspace( $parser, $title=null)
static speciale( $parser, $text)
Category objects are immutable, strictly speaking.
Definition Category.php:42
A class for passing options to services.
A service that provides utilities to do with language names and codes.
A class containing constants representing the names of configuration variables.
Service locator for MediaWiki core services.
The Message class deals with fetching and processing of interface message into a variety of formats.
Definition Message.php:150
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:155
getTargetLanguageConverter()
Shorthand for getting a Language Converter for Target language.
Definition Parser.php:1661
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:3966
getMagicWordFactory()
Get the MagicWordFactory that this Parser is using.
Definition Parser.php:1242
setFunctionHook( $id, callable $callback, $flags=0)
Create a function, e.g.
Definition Parser.php:5041
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:4184
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:3571
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:1252
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:1188
parseWidthParam( $value, $parseHeight=true)
Parsed a width param of imagelink like 300px or 200x300px.
Definition Parser.php:6380
getStripList()
Get a list of strippable XML-like elements.
Definition Parser.php:1348
extensionSubstitution(array $params, PPFrame $frame, bool $processNowiki=false)
Return the text to be used for a given extension tag.
Definition Parser.php:3990
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:1997
fetchCurrentRevisionRecordOfTitle(LinkTarget $link)
Fetch the current revision of a given title as a RevisionRecord.
Definition Parser.php:3541
HTML sanitizer for MediaWiki.
Definition Sanitizer.php:46
Exception representing a failure to look up a revision.
Page revision base class.
Static accessor class for site_stats and related things.
Definition SiteStats.php:36
Parent class for all special pages.
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:1302
equals(object $other)
Compares with another Title.
Definition Title.php:3096
getDBkey()
Get the main part with underscores.
Definition Title.php:1035
getText()
Get the text form (spaces not underscores) of the main part.
Definition Title.php:1017
internal since 1.36
Definition User.php:93
hasFragment()
Whether the link target has a fragment.