83 # Flags for Parser::setFunctionHook
87 # Constants needed for external link processing
88 # Everything except bracket, space, or control characters
89 # \p{Zs} is unicode 'separator, space' category. It covers the space 0x20
90 # as well as U+3000 is IDEOGRAPHIC SPACE for T21052
91 # \x{FFFD} is the Unicode replacement character, which Preprocessor_DOM
92 # uses to replace invalid HTML characters.
94 # Simplified expression to match an IPv4 or IPv6 address, or
95 # at least one character of a host name (embeds EXT_LINK_URL_CLASS)
96 const EXT_LINK_ADDR =
'(?:[0-9.]+|\\[(?i:[0-9a-f:.]+)\\]|[^][<>"\\x00-\\x20\\x7F\p{Zs}\x{FFFD}])';
97 # RegExp to make image URLs (embeds IPv6 part of EXT_LINK_ADDR)
99 const EXT_IMAGE_REGEX =
'/^(http:\/\/|https:\/\/)((?:\\[(?i:[0-9a-f:.]+)\\])?[^][<>"\\x00-\\x20\\x7F\p{Zs}\x{FFFD}]+)
100 \\/([A-Za-z0-9_.,~%\\-+&;#*?!=()@\\x80-\\xFF]+)\\.((?i)gif|png|jpg|jpeg)$/Sxu';
102 # Regular expression for a non-newline space
105 # Flags for preprocessToDom
108 # Allowed values for $this->mOutputType
109 # Parameter to startExternalParse().
114 const
OT_PLAIN = 4;
# like extractSections() - portions of the original are returned unchanged.
133 const MARKER_SUFFIX =
"-QINU`\"'\x7f";
136 # Markers used for wrapping the table of contents
158 public $mFirstCall =
true;
160 # Initialised by initialiseVariables()
171 # Initialised in constructor
172 public $mConf, $mExtLinkBracketedRegex, $mUrlProtocols;
174 # Initialized in getPreprocessor()
178 # Cleared with clearState():
209 # These are variables reset at least once per parse regardless of $clearState
222 public $mRevisionObject;
# The revision object of the specified revision ID
227 public $mRevIdForTs; # The revision ID which was used to fetch the timestamp
234 public $mUniqPrefix = self::MARKER_PREFIX;
255 public $mInParse =
false;
299 $this->mConf = $parserConf;
301 $this->mExtLinkBracketedRegex =
'/\[(((?i)' . $this->mUrlProtocols .
')' .
302 self::EXT_LINK_ADDR .
303 self::EXT_LINK_URL_CLASS .
'*)\p{Zs}*([^\]\\x00-\\x08\\x0a-\\x1F\\x{FFFD}]*?)\]/Su';
304 if (
isset( $parserConf[
'preprocessorClass'] ) ) {
305 $this->mPreprocessorClass = $parserConf[
'preprocessorClass'];
307 # Under HHVM Preprocessor_Hash is much faster than Preprocessor_DOM
308 $this->mPreprocessorClass = Preprocessor_Hash::class;
310 # PECL extension that conflicts with the core DOM extension (T15770)
311 wfDebug(
"Warning: you have the obsolete domxml extension for PHP. Please remove it!\n" );
312 $this->mPreprocessorClass = Preprocessor_Hash::class;
314 $this->mPreprocessorClass = Preprocessor_DOM::class;
316 $this->mPreprocessorClass = Preprocessor_Hash::class;
318 wfDebug( __CLASS__ .
": using preprocessor: {$this->mPreprocessorClass}\n" );
320 $services = MediaWikiServices::getInstance();
321 $this->magicWordFactory = $magicWordFactory ??
324 $this->contLang = $contLang ??
$services->getContentLanguage();
326 $this->factory = $factory ??
$services->getParserFactory();
327 $this->specialPageFactory = $spFactory ??
$services->getSpecialPageFactory();
328 $this->siteConfig = $siteConfig ?? MediaWikiServices::getInstance()->getMainConfig();
330 $this->linkRendererFactory =
331 $linkRendererFactory ?? MediaWikiServices::getInstance()->getLinkRendererFactory();
338 if (
isset( $this->mLinkHolders ) ) {
339 unset( $this->mLinkHolders );
341 foreach ( $this as $name =>
$value ) {
342 unset( $this->$name );
350 $this->mInParse =
false;
358 foreach ( [
'mStripState',
'mVarCache' ] as $k ) {
366 Hooks::run(
'ParserCloned', [ $this ] );
373 if ( !$this->mFirstCall ) {
376 $this->mFirstCall =
false;
384 Hooks::run(
'ParserFirstCallInit', [ &
$parser ] );
395 $this->mOptions->registerWatcher( [ $this->mOutput,
'recordOption' ] );
396 $this->mAutonumber = 0;
397 $this->mIncludeCount = [];
400 $this->mRevisionObject = $this->mRevisionTimestamp =
401 $this->mRevisionId = $this->mRevisionUser = $this->mRevisionSize =
null;
402 $this->mVarCache = [];
404 $this->mLangLinkLanguages = [];
405 $this->currentRevisionCache =
null;
409 # Clear these on every parse, T6549
410 $this->mTplRedirCache = $this->mTplDomCache = [];
412 $this->mShowToc =
true;
413 $this->mForceTocPosition =
false;
414 $this->mIncludeSizes = [
418 $this->mPPNodeCount = 0;
419 $this->mGeneratedPPNodeCount = 0;
420 $this->mHighestExpansionDepth = 0;
421 $this->mDefaultSort =
false;
422 $this->mHeadings = [];
423 $this->mDoubleUnderscores = [];
424 $this->mExpensiveFunctionCount = 0;
427 if (
isset( $this->mPreprocessor ) && $this->mPreprocessor->parser !== $this ) {
428 $this->mPreprocessor =
null;
435 Hooks::run(
'ParserClearState', [ &
$parser ] );
454 $linestart =
true, $clearState =
true, $revid =
null
459 $text =
strtr( $text,
"\x7f",
"?" );
460 $magicScopeVariable = $this->
lock();
467 $this->currentRevisionCache =
null;
468 $this->mInputSize =
strlen( $text );
469 if ( $this->mOptions->getEnableLimitReport() ) {
470 $this->mOutput->resetParseStartTime();
473 $oldRevisionId = $this->mRevisionId;
474 $oldRevisionObject = $this->mRevisionObject;
475 $oldRevisionTimestamp = $this->mRevisionTimestamp;
476 $oldRevisionUser = $this->mRevisionUser;
477 $oldRevisionSize = $this->mRevisionSize;
478 if ( $revid !==
null ) {
479 $this->mRevisionId = $revid;
480 $this->mRevisionObject =
null;
481 $this->mRevisionTimestamp =
null;
482 $this->mRevisionUser =
null;
483 $this->mRevisionSize =
null;
488 Hooks::run(
'ParserBeforeStrip', [ &
$parser, &$text, &$this->mStripState ] );
490 Hooks::run(
'ParserAfterStrip', [ &
$parser, &$text, &$this->mStripState ] );
492 Hooks::run(
'ParserAfterParse', [ &
$parser, &$text, &$this->mStripState ] );
503 if ( !(
$options->getDisableTitleConversion()
504 || isset( $this->mDoubleUnderscores[
'nocontentconvert'] )
505 ||
isset( $this->mDoubleUnderscores[
'notitleconvert'] )
506 || $this->mOutput->getDisplayTitle() !==
false )
509 if ( $convruletitle ) {
510 $this->mOutput->setTitleText( $convruletitle );
513 $this->mOutput->setTitleText( $titleText );
517 # Compute runtime adaptive expiry if set
518 $this->mOutput->finalizeAdaptiveCacheExpiry();
520 # Warn if too many heavyweight parser functions were used
521 if ( $this->mExpensiveFunctionCount > $this->mOptions->getExpensiveParserFunctionLimit() ) {
523 $this->mExpensiveFunctionCount,
524 $this->mOptions->getExpensiveParserFunctionLimit()
528 # Information on limits, for the benefit of users who try to skirt them
529 if ( $this->mOptions->getEnableLimitReport() ) {
533 # Wrap non-interface parser output in a <div> so it can be targeted
535 $class = $this->mOptions->getWrapOutputClass();
536 if ( $class !==
false && !$this->mOptions->getInterfaceMessage() ) {
537 $this->mOutput->addWrapperDivClass( $class );
540 $this->mOutput->setText( $text );
547 $this->mInputSize =
false;
548 $this->currentRevisionCache =
null;
550 return $this->mOutput;
560 $maxIncludeSize = $this->mOptions->getMaxIncludeSize();
562 $cpuTime = $this->mOutput->getTimeSinceStart(
'cpu' );
563 if ( $cpuTime !==
null ) {
564 $this->mOutput->setLimitReportData(
'limitreport-cputime',
569 $wallTime = $this->mOutput->getTimeSinceStart(
'wall' );
570 $this->mOutput->setLimitReportData(
'limitreport-walltime',
574 $this->mOutput->setLimitReportData(
'limitreport-ppvisitednodes',
575 [ $this->mPPNodeCount, $this->mOptions->getMaxPPNodeCount() ]
577 $this->mOutput->setLimitReportData(
'limitreport-ppgeneratednodes',
578 [ $this->mGeneratedPPNodeCount, $this->mOptions->getMaxGeneratedPPNodeCount() ]
580 $this->mOutput->setLimitReportData(
'limitreport-postexpandincludesize',
581 [ $this->mIncludeSizes[
'post-expand'], $maxIncludeSize ]
583 $this->mOutput->setLimitReportData(
'limitreport-templateargumentsize',
584 [ $this->mIncludeSizes[
'arg'], $maxIncludeSize ]
586 $this->mOutput->setLimitReportData(
'limitreport-expansiondepth',
587 [ $this->mHighestExpansionDepth, $this->mOptions->getMaxPPExpandDepth() ]
589 $this->mOutput->setLimitReportData(
'limitreport-expensivefunctioncount',
590 [ $this->mExpensiveFunctionCount, $this->mOptions->getExpensiveParserFunctionLimit() ]
593 foreach ( $this->mStripState->getLimitReport() as
list( $key,
$value ) ) {
594 $this->mOutput->setLimitReportData( $key,
$value );
597 Hooks::run(
'ParserLimitReportPrepare', [ $this, $this->mOutput ] );
599 $limitReport =
"NewPP limit report\n";
600 if ( $this->siteConfig->get(
'ShowHostnames' ) ) {
601 $limitReport .=
'Parsed by ' .
wfHostname() .
"\n";
603 $limitReport .=
'Cached time: ' . $this->mOutput->getCacheTime() .
"\n";
604 $limitReport .=
'Cache expiry: ' . $this->mOutput->getCacheExpiry() .
"\n";
605 $limitReport .=
'Dynamic content: ' .
606 ( $this->mOutput->hasDynamicContent() ?
'true' :
'false' ) .
609 foreach ( $this->mOutput->getLimitReportData() as $key =>
$value ) {
610 if ( Hooks::run(
'ParserLimitReportFormat',
611 [ $key, &
$value, &$limitReport,
false,
false ]
613 $keyMsg =
wfMessage( $key )->inLanguage(
'en' )->useDatabase(
false );
614 $valueMsg =
wfMessage( [
"$key-value-text",
"$key-value" ] )
615 ->inLanguage(
'en' )->useDatabase(
false );
616 if ( !$valueMsg->exists() ) {
619 if ( !$keyMsg->isDisabled() && !$valueMsg->isDisabled() ) {
620 $valueMsg->params(
$value );
621 $limitReport .=
"{$keyMsg->text()}: {$valueMsg->text()}\n";
631 $limitReport =
str_replace( [
'-',
'&' ], [
'‐',
'&' ], $limitReport );
632 $text =
"\n<!-- \n$limitReport-->\n";
635 $dataByFunc = $this->mProfiler->getFunctionStats();
636 uasort( $dataByFunc,
function ( $a, $b ) {
637 return $b[
'real'] <=> $a[
'real'];
640 foreach (
array_slice( $dataByFunc, 0, 10 ) as $item ) {
641 $profileReport[] =
sprintf(
"%6.2f%% %8.3f %6d %s",
642 $item[
'%real'], $item[
'real'], $item[
'calls'],
645 $text .=
"<!--\nTransclusion expansion time report (%,ms,calls,template)\n";
646 $text .= implode(
"\n", $profileReport ) .
"\n-->\n";
648 $this->mOutput->setLimitReportData(
'limitreport-timingprofile', $profileReport );
651 if ( $this->siteConfig->get(
'ShowHostnames' ) ) {
652 $this->mOutput->setLimitReportData(
'cachereport-origin',
wfHostname() );
654 $this->mOutput->setLimitReportData(
'cachereport-timestamp',
655 $this->mOutput->getCacheTime() );
656 $this->mOutput->setLimitReportData(
'cachereport-ttl',
657 $this->mOutput->getCacheExpiry() );
658 $this->mOutput->setLimitReportData(
'cachereport-transientcontent',
659 $this->mOutput->hasDynamicContent() );
661 if ( $this->mGeneratedPPNodeCount > $this->mOptions->getMaxGeneratedPPNodeCount() / 10 ) {
662 wfDebugLog(
'generated-pp-node-count', $this->mGeneratedPPNodeCount .
' ' .
663 $this->mTitle->getPrefixedDBkey() );
695 Hooks::run(
'ParserBeforeStrip', [ &
$parser, &$text, &$this->mStripState ] );
696 Hooks::run(
'ParserAfterStrip', [ &
$parser, &$text, &$this->mStripState ] );
740 $magicScopeVariable = $this->
lock();
742 if ( $revid !==
null ) {
743 $this->mRevisionId = $revid;
747 Hooks::run(
'ParserBeforeStrip', [ &
$parser, &$text, &$this->mStripState ] );
748 Hooks::run(
'ParserAfterStrip', [ &
$parser, &$text, &$this->mStripState ] );
750 $text = $this->mStripState->unstripBoth( $text );
765 $text = $this->mStripState->unstripBoth( $text );
784 $text = $msg->params(
$params )->plain();
786 # Parser (re)initialisation
787 $magicScopeVariable = $this->
lock();
790 $flags = PPFrame::NO_ARGS | PPFrame::NO_TEMPLATES;
793 $text = $this->mStripState->unstripBoth( $text );
804 $this->mUser =
$user;
814 $t = Title::newFromText(
'NO TITLE' );
817 if (
$t->hasFragment() ) {
818 # Strip the fragment to avoid various odd effects
819 $this->mTitle =
$t->createFragmentTarget(
'' );
831 return $this->mTitle;
840 public function Title( $x =
null ) {
841 return wfSetVar( $this->mTitle, $x );
850 $this->mOutputType =
$ot;
867 return wfSetVar( $this->mOutputType, $x );
876 return $this->mOutput;
885 return $this->mOptions;
895 return wfSetVar( $this->mOptions, $x );
902 return $this->mLinkID++;
909 $this->mLinkID = $id;
930 $target = $this->mOptions->getTargetLanguage();
932 if ( $target !==
null ) {
934 }
elseif ( $this->mOptions->getInterfaceMessage() ) {
935 return $this->mOptions->getUserLangObj();
937 throw new MWException( __METHOD__ .
': $this->mTitle is null' );
940 return $this->mTitle->getPageLanguage();
959 if ( !
is_null( $this->mUser ) ) {
962 return $this->mOptions->getUser();
971 if ( !
isset( $this->mPreprocessor ) ) {
973 $this->mPreprocessor =
new $class( $this );
975 return $this->mPreprocessor;
986 if ( !$this->mLinkRenderer ) {
987 $this->mLinkRenderer = $this->linkRendererFactory->create();
988 $this->mLinkRenderer->setStubThreshold(
993 return $this->mLinkRenderer;
1003 return $this->magicWordFactory;
1013 return $this->contLang;
1040 $taglist = implode(
'|', $elements );
1041 $start =
"/<($taglist)(\\s+[^>]*?|\\s*?)(\/?>)|<(!--)/i";
1043 while ( $text !=
'' ) {
1044 $p =
preg_split( $start, $text, 2, PREG_SPLIT_DELIM_CAPTURE );
1046 if ( count( $p ) < 5 ) {
1049 if ( count( $p ) > 5 ) {
1057 list( , $element, $attributes, $close, $inside ) =
$p;
1063 if ( $close ===
'/>' ) {
1064 # Empty element tag, <tag />
1069 if ( $element ===
'!--' ) {
1072 $end =
"/(<\\/$element\\s*>)/i";
1074 $q =
preg_split( $end, $inside, 2, PREG_SPLIT_DELIM_CAPTURE );
1076 if ( count( $q ) < 3 ) {
1077 # No end tag -- let it run out to the end of the text.
1081 list( , $tail, $text ) =
$q;
1087 Sanitizer::decodeTagAttributes( $attributes ),
1088 "<$element$attributes$close$content$tail" ];
1099 return $this->mStripList;
1113 $this->mMarkerIndex++;
1114 $this->mStripState->addGeneral( $marker, $text );
1126 $lines = StringUtils::explode(
"\n", $text );
1128 $td_history = []; # Is currently a td tag open?
1129 $last_tag_history = []; # Save
history of last lag
activated (td, th or caption)
1130 $tr_history = []; # Is currently a tr tag open?
1131 $tr_attributes = []; #
history of tr attributes
1132 $has_opened_tr = []; # Did
this table open a <tr> element?
1133 $indent_level = 0; # indent level of the
table
1135 foreach (
$lines as $outLine ) {
1138 if (
$line ===
'' ) { # empty
line, go to next line
1139 $out .= $outLine .
"\n";
1143 $first_character =
$line[0];
1148 # First check if we are starting a new table
1151 $attributes = $this->mStripState->unstripBoth(
$matches[2] );
1152 $attributes = Sanitizer::fixTagAttributes( $attributes,
'table' );
1154 $outLine =
str_repeat(
'<dl><dd>', $indent_level ) .
"<table{$attributes}>";
1160 }
elseif ( count( $td_history ) == 0 ) {
1161 # Don't do any of the following
1162 $out .= $outLine .
"\n";
1164 }
elseif ( $first_two ===
'|}' ) {
1165 # We are ending a table
1167 $last_tag =
array_pop( $last_tag_history );
1170 $line =
"<tr><td></td></tr>{$line}";
1174 $line =
"</tr>{$line}";
1178 $line =
"</{$last_tag}>{$line}";
1181 if ( $indent_level > 0 ) {
1186 }
elseif ( $first_two ===
'|-' ) {
1187 # Now we have a table row
1190 # Whats after the tag is now only attributes
1191 $attributes = $this->mStripState->unstripBoth(
$line );
1192 $attributes = Sanitizer::fixTagAttributes( $attributes,
'tr' );
1197 $last_tag =
array_pop( $last_tag_history );
1206 $line =
"</{$last_tag}>{$line}";
1213 }
elseif ( $first_character ===
'|'
1214 || $first_character ===
'!'
1215 || $first_two ===
'|+'
1217 # This might be cell elements, td, th or captions
1218 if ( $first_two ===
'|+' ) {
1219 $first_character =
'+';
1226 if ( $first_character ===
'!' ) {
1227 $line = StringUtils::replaceMarkup(
'!!',
'||',
$line );
1230 # Split up multiple cells on the same line.
1231 # FIXME : This can result in improper nesting of tags processed
1232 # by earlier parser steps.
1233 $cells = explode(
'||',
$line );
1237 # Loop through each table cell
1238 foreach ( $cells as $cell ) {
1240 if ( $first_character !==
'+' ) {
1241 $tr_after =
array_pop( $tr_attributes );
1243 $previous =
"<tr{$tr_after}>\n";
1251 $last_tag =
array_pop( $last_tag_history );
1254 $previous =
"</{$last_tag}>\n{$previous}";
1257 if ( $first_character ===
'|' ) {
1259 }
elseif ( $first_character ===
'!' ) {
1261 }
elseif ( $first_character ===
'+' ) {
1262 $last_tag =
'caption';
1269 # A cell could contain both parameters and data
1270 $cell_data = explode(
'|', $cell, 2 );
1272 # T2553: Note that a '|' inside an invalid link should not
1273 # be mistaken as delimiting cell parameters
1274 # Bug T153140: Neither should language converter markup.
1275 if (
preg_match(
'/\[\[|-\{/', $cell_data[0] ) === 1 ) {
1276 $cell =
"{$previous}<{$last_tag}>" .
trim( $cell );
1277 }
elseif ( count( $cell_data ) == 1 ) {
1279 $cell =
"{$previous}<{$last_tag}>" .
trim( $cell_data[0] );
1281 $attributes = $this->mStripState->unstripBoth( $cell_data[0] );
1282 $attributes = Sanitizer::fixTagAttributes( $attributes, $last_tag );
1284 $cell =
"{$previous}<{$last_tag}{$attributes}>" .
trim( $cell_data[1] );
1291 $out .= $outLine .
"\n";
1294 # Closing open td, tr && table
1295 while ( count( $td_history ) > 0 ) {
1303 $out .=
"<tr><td></td></tr>\n";
1306 $out .=
"</table>\n";
1309 # Remove trailing line-ending (b/c)
1314 # special case: don't return empty table
1315 if (
$out ===
"<table>\n<tr><td></td></tr>\n</table>" ) {
1341 # Hook to suspend the parser in this state
1342 if ( !Hooks::run(
'ParserBeforeInternalParse', [ &
$parser, &$text, &$this->mStripState ] ) ) {
1346 # if $frame is provided, then use $frame for replacing any variables
1348 # use frame depth to infer how include/noinclude tags should be handled
1349 # depth=0 means this is the top-level document; otherwise it's an included document
1350 if ( !$frame->depth ) {
1353 $flag = self::PTD_FOR_INCLUSION;
1356 $text = $frame->expand( $dom );
1358 # if $frame is not provided, then use old-style replaceVariables
1362 Hooks::run(
'InternalParseBeforeSanitize', [ &
$parser, &$text, &$this->mStripState ] );
1363 $text = Sanitizer::removeHTMLtags(
1365 [ $this,
'attributeStripCallback' ],
1369 [ $this,
'addTrackingCategory' ]
1371 Hooks::run(
'InternalParseBeforeLinks', [ &
$parser, &$text, &$this->mStripState ] );
1373 # Tables need to come after variable replacement for things to work
1374 # properly; putting them before other transformations should keep
1375 # exciting things like link expansions from showing up in surprising
1379 $text =
preg_replace(
'/(^|\n)-----*/',
'\\1<hr />', $text );
1388 # replaceInternalLinks may sometimes leave behind
1389 # absolute URLs, which have to be masked to hide them from replaceExternalLinks
1390 $text =
str_replace( self::MARKER_PREFIX .
'NOPARSE',
'', $text );
1408 $text = $this->mStripState->unstripGeneral( $text );
1414 Hooks::run(
'ParserAfterUnstrip', [ &
$parser, &$text ] );
1417 # Clean up special characters, only run once, next-to-last before doBlockLevels
1418 $text = Sanitizer::armorFrenchSpaces( $text );
1431 if ( !( $this->mOptions->getDisableContentConversion()
1432 || isset( $this->mDoubleUnderscores[
'nocontentconvert'] ) )
1433 && !$this->mOptions->getInterfaceMessage()
1435 # The position of the convert() call should not be changed. it
1436 # assumes that the links are all replaced and the only thing left
1437 # is the <nowiki> mark.
1441 $text = $this->mStripState->unstripNoWiki( $text );
1444 Hooks::run(
'ParserBeforeTidy', [ &
$parser, &$text ] );
1448 $text = $this->mStripState->unstripGeneral( $text );
1450 $text = Sanitizer::normalizeCharReferences( $text );
1453 if ( $this->mOptions->getTidy() ) {
1457 # attempt to sanitize at least some nesting problems
1458 # (T4702 and quite a few others)
1459 # This code path is buggy and deprecated!
1462 # ''Something [http:
1463 # <i>Something</i><a href="http://www.cool.com"..><i>cool></i></a>
1464 '/(<([bi])>)(<([bi])>)?([^<]*)(<\/?a[^<]*>)([^<]*)(<\/\\4>)?(<\/\\2>)/' =>
1465 '\\1\\3\\5\\8\\9\\6\\1\\3\\7\\8\\9',
1466 # fix up an anchor inside another anchor, only
1467 # at least for a single single nested link (T5695)
1468 '/(<a[^>]+>)([^<]*)(<a[^>]+>[^<]*)<\/a>(.*)<\/a>/' =>
1469 '\\1\\2</a>\\3</a>\\1\\4</a>',
1470 # fix div inside inline elements- doBlockLevels won't wrap a line which
1471 # contains a div, so fix it up here; replace
1472 # div with escaped text
1473 '/(<([aib]) [^>]+>)([^<]*)(<div([^>]*)>)(.*)(<\/div>)([^<]*)(<\/\\2>)/' =>
1474 '\\1\\3<div\\5>\\6</div>\\8\\9',
1475 # remove empty italic or bold tag pairs, some
1476 # introduced by rules above
1477 '/<([bi])><\/\\1>/' =>
'',
1487 Hooks::run(
'ParserAfterTidy', [ &
$parser, &$text ] );
1506 $urlChar = self::EXT_LINK_URL_CLASS;
1507 $addr = self::EXT_LINK_ADDR;
1508 $space = self::SPACE_NOT_NL; # non-newline space
1509 $spdash =
"(?:-|$space)"; # a dash or a non-newline space
1510 $spaces =
"$space++"; # possessive match of 1 or more spaces
1513 (<a[ \t\r\n>].*?</a>) | # m[1]: Skip link text
1514 (<.*?>) | # m[2]: Skip stuff inside HTML elements' .
"
1515 (\b # m[3]: Free external links
1517 ($addr$urlChar*) # m[4]: Post-protocol path
1519 \b(?:RFC|PMID) $spaces # m[5]: RFC or PMID, capture number
1521 \bISBN $spaces ( # m[6]: ISBN, capture number
1522 (?: 97[89] $spdash? )? # optional 13-digit ISBN prefix
1523 (?: [0-9] $spdash? ){9} # 9 digits with opt. delimiters
1524 [0-9Xx] # check digit
1526 )!xu", [ $this,
'magicLinkCallback' ], $text );
1536 if (
isset( $m[1] ) && $m[1] !==
'' ) {
1543 # Free external link
1547 if (
substr( $m[0], 0, 3 ) ===
'RFC' ) {
1548 if ( !$this->mOptions->getMagicRFCLinks() ) {
1553 $cssClass =
'mw-magiclink-rfc';
1554 $trackingCat =
'magiclink-tracking-rfc';
1557 if ( !$this->mOptions->getMagicPMIDLinks() ) {
1561 $urlmsg =
'pubmedurl';
1562 $cssClass =
'mw-magiclink-pmid';
1563 $trackingCat =
'magiclink-tracking-pmid';
1566 throw new MWException( __METHOD__ .
': unrecognised match type "' .
1567 substr( $m[0], 0, 20 ) .
'"' );
1569 $url =
wfMessage( $urlmsg, $id )->inContentLanguage()->text();
1573 && $this->mOptions->getMagicISBNLinks()
1577 $space = self::SPACE_NOT_NL; # non-newline space
1579 $num =
strtr( $isbn, [
1586 SpecialPage::getTitleFor(
'Booksources', $num ),
1589 'class' =>
'internal mw-magiclink-isbn',
1610 # The characters '<' and '>' (which were escaped by
1611 # removeHTMLtags()) should not be included in
1612 # URLs, per RFC 2396.
1613 # Make terminate a URL as well (bug T84937)
1616 '/&(lt|gt|nbsp|#x0*(3[CcEe]|[Aa]0)|#0*(60|62|160));/',
1622 $url =
substr( $url, 0, $m2[0][1] );
1625 # Move trailing punctuation to $trail
1627 # If there is no left bracket, then consider right brackets fair game too
1628 if (
strpos( $url,
'(' ) ===
false ) {
1632 $urlRev =
strrev( $url );
1633 $numSepChars =
strspn( $urlRev, $sep );
1634 # Don't break a trailing HTML entity by moving the ; into $trail
1635 # This is in hot code, so use substr_compare to avoid having to
1636 # create a new string object for the comparison
1637 if ( $numSepChars &&
substr_compare( $url,
";", -$numSepChars, 1 ) === 0 ) {
1638 # more optimization: instead of running preg_match with a $
1639 # anchor, which can be slow, do the match on the reversed
1640 # string starting at the desired offset.
1641 # un-reversed regexp is: /&([a-z]+|#x[\da-f]+|#\d+)$/i
1642 if (
preg_match(
'/\G([a-z]+|[\da-f]+x#|\d+#)&/i', $urlRev, $m2, 0, $numSepChars ) ) {
1646 if ( $numSepChars ) {
1648 $url =
substr( $url, 0, -$numSepChars );
1651 # Verify that we still have a real URL after trail removal, and
1652 # not just lone protocol
1653 if (
strlen( $trail ) >= $numPostProto ) {
1657 $url = Sanitizer::cleanUrl( $url );
1659 # Is this an external image?
1661 if ( $text ===
false ) {
1662 # Not an image, make a link
1667 # Register it in the output object...
1668 $this->mOutput->addExternalLink( $url );
1683 for ( $i = 6; $i >= 1; --
$i ) {
1687 $text =
preg_replace(
"/^(?:$h)[ \\t]*(.+?)[ \\t]*(?:$h)\\s*$/m",
"<h$i>\\1</h$i>", $text );
1702 $lines = StringUtils::explode(
"\n", $text );
1706 $outtext =
substr( $outtext, 0, -1 );
1718 $arr =
preg_split(
"/(''+)/", $text, -1, PREG_SPLIT_DELIM_CAPTURE );
1719 $countarr = count( $arr );
1720 if ( $countarr == 1 ) {
1729 for ( $i = 1; $i <
$countarr; $i += 2 ) {
1730 $thislen =
strlen( $arr[$i] );
1734 if ( $thislen == 4 ) {
1735 $arr[$i - 1] .=
"'";
1738 }
elseif ( $thislen > 5 ) {
1742 $arr[$i - 1] .=
str_repeat(
"'", $thislen - 5 );
1747 if ( $thislen == 2 ) {
1749 }
elseif ( $thislen == 3 ) {
1751 }
elseif ( $thislen == 5 ) {
1761 if ( ( $numbold % 2 == 1 ) && ( $numitalics % 2 == 1 ) ) {
1762 $firstsingleletterword = -1;
1763 $firstmultiletterword = -1;
1765 for ( $i = 1; $i <
$countarr; $i += 2 ) {
1766 if (
strlen( $arr[$i] ) == 3 ) {
1767 $x1 =
substr( $arr[$i - 1], -1 );
1768 $x2 =
substr( $arr[$i - 1], -2, 1 );
1769 if ( $x1 ===
' ' ) {
1770 if ( $firstspace == -1 ) {
1773 }
elseif ( $x2 ===
' ' ) {
1774 $firstsingleletterword =
$i;
1778 }
elseif ( $firstmultiletterword == -1 ) {
1779 $firstmultiletterword =
$i;
1785 if ( $firstsingleletterword > -1 ) {
1787 $arr[$firstsingleletterword - 1] .=
"'";
1788 }
elseif ( $firstmultiletterword > -1 ) {
1791 $arr[$firstmultiletterword - 1] .=
"'";
1792 }
elseif ( $firstspace > -1 ) {
1797 $arr[$firstspace - 1] .=
"'";
1806 foreach ( $arr as $r ) {
1807 if ( ( $i % 2 ) == 0 ) {
1808 if ( $state ===
'both' ) {
1815 if ( $thislen == 2 ) {
1816 if ( $state ===
'i' ) {
1819 }
elseif ( $state ===
'bi' ) {
1822 }
elseif ( $state ===
'ib' ) {
1825 }
elseif ( $state ===
'both' ) {
1832 }
elseif ( $thislen == 3 ) {
1833 if ( $state ===
'b' ) {
1836 }
elseif ( $state ===
'bi' ) {
1839 }
elseif ( $state ===
'ib' ) {
1842 }
elseif ( $state ===
'both' ) {
1849 }
elseif ( $thislen == 5 ) {
1850 if ( $state ===
'b' ) {
1853 }
elseif ( $state ===
'i' ) {
1856 }
elseif ( $state ===
'bi' ) {
1859 }
elseif ( $state ===
'ib' ) {
1862 }
elseif ( $state ===
'both' ) {
1874 if ( $state ===
'b' || $state ===
'ib' ) {
1877 if ( $state ===
'i' || $state ===
'bi' || $state ===
'ib' ) {
1880 if ( $state ===
'bi' ) {
1884 if ( $state ===
'both' &&
$buffer ) {
1904 $bits =
preg_split( $this->mExtLinkBracketedRegex, $text, -1, PREG_SPLIT_DELIM_CAPTURE );
1905 if ( $bits ===
false ) {
1906 throw new MWException(
"PCRE needs to be compiled with "
1907 .
"--enable-unicode-properties in order for MediaWiki to function" );
1912 while ( $i < count( $bits ) ) {
1915 $text = $bits[$i++];
1916 $trail = $bits[$i++];
1918 # The characters '<' and '>' (which were escaped by
1919 # removeHTMLtags()) should not be included in
1920 # URLs, per RFC 2396.
1922 if (
preg_match(
'/&(lt|gt);/', $url, $m2, PREG_OFFSET_CAPTURE ) ) {
1923 $text =
substr( $url, $m2[0][1] ) .
' ' . $text;
1924 $url =
substr( $url, 0, $m2[0][1] );
1927 # If the link text is an image URL, replace it with an <img> tag
1928 # This happened by accident in the original parser, but some people used it extensively
1930 if ( $img !==
false ) {
1936 # Set linktype for CSS
1939 # No link text, e.g. [http:
1940 if ( $text ==
'' ) {
1943 $text =
'[' . $langObj->formatNum( ++$this->mAutonumber ) .
']';
1944 $linktype =
'autonumber';
1946 # Have link text, e.g. [http:
1956 $url = Sanitizer::cleanUrl( $url );
1958 # Use the encoded URL
1959 # This means that users can paste URLs directly into the text
1960 # Funny characters like ö aren't valid in URLs anyway
1961 # This was changed in August 2004
1965 # Register link in the output object.
1966 $this->mOutput->addExternalLink( $url );
1983 $ns = $title ? $title->getNamespace() :
false;
2004 $rel = self::getExternalLinkRel( $url, $this->mTitle );
2006 $target = $this->mOptions->getExternalLinkTarget();
2009 if ( !
in_array( $target, [
'_self',
'_parent',
'_top' ] ) ) {
2013 if ( $rel !==
'' ) {
2016 $rel .=
'noreferrer noopener';
2033 # Test for RFC 3986 IPv6 syntax
2034 $scheme =
'[a-z][a-z0-9+.-]*:';
2035 $userinfo =
'(?:[a-z0-9\-._~!$&\'()*+,;=:]|%[0-9a-f]{2})*';
2036 $ipv6Host =
'\\[((?:[0-9a-f:]|%3[0-A]|%[46][1-6])+)\\]';
2037 if (
preg_match(
"<^(?:{$scheme})?//(?:{$userinfo}@)?{$ipv6Host}(?:[:/?#].*|)$>i", $url, $m ) &&
2045 # Make sure unsafe characters are encoded
2056 # Fragment part - 'fragment'
2057 $start =
strpos( $url,
'#' );
2058 if ( $start !==
false && $start < $end ) {
2059 $ret = self::normalizeUrlComponent(
2060 substr( $url, $start, $end - $start ),
'"#%<>[\]^`{|}' ) .
$ret;
2064 # Query part - 'query' minus &=+;
2065 $start =
strpos( $url,
'?' );
2066 if ( $start !==
false && $start < $end ) {
2067 $ret = self::normalizeUrlComponent(
2068 substr( $url, $start, $end - $start ),
'"#%<>[\]^`{|}&=+;' ) .
$ret;
2072 # Scheme and path part - 'pchar'
2073 # (we assume no userinfo or encoded colons in the host)
2074 $ret = self::normalizeUrlComponent(
2075 substr( $url, 0, $end ),
'"#%<>[\]^`{|}/?' ) .
$ret;
2078 if ( $isIPv6 !==
false ) {
2079 $ipv6Host =
"%5B({$isIPv6})%5D";
2081 "<^((?:{$scheme})?//(?:{$userinfo}@)?){$ipv6Host}(?=[:/?#]|$)>i",
2091 $callback =
function (
$matches ) use ( $unsafe ) {
2093 $ord =
ord( $char );
2094 if ( $ord > 32 && $ord < 127 &&
strpos( $unsafe, $char ) ===
false ) {
2098 # Leave it escaped, but use uppercase for a-f
2114 $imagesfrom = $this->mOptions->getAllowExternalImagesFrom();
2115 $imagesexception = !empty( $imagesfrom );
2117 # $imagesfrom could be either a single string or an array of strings, parse out the latter
2118 if ( $imagesexception &&
is_array( $imagesfrom ) ) {
2119 $imagematch =
false;
2120 foreach ( $imagesfrom as $match ) {
2121 if (
strpos( $url, $match ) === 0 ) {
2126 }
elseif ( $imagesexception ) {
2127 $imagematch = (
strpos( $url, $imagesfrom ) === 0 );
2129 $imagematch =
false;
2132 if ( $this->mOptions->getAllowExternalImages()
2135 if (
preg_match( self::EXT_IMAGE_REGEX, $url ) ) {
2140 if ( !$text && $this->mOptions->getEnableImageWhitelist()
2141 && preg_match( self::EXT_IMAGE_REGEX, $url )
2143 $whitelist = explode(
2145 wfMessage(
'external_image_whitelist' )->inContentLanguage()->
text()
2148 foreach ( $whitelist as $entry ) {
2149 # Sanitize the regex fragment, make it case-insensitive, ignore blank entries/comments
2150 if (
strpos( $entry,
'#' ) === 0 || $entry ===
'' ) {
2154 # Image matches a whitelist entry
2187 # the % is needed to support urlencoded titles as well
2189 $tc = Title::legalChars() .
'#%';
2190 # Match a link having the form [[namespace:link|alternate]]trail
2191 $e1 =
"/^([{$tc}]+)(?:\\|(.+?))?]](.*)\$/sD";
2192 # Match cases where there is no "]]", which might still be images
2193 $e1_img =
"/^([{$tc}]+)\\|(.*)\$/sD";
2198 # split the entire text string on occurrences of [[
2199 $a = StringUtils::explode(
'[[',
' ' .
$s );
2200 # get the first element (all text up to first [[), and remove the space we added
2203 $line = $a->current(); # Workaround
for broken ArrayIterator::next() that returns
"void"
2208 if ( $useLinkPrefixExtension ) {
2209 # Match the end of a line for a word that's not followed by whitespace,
2210 # e.g. in the case of 'The Arab al[[Razi]]', 'al' will be matched
2211 $charset = $this->contLang->linkPrefixCharset();
2212 $e2 =
"/^((?>.*[^$charset]|))(.+)$/sDu";
2215 if (
is_null( $this->mTitle ) ) {
2216 throw new MWException( __METHOD__ .
": \$this->mTitle is null\n" );
2218 $nottalk = !$this->mTitle->isTalkPage();
2220 if ( $useLinkPrefixExtension ) {
2223 $first_prefix = $m[2];
2225 $first_prefix =
false;
2233 # Loop for each link
2234 for ( ;
$line !==
false &&
$line !==
null; $a->next(),
$line = $a->current() ) {
2235 # Check for excessive memory usage
2236 if ( $holders->isBig() ) {
2238 # Do the existence check, replace the link holders and clear the array
2239 $holders->replace(
$s );
2243 if ( $useLinkPrefixExtension ) {
2245 list( ,
$s, $prefix ) = $m;
2250 if ( $first_prefix ) {
2252 $first_prefix =
false;
2256 $might_be_img =
false;
2260 # If we get a ] at the beginning of $m[3] that means we have a link that's something like:
2261 # [[Image:Foo.jpg|[http:
2262 # the real problem is with the $e1 regex
2264 # Still some problems for cases where the ] is meant to be outside punctuation,
2265 # and no image is in sight. See T4095.
2267 &&
substr( $m[3], 0, 1 ) ===
']'
2268 &&
strpos( $text,
'[' ) !==
false
2271 $m[3] =
substr( $m[3], 1 );
2273 # fix up urlencoded title texts
2274 if (
strpos( $m[1],
'%' ) !==
false ) {
2275 # Should anchors '#' also be rejected?
2280 # Invalid, but might be an image with a link in its caption
2281 $might_be_img =
true;
2283 if (
strpos( $m[1],
'%' ) !==
false ) {
2287 }
else { # Invalid
form; output directly
2292 $origLink =
ltrim( $m[1],
' ' );
2294 # Don't allow internal links to pages containing
2295 # PROTO: where PROTO is a valid URL protocol; these
2296 # should be external links.
2297 if (
preg_match(
'/^(?i:' . $this->mUrlProtocols .
')/', $origLink ) ) {
2302 # Make subpage if necessary
2303 if ( $useSubpages ) {
2313 $unstrip = $this->mStripState->killMarkers(
$link );
2314 $noMarkers = ( $unstrip ===
$link );
2316 $nt = $noMarkers ? Title::newFromText(
$link ) :
null;
2317 if ( $nt ===
null ) {
2322 $ns = $nt->getNamespace();
2323 $iw = $nt->getInterwiki();
2325 $noforce = (
substr( $origLink, 0, 1 ) !==
':' );
2327 if ( $might_be_img ) { #
if this is actually an invalid
link
2328 if ( $ns ==
NS_FILE && $noforce ) { # but might be an image
2331 # look at the next 'line' to see if we can close it there
2333 $next_line = $a->current();
2334 if ( $next_line ===
false || $next_line ===
null ) {
2337 $m = explode(
']]', $next_line, 3 );
2338 if ( count( $m ) == 3 ) {
2339 # the first ]] closes the inner link, the second the image
2341 $text .=
"[[{$m[0]}]]{$m[1]}";
2344 }
elseif ( count( $m ) == 2 ) {
2345 # if there's exactly one ]] that's fine, we'll keep looking
2346 $text .=
"[[{$m[0]}]]{$m[1]}";
2348 # if $next_line is invalid too, we need look no further
2354 # we couldn't find the end of this imageLink, so output it raw
2355 # but don't ignore what might be perfectly normal links in the text we've examined
2357 $s .=
"{$prefix}[[$link|$text";
2358 # note: no $trail, because without an end, there *is* no trail
2361 }
else { # it
's not an image, so output it raw
2362 $s .= "{$prefix}[[$link|$text";
2363 # note: no $trail, because without an end, there *is* no trail
2368 $wasblank = ( $text == '' );
2372 # Strip off leading ':
'
2373 $text = substr( $text, 1 );
2376 # T6598 madness. Handle the quotes only if they come from the alternate part
2377 # [[Lista d''e paise d''o munno]] -> <a href="...">Lista d''e paise d''o munno</a>
2378 # [[Criticism of Harry Potter|Criticism of ''Harry Potter'']]
2379 # -> <a href="Criticism of Harry Potter">Criticism of <i>Harry Potter</i></a>
2380 $text = $this->doQuotes( $text );
2383 # Link not escaped by : , create the various objects
2384 if ( $noforce && !$nt->wasLocalInterwiki() ) {
2387 $iw && $this->mOptions->getInterwikiMagic() && $nottalk && (
2388 Language::fetchLanguageName( $iw, null, 'mw
' ) ||
2389 in_array( $iw, $this->siteConfig->get( 'ExtraInterlanguageLinkPrefixes
' ) )
2392 # T26502: filter duplicates
2393 if ( !isset( $this->mLangLinkLanguages[$iw] ) ) {
2394 $this->mLangLinkLanguages[$iw] = true;
2395 $this->mOutput->addLanguageLink( $nt->getFullText() );
2401 $s = rtrim( $s . $prefix ) . $trail; # T175416
2405 if ( $ns == NS_FILE ) {
2406 if ( !wfIsBadImage( $nt->getDBkey(), $this->mTitle ) ) {
2408 # if no parameters were passed, $text
2409 # becomes something like "File:Foo.png",
2410 # which we don't want to pass on to the
2414 # recursively parse links inside the image caption
2415 # actually, this will parse them in any other parameters, too,
2416 # but it might be hard to fix that, and it doesn't matter ATM
2420 # cloak any absolute URLs inside the image markup, so replaceExternalLinks() won't touch them
2436 $sortkey = Sanitizer::decodeCharReferences( $sortkey );
2439 $this->mOutput->addCategory( $nt->getDBkey(), $sortkey );
2445 # Self-link checking. For some languages, variants of the title are checked in
2446 # LinkHolderArray::doVariants() to allow batching the existence checks necessary
2447 # for linking to a different variant.
2448 if ( $ns !=
NS_SPECIAL && $nt->equals( $this->mTitle ) && !$nt->hasFragment() ) {
2453 # NS_MEDIA is a pseudo-namespace for linking directly to a file
2454 # @todo FIXME: Should do batch file existence checks, see comment below
2456 # Give extensions a chance to select the file revision for us
2460 [ $this, $nt, &
$options, &$descQuery ] );
2461 # Fetch and register the file (file title may be different via hooks)
2463 # Cloak with NOPARSE to avoid replacement in replaceExternalLinks
2469 # Some titles, such as valid special pages or files in foreign repos, should
2470 # be shown as bluelinks even though they're not included in the page table
2471 # @todo FIXME: isAlwaysKnown() can be expensive for file links; we should really do
2472 # batch file existence checks for NS_FILE and NS_MEDIA
2473 if ( $iw ==
'' && $nt->isAlwaysKnown() ) {
2474 $this->mOutput->addLink( $nt );
2477 # Links will be added to the output link list after checking
2478 $s .= $holders->makeHolder( $nt, $text, [], $trail, $prefix );
2500 if ( $text ==
'' ) {
2504 $link = $this->getLinkRenderer()->makeKnownLink(
2505 $nt,
new HtmlArmor(
"$prefix$text$inside" )
2522 return preg_replace(
'/\b((?i)' . $this->mUrlProtocols .
')/',
2523 self::MARKER_PREFIX .
"NOPARSE$1", $text );
2531 # Some namespaces don't allow subpages
2532 return MWNamespace::hasSubpages( $this->mTitle->getNamespace() );
2571 if (
is_null( $this->mTitle ) ) {
2576 throw new MWException( __METHOD__ .
' Should only be '
2577 .
' called while parsing (no title set)' );
2587 if ( Hooks::run(
'ParserGetVariableValueVarCache', [ &
$parser, &$this->mVarCache ] )
2588 &&
isset( $this->mVarCache[$index] )
2590 return $this->mVarCache[$index];
2593 $ts =
wfTimestamp( TS_UNIX, $this->mOptions->getTimestamp() );
2594 Hooks::run(
'ParserGetVariableValueTs', [ &
$parser, &$ts ] );
2597 static $slowRevWords = [
'revisionid' =>
true ];
2599 isset( $slowRevWords[$index] ) &&
2600 $this->siteConfig->get(
'MiserMode' ) &&
2601 !$this->mOptions->getInterfaceMessage() &&
2603 MWNamespace::isContent( $this->mTitle->getNamespace() )
2605 return $this->mRevisionId ?
'-' :
'';
2608 $pageLang = $this->getFunctionLang();
2614 case 'currentmonth':
2615 $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->
format(
'm' ),
true );
2617 case 'currentmonth1':
2618 $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->
format(
'n' ),
true );
2620 case 'currentmonthname':
2621 $value = $pageLang->getMonthName( MWTimestamp::getInstance( $ts )->
format(
'n' ) );
2623 case 'currentmonthnamegen':
2624 $value = $pageLang->getMonthNameGen( MWTimestamp::getInstance( $ts )->
format(
'n' ) );
2626 case 'currentmonthabbrev':
2627 $value = $pageLang->getMonthAbbreviation( MWTimestamp::getInstance( $ts )->
format(
'n' ) );
2630 $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->
format(
'j' ),
true );
2633 $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->
format(
'd' ),
true );
2636 $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->
format(
'm' ),
true );
2639 $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->
format(
'n' ),
true );
2641 case 'localmonthname':
2642 $value = $pageLang->getMonthName( MWTimestamp::getLocalInstance( $ts )->
format(
'n' ) );
2644 case 'localmonthnamegen':
2645 $value = $pageLang->getMonthNameGen( MWTimestamp::getLocalInstance( $ts )->
format(
'n' ) );
2647 case 'localmonthabbrev':
2648 $value = $pageLang->getMonthAbbreviation( MWTimestamp::getLocalInstance( $ts )->
format(
'n' ) );
2651 $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->
format(
'j' ),
true );
2654 $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->
format(
'd' ),
true );
2662 case 'fullpagename':
2665 case 'fullpagenamee':
2671 case 'subpagenamee':
2674 case 'rootpagename':
2677 case 'rootpagenamee':
2681 $this->mTitle->getRootText()
2684 case 'basepagename':
2687 case 'basepagenamee':
2691 $this->mTitle->getBaseText()
2694 case 'talkpagename':
2695 if ( $this->mTitle->canHaveTalkPage() ) {
2696 $talkPage = $this->mTitle->getTalkPage();
2702 case 'talkpagenamee':
2703 if ( $this->mTitle->canHaveTalkPage() ) {
2704 $talkPage = $this->mTitle->getTalkPage();
2710 case 'subjectpagename':
2711 $subjPage = $this->mTitle->getSubjectPage();
2714 case 'subjectpagenamee':
2715 $subjPage = $this->mTitle->getSubjectPage();
2719 $pageid = $this->getTitle()->getArticleID();
2720 if ( $pageid == 0 ) {
2721 # 0 means the page doesn't exist in the database,
2722 # which means the user is previewing a new page.
2723 # The vary-revision flag must be set, because the magic word
2724 # will have a different value once the page is saved.
2725 $this->mOutput->setFlag(
'vary-revision' );
2726 wfDebug( __METHOD__ .
": {{PAGEID}} used in a new page, setting vary-revision...\n" );
2728 $value = $pageid ?:
null;
2731 # Let the edit saving system know we should parse the page
2732 # *after* a revision ID has been assigned.
2733 $this->mOutput->setFlag(
'vary-revision-id' );
2734 wfDebug( __METHOD__ .
": {{REVISIONID}} used, setting vary-revision-id...\n" );
2735 $value = $this->mRevisionId;
2738 $rev = $this->getRevisionObject();
2745 $value = $this->mOptions->getSpeculativeRevId();
2747 $this->mOutput->setSpeculativeRevIdUsed(
$value );
2752 $value = (
int)$this->getRevisionTimestampSubstring( 6, 2, self::MAX_TTS, $index );
2754 case 'revisionday2':
2755 $value = $this->getRevisionTimestampSubstring( 6, 2, self::MAX_TTS, $index );
2757 case 'revisionmonth':
2758 $value = $this->getRevisionTimestampSubstring( 4, 2, self::MAX_TTS, $index );
2760 case 'revisionmonth1':
2761 $value = (
int)$this->getRevisionTimestampSubstring( 4, 2, self::MAX_TTS, $index );
2763 case 'revisionyear':
2764 $value = $this->getRevisionTimestampSubstring( 0, 4, self::MAX_TTS, $index );
2766 case 'revisiontimestamp':
2767 # Let the edit saving system know we should parse the page
2768 # *after* a revision ID has been assigned. This is for null edits.
2769 $this->mOutput->setFlag(
'vary-revision' );
2770 wfDebug( __METHOD__ .
": {{REVISIONTIMESTAMP}} used, setting vary-revision...\n" );
2771 $value = $this->getRevisionTimestamp();
2773 case 'revisionuser':
2774 # Let the edit saving system know we should parse the page
2775 # *after* a revision ID has been assigned for null edits.
2776 $this->mOutput->setFlag(
'vary-user' );
2777 wfDebug( __METHOD__ .
": {{REVISIONUSER}} used, setting vary-user...\n" );
2778 $value = $this->getRevisionUser();
2780 case 'revisionsize':
2781 $value = $this->getRevisionSize();
2785 $this->contLang->getNsText( $this->mTitle->getNamespace() ) );
2788 $value =
wfUrlencode( $this->contLang->getNsText( $this->mTitle->getNamespace() ) );
2790 case 'namespacenumber':
2791 $value = $this->mTitle->getNamespace();
2794 $value = $this->mTitle->canHaveTalkPage()
2795 ?
str_replace(
'_',
' ', $this->mTitle->getTalkNsText() )
2799 $value = $this->mTitle->canHaveTalkPage() ?
wfUrlencode( $this->mTitle->getTalkNsText() ) :
'';
2801 case 'subjectspace':
2804 case 'subjectspacee':
2807 case 'currentdayname':
2808 $value = $pageLang->getWeekdayName( (
int)MWTimestamp::getInstance( $ts )->format(
'w' ) + 1 );
2811 $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format(
'Y' ),
true );
2817 $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format(
'H' ),
true );
2820 # @bug T6594 PHP5 has it zero padded, PHP4 does not, cast to
2821 # int to remove the padding
2822 $value = $pageLang->formatNum( (
int)MWTimestamp::getInstance( $ts )->format(
'W' ) );
2825 $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format(
'w' ) );
2827 case 'localdayname':
2828 $value = $pageLang->getWeekdayName(
2829 (
int)MWTimestamp::getLocalInstance( $ts )->format(
'w' ) + 1
2833 $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format(
'Y' ),
true );
2836 $value = $pageLang->time(
2837 MWTimestamp::getLocalInstance( $ts )->format(
'YmdHis' ),
2843 $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format(
'H' ),
true );
2846 # @bug T6594 PHP5 has it zero padded, PHP4 does not, cast to
2847 # int to remove the padding
2848 $value = $pageLang->formatNum( (
int)MWTimestamp::getLocalInstance( $ts )->format(
'W' ) );
2851 $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format(
'w' ) );
2853 case 'numberofarticles':
2856 case 'numberoffiles':
2859 case 'numberofusers':
2862 case 'numberofactiveusers':
2865 case 'numberofpages':
2868 case 'numberofadmins':
2871 case 'numberofedits':
2874 case 'currenttimestamp':
2877 case 'localtimestamp':
2878 $value = MWTimestamp::getLocalInstance( $ts )->format(
'YmdHis' );
2880 case 'currentversion':
2884 return $this->siteConfig->get(
'ArticlePath' );
2886 return $this->siteConfig->get(
'Sitename' );
2888 return $this->siteConfig->get(
'Server' );
2890 return $this->siteConfig->get(
'ServerName' );
2892 return $this->siteConfig->get(
'ScriptPath' );
2894 return $this->siteConfig->get(
'StylePath' );
2895 case 'directionmark':
2896 return $pageLang->getDirMark();
2897 case 'contentlanguage':
2898 return $this->siteConfig->get(
'LanguageCode' );
2899 case 'pagelanguage':
2900 $value = $pageLang->getCode();
2902 case 'cascadingsources':
2908 'ParserGetVariableValueSwitch',
2909 [ &
$parser, &$this->mVarCache, &$index, &
$ret, &$frame ]
2916 $this->mVarCache[$index] =
$value;
2930 # Get the timezone-adjusted timestamp to be used for this revision
2931 $resNow =
substr( $this->getRevisionTimestamp(), $start, $len );
2932 # Possibly set vary-revision if there is not yet an associated revision
2933 if ( !$this->getRevisionObject() ) {
2934 # Get the timezone-adjusted timestamp $mtts seconds in the future
2936 $this->contLang->userAdjust(
wfTimestamp( TS_MW, time() + $mtts ),
'' ),
2941 if ( $resNow !== $resThen ) {
2942 # Let the edit saving system know we should parse the page
2943 # *after* a revision ID has been assigned. This is for null edits.
2944 $this->mOutput->setFlag(
'vary-revision' );
2945 wfDebug( __METHOD__ .
": $variable used, setting vary-revision...\n" );
2958 $variableIDs = $this->magicWordFactory->getVariableIDs();
2959 $substIDs = $this->magicWordFactory->getSubstIDs();
2961 $this->mVariables = $this->magicWordFactory->newArray( $variableIDs );
2962 $this->mSubstWords = $this->magicWordFactory->newArray( $substIDs );
2988 $dom = $this->getPreprocessor()->preprocessToObj( $text, $flags );
3002 $trimmed =
rtrim( $ltrimmed );
3005 $w2 =
substr( $ltrimmed, -$diff );
3009 return [
$w1, $trimmed,
$w2 ];
3033 # Is there any text? Also, Prevent too big inclusions!
3034 $textSize =
strlen( $text );
3035 if ( $textSize < 1 || $textSize > $this->mOptions->getMaxIncludeSize() ) {
3039 if ( $frame ===
false ) {
3040 $frame = $this->getPreprocessor()->newFrame();
3042 wfDebug( __METHOD__ .
" called using plain parameters instead of "
3043 .
"a PPFrame instance. Creating custom frame.\n" );
3044 $frame = $this->getPreprocessor()->newCustomFrame( $frame );
3047 $dom = $this->preprocessToDom( $text );
3048 $flags = $argsOnly ? PPFrame::NO_TEMPLATES : 0;
3049 $text = $frame->expand( $dom, $flags );
3064 foreach (
$args as $arg ) {
3065 $eqpos =
strpos( $arg,
'=' );
3066 if ( $eqpos ===
false ) {
3067 $assocArgs[$index++] =
$arg;
3071 if (
$value ===
false ) {
3074 if ( $name !==
false ) {
3110 # does no harm if $current and $max are present but are unnecessary for the message
3111 # Not doing ->inLanguage( $this->mOptions->getUserLangObj() ), since this is shown
3112 # only during preview, and that would split the parser cache unnecessarily.
3113 $warning =
wfMessage(
"$limitationType-warning" )->numParams( $current, $max )
3115 $this->mOutput->addWarning( $warning );
3116 $this->addTrackingCategory(
"$limitationType-category" );
3141 $forceRawInterwiki =
false;
3143 $isChildObj =
false;
3145 $isLocalObj =
false;
3147 # Title object, where $text came from
3150 # $part1 is the bit before the first |, and must contain only title characters.
3151 # Various prefixes will be stripped from it later.
3152 $titleWithSpaces = $frame->expand( $piece[
'title'] );
3153 $part1 =
trim( $titleWithSpaces );
3156 # Original title text preserved for various purposes
3159 # $args is a list of argument nodes, starting from index 0, not including $part1
3160 # @todo FIXME: If piece['parts'] is null then the call to getLength()
3161 # below won't work b/c this $args isn't an object
3162 $args = ( $piece[
'parts'] ==
null ) ? [] : $piece[
'parts'];
3164 $profileSection =
null;
3168 $substMatch = $this->mSubstWords->matchStartAndRemove( $part1 );
3170 # Possibilities for substMatch: "subst", "safesubst" or FALSE
3171 # Decide whether to expand template or keep wikitext as-is.
3172 if ( $this->ot[
'wiki'] ) {
3173 if ( $substMatch ===
false ) {
3174 $literal =
true; # literal when in PST with no prefix
3179 if ( $substMatch ==
'subst' ) {
3180 $literal =
true; # literal when not in PST with
plain subst:
3182 $literal =
false; # expand when not in PST with
safesubst: or no prefix
3186 $text = $frame->virtualBracketedImplode(
'{{',
'|',
'}}', $titleWithSpaces,
$args );
3193 if ( !$found &&
$args->getLength() == 0 ) {
3194 $id = $this->mVariables->matchStartToEnd( $part1 );
3195 if ( $id !==
false ) {
3196 $text = $this->getVariableValue( $id, $frame );
3197 if ( $this->magicWordFactory->getCacheTTL( $id ) > -1 ) {
3198 $this->mOutput->updateCacheExpiry(
3199 $this->magicWordFactory->getCacheTTL( $id ) );
3205 # MSG, MSGNW and RAW
3208 $mwMsgnw = $this->magicWordFactory->get(
'msgnw' );
3209 if ( $mwMsgnw->matchStartAndRemove( $part1 ) ) {
3212 # Remove obsolete MSG:
3213 $mwMsg = $this->magicWordFactory->get(
'msg' );
3214 $mwMsg->matchStartAndRemove( $part1 );
3218 $mwRaw = $this->magicWordFactory->get(
'raw' );
3219 if ( $mwRaw->matchStartAndRemove( $part1 ) ) {
3220 $forceRawInterwiki =
true;
3226 $colonPos =
strpos( $part1,
':' );
3227 if ( $colonPos !==
false ) {
3228 $func =
substr( $part1, 0, $colonPos );
3229 $funcArgs = [
trim(
substr( $part1, $colonPos + 1 ) ) ];
3230 $argsLength =
$args->getLength();
3232 $funcArgs[] =
$args->item( $i );
3235 $result = $this->callParserFunction( $frame, $func, $funcArgs );
3238 if (
isset( $result[
'title'] ) ) {
3239 $title = $result[
'title'];
3241 if (
isset( $result[
'found'] ) ) {
3242 $found = $result[
'found'];
3246 $text = $result[
'text'];
3248 if (
isset( $result[
'nowiki'] ) ) {
3249 $nowiki = $result[
'nowiki'];
3251 if (
isset( $result[
'isHTML'] ) ) {
3252 $isHTML = $result[
'isHTML'];
3254 if (
isset( $result[
'forceRawInterwiki'] ) ) {
3255 $forceRawInterwiki = $result[
'forceRawInterwiki'];
3257 if (
isset( $result[
'isChildObj'] ) ) {
3258 $isChildObj = $result[
'isChildObj'];
3260 if (
isset( $result[
'isLocalObj'] ) ) {
3261 $isLocalObj = $result[
'isLocalObj'];
3266 # Finish mangling title and then check for loops.
3267 # Set $title to a Title object and $titleText to the PDBK
3270 # Split the title into page and subpage
3272 $relative = $this->maybeDoSubpageLink( $part1, $subpage );
3273 if ( $part1 !== $relative ) {
3275 $ns = $this->mTitle->getNamespace();
3277 $title = Title::newFromText( $part1, $ns );
3279 $titleText = $title->getPrefixedText();
3280 # Check for language variants if the template is not found
3281 if ( $this->getTargetLanguage()->hasVariants() && $title->getArticleID() == 0 ) {
3282 $this->getTargetLanguage()->findVariantLink( $part1, $title,
true );
3284 # Do recursion depth check
3285 $limit = $this->mOptions->getMaxTemplateDepth();
3286 if ( $frame->depth >= $limit ) {
3288 $text =
'<span class="error">'
3289 .
wfMessage(
'parser-template-recursion-depth-warning' )
3290 ->numParams( $limit )->inContentLanguage()->text()
3296 # Load from database
3297 if ( !$found && $title ) {
3298 $profileSection = $this->mProfiler->scopedProfileIn( $title->getPrefixedDBkey() );
3299 if ( !$title->isExternal() ) {
3300 if ( $title->isSpecialPage()
3301 && $this->mOptions->getAllowSpecialInclusion()
3302 && $this->ot[
'html']
3304 $specialPage = $this->specialPageFactory->getPage( $title->getDBkey() );
3309 $argsLength =
$args->getLength();
3311 $bits =
$args->item( $i )->splitArg();
3312 if (
strval( $bits[
'index'] ) ===
'' ) {
3313 $name =
trim( $frame->expand( $bits[
'name'], PPFrame::STRIP_COMMENTS ) );
3314 $value =
trim( $frame->expand( $bits[
'value'] ) );
3323 if ( $specialPage && $specialPage->maxIncludeCacheTime() === 0 ) {
3324 $context->setUser( $this->getUser() );
3329 $context->setLanguage( $this->mOptions->getUserLangObj() );
3330 $ret = $this->specialPageFactory->capturePath( $title,
$context, $this->getLinkRenderer() );
3332 $text =
$context->getOutput()->getHTML();
3333 $this->mOutput->addOutputPageMetadata(
$context->getOutput() );
3336 if ( $specialPage && $specialPage->maxIncludeCacheTime() !==
false ) {
3337 $this->mOutput->updateRuntimeAdaptiveExpiry(
3338 $specialPage->maxIncludeCacheTime()
3342 }
elseif ( MWNamespace::isNonincludable( $title->getNamespace() ) ) {
3343 $found =
false; # access denied
3344 wfDebug( __METHOD__ .
": template inclusion denied for " .
3345 $title->getPrefixedDBkey() .
"\n" );
3347 list( $text, $title ) = $this->getTemplateDom( $title );
3348 if ( $text !==
false ) {
3354 # If the title is valid but undisplayable, make a link to it
3355 if ( !$found && ( $this->ot[
'html'] || $this->ot[
'pre'] ) ) {
3356 $text =
"[[:$titleText]]";
3359 }
elseif ( $title->isTrans() ) {
3360 # Interwiki transclusion
3361 if ( $this->ot[
'html'] && !$forceRawInterwiki ) {
3362 $text = $this->interwikiTransclude( $title,
'render' );
3365 $text = $this->interwikiTransclude( $title,
'raw' );
3366 # Preprocess it like a template
3367 $text = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
3373 # Do infinite loop check
3374 # This has to be done after redirect resolution to avoid infinite loops via redirects
3375 if ( !$frame->loopCheck( $title ) ) {
3377 $text =
'<span class="error">'
3378 .
wfMessage(
'parser-template-loop-warning', $titleText )->inContentLanguage()->text()
3380 $this->addTrackingCategory(
'template-loop-category' );
3381 $this->mOutput->addWarning(
wfMessage(
'template-loop-warning',
3383 wfDebug( __METHOD__ .
": template loop broken at '$titleText'\n" );
3387 # If we haven't found text to substitute by now, we're done
3388 # Recover the source wikitext and return it
3390 $text = $frame->virtualBracketedImplode(
'{{',
'|',
'}}', $titleWithSpaces,
$args );
3391 if ( $profileSection ) {
3392 $this->mProfiler->scopedProfileOut( $profileSection );
3394 return [
'object' => $text ];
3397 # Expand DOM-style return values in a child frame
3398 if ( $isChildObj ) {
3399 # Clean up argument array
3400 $newFrame = $frame->newChild(
$args, $title );
3403 $text = $newFrame->expand( $text, PPFrame::RECOVER_ORIG );
3404 }
elseif ( $titleText !==
false && $newFrame->isEmpty() ) {
3405 # Expansion is eligible for the empty-frame cache
3406 $text = $newFrame->cachedExpand( $titleText, $text );
3408 # Uncached expansion
3409 $text = $newFrame->expand( $text );
3412 if ( $isLocalObj && $nowiki ) {
3413 $text = $frame->expand( $text, PPFrame::RECOVER_ORIG );
3414 $isLocalObj =
false;
3417 if ( $profileSection ) {
3418 $this->mProfiler->scopedProfileOut( $profileSection );
3421 # Replace raw HTML by a placeholder
3423 $text = $this->insertStripItem( $text );
3424 }
elseif ( $nowiki && ( $this->ot[
'html'] || $this->ot[
'pre'] ) ) {
3425 # Escape nowiki-style return values
3428 && !$piece[
'lineStart']
3429 &&
preg_match(
'/^(?:{\\||:|;|#|\*)/', $text )
3431 # T2529: if the template begins with a table or block-level
3432 # element, it should be treated as beginning a new line.
3433 # This behavior is somewhat controversial.
3434 $text =
"\n" . $text;
3437 if (
is_string( $text ) && !$this->incrementIncludeSize(
'post-expand',
strlen( $text ) ) ) {
3438 # Error, oversize inclusion
3439 if ( $titleText !==
false ) {
3440 # Make a working, properly escaped link if possible (T25588)
3441 $text =
"[[:$titleText]]";
3443 # This will probably not be a working link, but at least it may
3444 # provide some hint of where the problem is
3446 $text =
"[[:$originalTitle]]";
3448 $text .= $this->insertStripItem(
'<!-- WARNING: template omitted, '
3449 .
'post-expand include size too large -->' );
3450 $this->limitationWarn(
'post-expand-template-inclusion' );
3453 if ( $isLocalObj ) {
3454 $ret = [
'object' => $text ];
3456 $ret = [
'text' => $text ];
3482 # Case sensitive functions
3483 if (
isset( $this->mFunctionSynonyms[1][$function] ) ) {
3484 $function = $this->mFunctionSynonyms[1][
$function];
3486 # Case insensitive functions
3487 $function = $this->contLang->lc( $function );
3488 if (
isset( $this->mFunctionSynonyms[0][$function] ) ) {
3489 $function = $this->mFunctionSynonyms[0][
$function];
3491 return [
'found' =>
false ];
3495 list( $callback, $flags ) = $this->mFunctionHooks[
$function];
3501 if ( $flags & self::SFH_OBJECT_ARGS ) {
3502 # Convert arguments to PPNodes and collect for appending to $allArgs
3504 foreach (
$args as $k => $v ) {
3505 if ( $v instanceof
PPNode || $k === 0 ) {
3508 $funcArgs[] = $this->mPreprocessor->newPartNodeArray( [ $k => $v ] )->item( 0 );
3512 # Add a frame parameter, and pass the arguments as an array
3516 # Convert arguments to plain text and append to $allArgs
3517 foreach (
$args as $k => $v ) {
3518 if ( $v instanceof
PPNode ) {
3519 $allArgs[] =
trim( $frame->expand( $v ) );
3521 $allArgs[] =
trim( $v );
3523 $allArgs[] =
trim(
"$k=$v" );
3528 $result = $callback( ...$allArgs );
3530 # The interface for function hooks allows them to return a wikitext
3531 # string or an array containing the string and any flags. This mungs
3532 # things around to match what this method should return.
3539 if (
isset( $result[0] ) && !
isset( $result[
'text'] ) ) {
3540 $result[
'text'] = $result[0];
3542 unset( $result[0] );
3549 $preprocessFlags = 0;
3550 if (
isset( $result[
'noparse'] ) ) {
3551 $noparse = $result[
'noparse'];
3553 if (
isset( $result[
'preprocessFlags'] ) ) {
3554 $preprocessFlags = $result[
'preprocessFlags'];
3558 $result[
'text'] = $this->preprocessToDom( $result[
'text'], $preprocessFlags );
3559 $result[
'isChildObj'] =
true;
3575 $titleText = $title->getPrefixedDBkey();
3577 if (
isset( $this->mTplRedirCache[$titleText] ) ) {
3578 list( $ns, $dbk ) = $this->mTplRedirCache[$titleText];
3579 $title = Title::makeTitle( $ns, $dbk );
3580 $titleText = $title->getPrefixedDBkey();
3582 if (
isset( $this->mTplDomCache[$titleText] ) ) {
3583 return [ $this->mTplDomCache[$titleText],
$title ];
3586 # Cache miss, go to the database
3587 list( $text, $title ) = $this->fetchTemplateAndTitle( $title );
3589 if ( $text ===
false ) {
3590 $this->mTplDomCache[$titleText] =
false;
3591 return [
false,
$title ];
3594 $dom = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
3595 $this->mTplDomCache[$titleText] =
$dom;
3597 if ( !$title->equals( $cacheTitle ) ) {
3598 $this->mTplRedirCache[$cacheTitle->getPrefixedDBkey()] =
3599 [ $title->getNamespace(), $title->getDBkey() ];
3617 $cacheKey = $title->getPrefixedDBkey();
3618 if ( !$this->currentRevisionCache ) {
3619 $this->currentRevisionCache =
new MapCacheLRU( 100 );
3621 if ( !$this->currentRevisionCache->has( $cacheKey ) ) {
3622 $this->currentRevisionCache->set( $cacheKey,
3624 call_user_func( $this->mOptions->getCurrentRevisionCallback(), $title, $this )
3627 return $this->currentRevisionCache->get( $cacheKey );
3652 $templateCb = $this->mOptions->getTemplateCallback();
3655 $text = $stuff[
'text'];
3657 $text =
strtr( $text,
"\x7f",
"?" );
3659 $finalTitle = $stuff[
'finalTitle'] ??
$title;
3660 if (
isset( $stuff[
'deps'] ) ) {
3661 foreach ( $stuff[
'deps'] as $dep ) {
3662 $this->mOutput->addTemplate( $dep[
'title'], $dep[
'page_id'], $dep[
'rev_id'] );
3663 if ( $dep[
'title']->equals( $this->getTitle() ) ) {
3666 $this->mOutput->setFlag(
'vary-revision' );
3679 return $this->fetchTemplateAndTitle( $title )[0];
3692 $text = $skip =
false;
3696 # Loop to fetch the article, with up to 1 redirect
3697 for ( $i = 0; $i < 2 &&
is_object( $title ); $i++ ) {
3698 # Give extensions a chance to select the revision instead
3699 $id =
false; # Assume current
3700 Hooks::run(
'BeforeParserFetchTemplateAndtitle',
3701 [
$parser, $title, &$skip, &$id ] );
3707 'page_id' => $title->getArticleID(),
3716 $rev =
$parser->fetchCurrentRevisionOfTitle( $title );
3720 $rev_id =
$rev ?
$rev->getId() : 0;
3721 # If there is no current revision, there is no page
3722 if ( $id ===
false && !
$rev ) {
3723 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3724 $linkCache->addBadLinkObj( $title );
3729 'page_id' => $title->getArticleID(),
3731 if (
$rev && !$title->equals(
$rev->getTitle() ) ) {
3732 # We fetched a rev from a different title; register it too...
3734 'title' =>
$rev->getTitle(),
3735 'page_id' =>
$rev->getPage(),
3743 Hooks::run(
'ParserFetchTemplate',
3746 if ( $text ===
false || $text ===
null ) {
3750 }
elseif ( $title->getNamespace() == NS_MEDIAWIKI ) {
3751 $message =
wfMessage( MediaWikiServices::getInstance()->getContentLanguage()->
3752 lcfirst( $title->getText() ) )->inContentLanguage();
3753 if ( !$message->exists() ) {
3758 $text = $message->plain();
3767 $title =
$content->getRedirectTarget();
3785 return $this->fetchFileAndTitle( $title,
$options )[0];
3796 $file = $this->fetchFileNoRegister( $title,
$options );
3798 $time = $file ? $file->getTimestamp() :
false;
3799 $sha1 = $file ? $file->getSha1() :
false;
3800 # Register the file as a dependency...
3801 $this->mOutput->addImage( $title->getDBkey(),
$time, $sha1 );
3802 if ( $file && !$title->equals( $file->getTitle() ) ) {
3803 # Update fetched file title
3804 $title = $file->getTitle();
3805 $this->mOutput->addImage( $title->getDBkey(),
$time, $sha1 );
3840 if ( !$this->siteConfig->get(
'EnableScaryTranscluding' ) ) {
3841 return wfMessage(
'scarytranscludedisabled' )->inContentLanguage()->text();
3844 $url = $title->getFullURL( [
'action' => $action ] );
3845 if (
strlen( $url ) > 1024 ) {
3846 return wfMessage(
'scarytranscludetoolong' )->inContentLanguage()->text();
3849 $wikiId = $title->getTransWikiID();
3852 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
3856 'interwiki-transclude',
3857 ( $wikiId !==
false ) ? $wikiId :
'external',
3860 $this->siteConfig->get(
'TranscludeCacheExpiry' ),
3862 $req = MWHttpRequest::factory( $url, [],
$fname );
3866 $ttl = $cache::TTL_UNCACHEABLE;
3867 }
elseif (
$req->getResponseHeader(
'X-Database-Lagged' ) !==
null ) {
3868 $ttl = min( $cache::TTL_LAGGED, $ttl );
3872 'text' =>
$status->isOK() ?
$req->getContent() :
null,
3873 'code' =>
$req->getStatus()
3877 'checkKeys' => ( $wikiId !==
false )
3878 ? [
$cache->makeGlobalKey(
'interwiki-page', $wikiId, $title->getDBkey() ) ]
3880 'pcGroup' =>
'interwiki-transclude:5',
3881 'pcTTL' => $cache::TTL_PROC_LONG
3886 $text =
$data[
'text'];
3889 $text =
wfMessage(
'scarytranscludefailed-httpstatus' )
3890 ->params( $url,
$data[
'code'] )->inContentLanguage()->text();
3892 $text =
wfMessage(
'scarytranscludefailed', $url )->inContentLanguage()->text();
3909 $parts = $piece[
'parts'];
3910 $nameWithSpaces = $frame->expand( $piece[
'title'] );
3911 $argName =
trim( $nameWithSpaces );
3913 $text = $frame->getArgument( $argName );
3914 if ( $text ===
false && $parts->getLength() > 0
3915 && ( $this->ot[
'html']
3917 || ( $this->ot[
'wiki'] && $frame->isTemplate() )
3920 # No match in frame, use the supplied default
3921 $object = $parts->item( 0 )->getChildren();
3923 if ( !$this->incrementIncludeSize(
'arg',
strlen( $text ) ) ) {
3924 $error =
'<!-- WARNING: argument omitted, expansion size too large -->';
3925 $this->limitationWarn(
'post-expand-template-argument' );
3928 if ( $text ===
false && $object ===
false ) {
3930 $object = $frame->virtualBracketedImplode(
'{{{',
'|',
'}}}', $nameWithSpaces, $parts );
3932 if ( $error !==
false ) {
3935 if ( $object !==
false ) {
3936 $ret = [
'object' => $object ];
3938 $ret = [
'text' => $text ];
3960 static $errorStr =
'<span class="error">';
3961 static $errorLen = 20;
3963 $name = $frame->expand(
$params[
'name'] );
3964 if (
substr( $name, 0, $errorLen ) === $errorStr ) {
3971 if (
substr( $attrText, 0, $errorLen ) === $errorStr ) {
3981 $marker = self::MARKER_PREFIX .
"-$name-"
3984 $isFunctionTag =
isset( $this->mFunctionTagHooks[
strtolower( $name )] ) &&
3985 ( $this->ot[
'html'] || $this->ot[
'pre'] );
3986 if ( $isFunctionTag ) {
3987 $markerType =
'none';
3989 $markerType =
'general';
3991 if ( $this->ot[
'html'] || $isFunctionTag ) {
3993 $attributes = Sanitizer::decodeTagAttributes( $attrText );
3995 $attributes +=
$params[
'attributes'];
3998 if (
isset( $this->mTagHooks[$name] ) ) {
4000 [
$content, $attributes, $this, $frame ] );
4001 }
elseif (
isset( $this->mFunctionTagHooks[$name] ) ) {
4002 list( $callback, ) = $this->mFunctionTagHooks[
$name];
4008 $output =
'<span class="error">Invalid tag extension name: ' .
4016 if (
isset( $flags[
'markerType'] ) ) {
4017 $markerType = $flags[
'markerType'];
4025 foreach (
$params[
'attributes'] as $attrName => $attrValue ) {
4031 $output =
"<$name$attrText/>";
4034 if (
substr( $close, 0, $errorLen ) === $errorStr ) {
4038 $output =
"<$name$attrText>$content$close";
4042 if ( $markerType ===
'none' ) {
4044 }
elseif ( $markerType ===
'nowiki' ) {
4045 $this->mStripState->addNoWiki( $marker,
$output );
4046 }
elseif ( $markerType ===
'general' ) {
4047 $this->mStripState->addGeneral( $marker,
$output );
4049 throw new MWException( __METHOD__ .
': invalid marker type' );
4062 if ( $this->mIncludeSizes[
$type] + $size > $this->mOptions->getMaxIncludeSize() ) {
4065 $this->mIncludeSizes[
$type] += $size;
4076 $this->mExpensiveFunctionCount++;
4077 return $this->mExpensiveFunctionCount <= $this->mOptions->getExpensiveParserFunctionLimit();
4089 # The position of __TOC__ needs to be recorded
4090 $mw = $this->magicWordFactory->get(
'toc' );
4091 if ( $mw->match( $text ) ) {
4092 $this->mShowToc =
true;
4093 $this->mForceTocPosition =
true;
4095 # Set a placeholder. At the end we'll fill it in with the TOC.
4096 $text = $mw->replace(
'<!--MWTOC\'"-->', $text, 1 );
4098 # Only keep the first one.
4099 $text = $mw->replace(
'', $text );
4102 # Now match and remove the rest of them
4103 $mwa = $this->magicWordFactory->getDoubleUnderscoreArray();
4104 $this->mDoubleUnderscores = $mwa->matchAndRemove( $text );
4106 if (
isset( $this->mDoubleUnderscores[
'nogallery'] ) ) {
4107 $this->mOutput->mNoGallery =
true;
4109 if (
isset( $this->mDoubleUnderscores[
'notoc'] ) && !$this->mForceTocPosition ) {
4110 $this->mShowToc =
false;
4112 if (
isset( $this->mDoubleUnderscores[
'hiddencat'] )
4115 $this->addTrackingCategory(
'hidden-category-category' );
4117 # (T10068) Allow control over whether robots index a page.
4118 # __INDEX__ always overrides __NOINDEX__, see T16899
4119 if (
isset( $this->mDoubleUnderscores[
'noindex'] ) && $this->mTitle->canUseNoindex() ) {
4120 $this->mOutput->setIndexPolicy(
'noindex' );
4121 $this->addTrackingCategory(
'noindex-category' );
4123 if (
isset( $this->mDoubleUnderscores[
'index'] ) && $this->mTitle->canUseNoindex() ) {
4124 $this->mOutput->setIndexPolicy(
'index' );
4125 $this->addTrackingCategory(
'index-category' );
4128 # Cache all double underscores in the database
4129 foreach ( $this->mDoubleUnderscores as $key => $val ) {
4130 $this->mOutput->setProperty( $key,
'' );
4142 return $this->mOutput->addTrackingCategory( $msg, $this->mTitle );
4162 # Inhibit editsection links if requested in the page
4163 if (
isset( $this->mDoubleUnderscores[
'noeditsection'] ) ) {
4164 $maybeShowEditLink =
false;
4166 $maybeShowEditLink =
true;
4169 # Get all headlines for numbering them and adding funky stuff like [edit]
4170 # links - this is for later, but we need the number of headlines right now
4171 # NOTE: white space in headings have been trimmed in doHeadings. They shouldn't
4172 # be trimmed here since whitespace in HTML headings is significant.
4175 '/<H(?P<level>[1-6])(?P<attrib>.*?>)(?P<header>[\s\S]*?)<\/H[1-6] *>/i',
4180 # if there are fewer than 4 headlines in the article, do not show TOC
4181 # unless it's been explicitly enabled.
4182 $enoughToc = $this->mShowToc &&
4183 ( ( $numMatches >= 4 ) || $this->mForceTocPosition );
4185 # Allow user to stipulate that a page should have a "new section"
4186 # link added via __NEWSECTIONLINK__
4187 if (
isset( $this->mDoubleUnderscores[
'newsectionlink'] ) ) {
4188 $this->mOutput->setNewSection(
true );
4191 # Allow user to remove the "new section"
4192 # link via __NONEWSECTIONLINK__
4193 if (
isset( $this->mDoubleUnderscores[
'nonewsectionlink'] ) ) {
4194 $this->mOutput->hideNewSection(
true );
4197 # if the string __FORCETOC__ (not case-sensitive) occurs in the HTML,
4198 # override above conditions and always show TOC above first header
4199 if (
isset( $this->mDoubleUnderscores[
'forcetoc'] ) ) {
4200 $this->mShowToc =
true;
4208 # Ugh .. the TOC should have neat indentation levels which can be
4209 # passed to the skin functions. These are determined here
4213 $sublevelCount = [];
4220 $baseTitleText = $this->mTitle->getPrefixedDBkey();
4221 $oldType = $this->mOutputType;
4222 $this->setOutputType( self::OT_WIKI );
4223 $frame = $this->getPreprocessor()->newFrame();
4224 $root = $this->preprocessToDom( $origText );
4225 $node = $root->getFirstChild();
4230 $headlines = $numMatches !==
false ?
$matches[3] : [];
4232 $maxTocLevel = $this->siteConfig->get(
'MaxTocLevel' );
4233 foreach ( $headlines as $headline ) {
4234 $isTemplate =
false;
4236 $sectionIndex =
false;
4238 $markerMatches = [];
4239 if (
preg_match(
"/^$markerRegex/", $headline, $markerMatches ) ) {
4240 $serial = $markerMatches[1];
4241 list( $titleText, $sectionIndex ) = $this->mHeadings[
$serial];
4243 $headline =
preg_replace(
"/^$markerRegex\\s*/",
"", $headline );
4247 $prevlevel = $level;
4251 if ( $level > $prevlevel ) {
4252 # Increase TOC level
4255 if ( $toclevel < $maxTocLevel ) {
4260 }
elseif ( $level < $prevlevel && $toclevel > 1 ) {
4261 # Decrease TOC level, find level to jump to
4263 for ( $i = $toclevel; $i > 0; $i-- ) {
4264 if ( $levelCount[$i] == $level ) {
4265 # Found last matching level
4268 }
elseif ( $levelCount[$i] < $level ) {
4269 # Found first matching level below current level
4277 if ( $toclevel < $maxTocLevel ) {
4278 if ( $prevtoclevel < $maxTocLevel ) {
4279 # Unindent only if the previous toc level was shown :p
4287 # No change in level, end TOC line
4288 if ( $toclevel < $maxTocLevel ) {
4295 # count number of headlines for each level
4299 if ( !empty( $sublevelCount[$i] ) ) {
4303 $numbering .= $this->getTargetLanguage()->formatNum( $sublevelCount[$i] );
4308 # The safe header is a version of the header text safe to use for links
4310 # Remove link placeholders by the link text.
4311 # <!--LINK number-->
4313 # link text with suffix
4314 # Do this before unstrip since link text can contain strip markers
4315 $safeHeadline = $this->replaceLinkHoldersText( $headline );
4317 # Avoid insertion of weird stuff like <math> by expanding the relevant sections
4318 $safeHeadline = $this->mStripState->unstripBoth( $safeHeadline );
4320 # Remove any <style> or <script> tags (T198618)
4322 '#<(style|script)(?: [^>]*[^>/])?>.*?</\1>#is',
4327 # Strip out HTML (first regex removes any tag not allowed)
4329 # * <sup> and <sub> (T10393)
4333 # * <span dir="rtl"> and <span dir="ltr"> (T37167)
4334 # * <s> and <strike> (T35715)
4335 # We strip any parameter from accepted tags (second regex), except dir="rtl|ltr" from <span>,
4336 # to allow setting directionality in toc items.
4339 '#<(?!/?(span|sup|sub|bdi|i|b|s|strike)(?: [^>]*)?>).*?>#',
4340 '#<(/?(?:span(?: dir="(?:rtl|ltr)")?|sup|sub|bdi|i|b|s|strike))(?: .*?)?>#'
4346 # Strip '<span></span>', which is the result from the above if
4347 # <span id="foo"></span> is used to produce an additional anchor
4349 $tocline =
str_replace(
'<span></span>',
'', $tocline );
4351 $tocline =
trim( $tocline );
4353 # For the anchor, strip out HTML-y stuff period
4354 $safeHeadline =
preg_replace(
'/<.*?>/',
'', $safeHeadline );
4355 $safeHeadline = Sanitizer::normalizeSectionNameWhitespace( $safeHeadline );
4357 # Save headline for section edit hint before it's escaped
4360 # Decode HTML entities
4361 $safeHeadline = Sanitizer::decodeCharReferences( $safeHeadline );
4363 $safeHeadline = self::normalizeSectionName( $safeHeadline );
4365 $fallbackHeadline = Sanitizer::escapeIdForAttribute( $safeHeadline, Sanitizer::ID_FALLBACK );
4366 $linkAnchor = Sanitizer::escapeIdForLink( $safeHeadline );
4367 $safeHeadline = Sanitizer::escapeIdForAttribute( $safeHeadline, Sanitizer::ID_PRIMARY );
4368 if ( $fallbackHeadline === $safeHeadline ) {
4369 # No reason to have both (in fact, we can't)
4370 $fallbackHeadline =
false;
4373 # HTML IDs must be case-insensitively unique for IE compatibility (T12721).
4374 # @todo FIXME: We may be changing them depending on the current locale.
4376 if ( $fallbackHeadline ===
false ) {
4377 $fallbackArrayKey =
false;
4379 $fallbackArrayKey =
strtolower( $fallbackHeadline );
4382 # Create the anchor for linking from the TOC to the section
4385 if (
isset( $refers[$arrayKey] ) ) {
4387 for ( $i = 2;
isset( $refers[
"${arrayKey}_$i"] ); ++
$i );
4389 $linkAnchor .=
"_$i";
4390 $refers[
"${arrayKey}_$i"] =
true;
4394 if ( $fallbackHeadline !==
false &&
isset( $refers[$fallbackArrayKey] ) ) {
4396 for ( $i = 2;
isset( $refers[
"${fallbackArrayKey}_$i"] ); ++
$i );
4397 $fallbackAnchor .=
"_$i";
4398 $refers[
"${fallbackArrayKey}_$i"] =
true;
4403 # Don't number the heading if it is the only one (looks silly)
4404 if ( count(
$matches[3] ) > 1 && $this->mOptions->getNumberHeadings() ) {
4405 # the two are different if the line contains a link
4406 $headline = Html::element(
4408 [
'class' =>
'mw-headline-number' ],
4413 if ( $enoughToc && ( !
isset( $maxTocLevel ) || $toclevel < $maxTocLevel ) ) {
4415 $numbering, $toclevel, ( $isTemplate ?
false :
$sectionIndex ) );
4418 # Add the section to the section tree
4419 # Find the DOM node for this header
4420 $noOffset = ( $isTemplate || $sectionIndex ===
false );
4421 while ( $node && !$noOffset ) {
4422 if ( $node->getName() ===
'h' ) {
4423 $bits = $node->splitHeading();
4424 if ( $bits[
'i'] == $sectionIndex ) {
4428 $byteOffset +=
mb_strlen( $this->mStripState->unstripBoth(
4429 $frame->expand( $node, PPFrame::RECOVER_ORIG ) ) );
4430 $node = $node->getNextSibling();
4437 'index' => ( $isTemplate ?
'T-' :
'' ) . $sectionIndex,
4438 'fromtitle' => $titleText,
4439 'byteoffset' => ( $noOffset ?
null :
$byteOffset ),
4440 'anchor' => $anchor,
4443 # give headline the correct <h#> tag
4444 if ( $maybeShowEditLink && $sectionIndex !==
false ) {
4446 if ( $isTemplate ) {
4447 # Put a T flag in the section identifier, to indicate to extractSections()
4448 # that sections inside <includeonly> should be counted.
4449 $editsectionPage = $titleText;
4450 $editsectionSection =
"T-$sectionIndex";
4451 $editsectionContent =
null;
4453 $editsectionPage = $this->mTitle->getPrefixedText();
4469 $editlink =
'<mw:editsection page="' .
htmlspecialchars( $editsectionPage );
4470 $editlink .=
'" section="' .
htmlspecialchars( $editsectionSection ) .
'"';
4471 if ( $editsectionContent !==
null ) {
4472 $editlink .=
'>' . $editsectionContent .
'</mw:editsection>';
4480 $matches[
'attrib'][$headlineCount], $anchor, $headline,
4481 $editlink, $fallbackAnchor );
4486 $this->setOutputType( $oldType );
4488 # Never ever show TOC if no headers
4489 if ( $numVisible < 1 ) {
4494 if ( $prevtoclevel > 0 && $prevtoclevel < $maxTocLevel ) {
4498 $this->mOutput->setTOCHTML( $toc );
4499 $toc = self::TOC_START . $toc . self::TOC_END;
4503 $this->mOutput->setSections( $tocraw );
4506 # split up and insert constructed headlines
4507 $blocks =
preg_split(
'/<H[1-6].*?>[\s\S]*?<\/H[1-6]>/i', $text );
4512 foreach ( $blocks as $block ) {
4514 if ( empty( $head[$i - 1] ) ) {
4515 $sections[
$i] = $block;
4517 $sections[
$i] = $head[$i - 1] . $block;
4530 Hooks::run(
'ParserSectionCreate', [ $this, $i, &$sections[$i], $maybeShowEditLink ] );
4535 if ( $enoughToc && $isMain && !$this->mForceTocPosition ) {
4538 $sections[0] .= $toc .
"\n";
4541 $full .= implode(
'', $sections );
4543 if ( $this->mForceTocPosition ) {
4544 return str_replace(
'<!--MWTOC\'"-->', $toc, $full );
4564 if ( $clearState ) {
4565 $magicScopeVariable = $this->lock();
4567 $this->startParse( $title,
$options, self::OT_WIKI, $clearState );
4568 $this->setUser( $user );
4576 $text = TextContent::normalizeLineEndings( $text );
4578 if (
$options->getPreSaveTransform() ) {
4579 $text = $this->pstPass2( $text, $user );
4581 $text = $this->mStripState->unstripBoth( $text );
4583 $this->setUser(
null ); # Reset
4597 # Note: This is the timestamp saved as hardcoded wikitext to the database, we use
4598 # $this->contLang here in order to give everyone the same signature and use the default one
4599 # rather than the one selected in each user's preferences. (see also T14815)
4600 $ts = $this->mOptions->getTimestamp();
4601 $timestamp = MWTimestamp::getLocalInstance( $ts );
4602 $ts = $timestamp->format(
'YmdHis' );
4603 $tzMsg = $timestamp->getTimezoneMessage()->inContentLanguage()->text();
4605 $d = $this->contLang->timeanddate( $ts,
false,
false ) .
" ($tzMsg)";
4607 # Variable replacement
4608 # Because mOutputType is OT_WIKI, this will only process {{subst:xxx}} type tags
4609 $text = $this->replaceVariables( $text );
4611 # This works almost by chance, as the replaceVariables are done before the getUserSig(),
4612 # which may corrupt this parser instance via its wfMessage()->text() call-
4615 if (
strpos( $text,
'~~~' ) !==
false ) {
4616 $sigText = $this->getUserSig( $user );
4617 $text =
strtr( $text, [
4619 '~~~~' =>
"$sigText $d",
4622 # The main two signature forms used above are time-sensitive
4623 $this->mOutput->setFlag(
'user-signature' );
4626 # Context links ("pipe tricks"): [[|name]] and [[name (context)|]]
4627 $tc =
'[' . Title::legalChars() .
']';
4628 $nc =
'[ _0-9A-Za-z\x80-\xff-]'; # Namespaces can use non-ascii!
4631 $p1 =
"/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\))\\|]]/";
4633 $p4 =
"/\[\[(:?$nc+:|:|)($tc+?)( ?($tc+))\\|]]/";
4635 $p3 =
"/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\)|)((?:, |,)$tc+|)\\|]]/";
4637 $p2 =
"/\[\[\\|($tc+)]]/";
4639 # try $p1 first, to turn "[[A, B (C)|]]" into "[[A, B (C)|A, B]]"
4640 $text =
preg_replace( $p1,
'[[\\1\\2\\3|\\2]]', $text );
4641 $text =
preg_replace( $p4,
'[[\\1\\2\\3|\\2]]', $text );
4642 $text =
preg_replace( $p3,
'[[\\1\\2\\3\\4|\\2]]', $text );
4644 $t = $this->mTitle->getText();
4646 if (
preg_match(
"/^($nc+:|)$tc+?( \\($tc+\\))$/",
$t, $m ) ) {
4647 $text =
preg_replace( $p2,
"[[$m[1]\\1$m[2]|\\1]]", $text );
4648 }
elseif (
preg_match(
"/^($nc+:|)$tc+?(, $tc+|)$/",
$t, $m ) &&
"$m[1]$m[2]" !=
'' ) {
4649 $text =
preg_replace( $p2,
"[[$m[1]\\1$m[2]|\\1]]", $text );
4651 # if there's no context, don't bother duplicating the title
4672 public function getUserSig( &$user, $nickname =
false, $fancySig =
null ) {
4675 # If not given, retrieve from the user object.
4676 if ( $nickname ===
false ) {
4677 $nickname = $user->getOption(
'nickname' );
4681 $fancySig = $user->getBoolOption(
'fancysig' );
4686 if (
mb_strlen( $nickname ) > $this->siteConfig->get(
'MaxSigChars' ) ) {
4688 wfDebug( __METHOD__ .
": $username has overlong signature.\n" );
4689 }
elseif ( $fancySig !==
false ) {
4690 # Sig. might contain markup; validate this
4691 if ( $this->validateSig( $nickname ) !==
false ) {
4692 # Validated; clean up (if needed) and return it
4693 return $this->cleanSig( $nickname,
true );
4695 # Failed to validate; fall back to the default
4697 wfDebug( __METHOD__ .
": $username has bad XML tags in signature.\n" );
4701 # Make sure nickname doesnt get a sig in a sig
4702 $nickname = self::cleanSigInSig( $nickname );
4704 # If we're still here, make it a link to the user page
4707 $msgName = $user->isAnon() ?
'signature-anon' :
'signature';
4709 return wfMessage( $msgName, $userText, $nickText )->inContentLanguage()
4710 ->title( $this->getTitle() )->text();
4720 return Xml::isWellFormedXmlFragment( $text ) ? $text :
false;
4736 $magicScopeVariable = $this->lock();
4740 # Option to disable this feature
4741 if ( !$this->mOptions->getCleanSignatures() ) {
4745 # @todo FIXME: Regex doesn't respect extension tags or nowiki
4746 # => Move this logic to braceSubstitution()
4747 $substWord = $this->magicWordFactory->get(
'subst' );
4748 $substRegex =
'/\{\{(?!(?:' . $substWord->getBaseRegex() .
'))/x' . $substWord->getRegexCase();
4749 $substText =
'{{' . $substWord->getSynonym( 0 );
4751 $text =
preg_replace( $substRegex, $substText, $text );
4752 $text = self::cleanSigInSig( $text );
4753 $dom = $this->preprocessToDom( $text );
4754 $frame = $this->getPreprocessor()->newFrame();
4755 $text = $frame->expand( $dom );
4758 $text = $this->mStripState->unstripBoth( $text );
4785 $outputType, $clearState =
true
4787 $this->startParse( $title,
$options, $outputType, $clearState );
4797 $outputType, $clearState =
true
4799 $this->setTitle( $title );
4801 $this->setOutputType( $outputType );
4802 if ( $clearState ) {
4803 $this->clearState();
4816 static $executing =
false;
4818 # Guard against infinite recursion
4829 $text = $this->preprocess( $text, $title,
$options );
4859 public function setHook( $tag, callable $callback ) {
4861 if (
preg_match(
'/[<>\r\n]/', $tag, $m ) ) {
4862 throw new MWException(
"Invalid character {$m[0]} in setHook('$tag', ...) call" );
4864 $oldVal = $this->mTagHooks[$tag] ??
null;
4865 $this->mTagHooks[$tag] = $callback;
4866 if ( !
in_array( $tag, $this->mStripList ) ) {
4867 $this->mStripList[] = $tag;
4892 if (
preg_match(
'/[<>\r\n]/', $tag, $m ) ) {
4893 throw new MWException(
"Invalid character {$m[0]} in setTransparentHook('$tag', ...) call" );
4895 $oldVal = $this->mTransparentTagHooks[$tag] ??
null;
4896 $this->mTransparentTagHooks[$tag] = $callback;
4905 $this->mTagHooks = [];
4906 $this->mFunctionTagHooks = [];
4907 $this->mStripList = $this->mDefaultStripList;
4954 $oldVal =
isset( $this->mFunctionHooks[$id] ) ? $this->mFunctionHooks[$id][0] :
null;
4955 $this->mFunctionHooks[$id] = [ $callback, $flags ];
4957 # Add to function cache
4958 $mw = $this->magicWordFactory->get( $id );
4960 throw new MWException( __METHOD__ .
'() expecting a magic word identifier.' );
4963 $synonyms = $mw->getSynonyms();
4964 $sensitive =
intval( $mw->isCaseSensitive() );
4966 foreach ( $synonyms as $syn ) {
4968 if ( !$sensitive ) {
4969 $syn = $this->contLang->lc( $syn );
4972 if ( !( $flags & self::SFH_NO_HASH ) ) {
4975 # Remove trailing colon
4976 if (
substr( $syn, -1, 1 ) ===
':' ) {
4977 $syn =
substr( $syn, 0, -1 );
4990 $this->firstCallInit();
5006 if (
preg_match(
'/[<>\r\n]/', $tag, $m ) ) {
5007 throw new MWException(
"Invalid character {$m[0]} in setFunctionTagHook('$tag', ...) call" );
5009 $old = $this->mFunctionTagHooks[$tag] ??
null;
5010 $this->mFunctionTagHooks[$tag] = [ $callback, $flags ];
5012 if ( !
in_array( $tag, $this->mStripList ) ) {
5013 $this->mStripList[] = $tag;
5027 $this->mLinkHolders->replace( $text );
5038 return $this->mLinkHolders->replaceText( $text );
5062 }
catch ( Exception
$e ) {
5067 $ig->setContextTitle( $this->mTitle );
5068 $ig->setShowBytes(
false );
5069 $ig->setShowDimensions(
false );
5070 $ig->setShowFilename(
false );
5071 $ig->setParser( $this );
5072 $ig->setHideBadImages();
5073 $ig->setAttributes( Sanitizer::validateTagAttributes(
$params,
'ul' ) );
5076 $ig->setShowFilename(
true );
5078 $ig->setShowFilename(
false );
5084 $caption = $this->recursiveTagParse(
$params[
'caption'] );
5085 $ig->setCaptionHtml( $caption );
5088 $ig->setPerRow(
$params[
'perrow'] );
5091 $ig->setWidths(
$params[
'widths'] );
5094 $ig->setHeights(
$params[
'heights'] );
5096 $ig->setAdditionalOptions(
$params );
5100 Hooks::run(
'BeforeParserrenderImageGallery', [ &
$parser, &$ig ] );
5102 $lines = StringUtils::explode(
"\n", $text );
5104 # match lines like these:
5105 # Image:someimage.jpg|This is some image
5118 # Bogus title. Ignore these so we don't bomb out later.
5122 # We need to get what handler the file uses, to figure out parameters.
5123 # Note, a hook can overide the file name, and chose an entirely different
5124 # file (which potentially could be of a different type and have different handler).
5127 Hooks::run(
'BeforeParserFetchFileAndTitle',
5128 [ $this, $title, &
$options, &$descQuery ] );
5129 # Don't register it now, as TraditionalImageGallery does that later.
5130 $file = $this->fetchFileNoRegister( $title,
$options );
5131 $handler = $file ? $file->getHandler() :
false;
5134 'img_alt' =>
'gallery-internal-alt',
5135 'img_link' =>
'gallery-internal-link',
5138 $paramMap +=
$handler->getParamMap();
5141 unset( $paramMap[
'img_width'] );
5144 $mwArray = $this->magicWordFactory->newArray(
array_keys( $paramMap ) );
5149 $handlerOptions = [];
5159 $parameterMatches = StringUtils::delimiterExplode(
5163 foreach ( $parameterMatches as $parameterMatch ) {
5164 list( $magicName, $match ) = $mwArray->matchVariableStartToEnd( $parameterMatch );
5168 switch ( $paramName ) {
5169 case 'gallery-internal-alt':
5170 $alt = $this->stripAltText( $match,
false );
5172 case 'gallery-internal-link':
5173 $linkValue = $this->stripAltText( $match,
false );
5174 if (
preg_match(
'/^-{R|(.*)}-$/', $linkValue ) ) {
5177 $linkValue =
substr( $linkValue, 4, -2 );
5179 list(
$type, $target ) = $this->parseLinkParameter( $linkValue );
5180 if (
$type ===
'link-url' ) {
5182 $this->mOutput->addExternalLink( $target );
5184 $link = $target->getLinkURL();
5185 $this->mOutput->addLink( $target );
5190 if (
$handler->validateParam( $paramName, $match ) ) {
5194 wfDebug(
"$parameterMatch failed parameter validation\n" );
5206 $ig->add( $title, $label, $alt,
$link, $handlerOptions );
5208 $html = $ig->toHTML();
5209 Hooks::run(
'AfterParserFetchFileAndTitle', [ $this, $ig, &
$html ] );
5223 if ( !
isset( $this->mImageParams[$handlerClass] ) ) {
5224 # Initialise static lists
5225 static $internalParamNames = [
5226 'horizAlign' => [
'left',
'right',
'center',
'none' ],
5227 'vertAlign' => [
'baseline',
'sub',
'super',
'top',
'text-top',
'middle',
5228 'bottom',
'text-bottom' ],
5229 'frame' => [
'thumbnail',
'manualthumb',
'framed',
'frameless',
5230 'upright',
'border',
'link',
'alt',
'class' ],
5233 if ( !$internalParamMap ) {
5234 $internalParamMap = [];
5235 foreach ( $internalParamNames as
$type => $names ) {
5236 foreach ( $names as $name ) {
5242 $magicName =
str_replace(
'-',
'_',
"img_$name" );
5248 # Add handler params
5251 $handlerParamMap =
$handler->getParamMap();
5252 foreach ( $handlerParamMap as $magic => $paramName ) {
5253 $paramMap[$magic] = [
'handler',
$paramName ];
5258 $this->magicWordFactory->newArray(
array_keys( $paramMap ) );
5272 # Check if the options text is of the form "options|alt text"
5274 # * thumbnail make a thumbnail with enlarge-icon and caption, alignment depends on lang
5275 # * left no resizing, just left align. label is used for alt= only
5276 # * right same, but right aligned
5277 # * none same, but not aligned
5278 # * ___px scale to ___ pixels width, no aligning. e.g. use in taxobox
5279 # * center center the image
5280 # * frame Keep original image size, no magnify-button.
5281 # * framed Same as "frame"
5282 # * frameless like 'thumb' but without a frame. Keeps user preferences for width
5283 # * upright reduce width for upright images, rounded to full __0 px
5284 # * border draw a 1px border around the image
5285 # * alt Text for HTML alt attribute (defaults to empty)
5286 # * class Set a class for img node
5287 # * link Set the target of the image link. Can be external, interwiki, or local
5288 # vertical-align values (no % or length right now):
5298 # Protect LanguageConverter markup when splitting into parts
5299 $parts = StringUtils::delimiterExplode(
5303 # Give extensions a chance to select the file revision for us
5306 Hooks::run(
'BeforeParserFetchFileAndTitle',
5307 [ $this, $title, &
$options, &$descQuery ] );
5308 # Fetch and register the file (file title may be different via hooks)
5309 list( $file, $title ) = $this->fetchFileAndTitle( $title,
$options );
5312 $handler = $file ? $file->getHandler() :
false;
5314 list( $paramMap, $mwArray ) = $this->getImageParams(
$handler );
5317 $this->addTrackingCategory(
'broken-file-category' );
5320 # Process the input parameters
5322 $params = [
'frame' => [],
'handler' => [],
5323 'horizAlign' => [],
'vertAlign' => [] ];
5324 $seenformat =
false;
5325 foreach ( $parts as $part ) {
5326 $part =
trim( $part );
5327 list( $magicName,
$value ) = $mwArray->matchVariableStartToEnd( $part );
5329 if (
isset( $paramMap[$magicName] ) ) {
5332 # Special case; width and height come in one variable together
5333 if (
$type ===
'handler' && $paramName ===
'width' ) {
5334 $parsedWidthParam = self::parseWidthParam(
$value );
5335 if (
isset( $parsedWidthParam[
'width'] ) ) {
5336 $width = $parsedWidthParam[
'width'];
5337 if (
$handler->validateParam(
'width', $width ) ) {
5342 if (
isset( $parsedWidthParam[
'height'] ) ) {
5343 $height = $parsedWidthParam[
'height'];
5344 if (
$handler->validateParam(
'height', $height ) ) {
5349 # else no validation -- T15436
5351 if (
$type ===
'handler' ) {
5352 # Validate handler parameter
5355 # Validate internal parameters
5356 switch ( $paramName ) {
5360 # @todo FIXME: Possibly check validity here for
5361 # manualthumb? downstream behavior seems odd with
5362 # missing manual thumbs.
5368 $this->parseLinkParameter(
5369 $this->stripAltText(
$value, $holders )
5373 if ( $paramName ===
'no-link' ) {
5376 if ( ( $paramName ===
'link-url' ) && $this->mOptions->getExternalLinkTarget() ) {
5377 $params[
$type][
'link-target'] = $this->mOptions->getExternalLinkTarget();
5389 # Most other things appear to be empty or numeric...
5399 if ( !$validated ) {
5404 # Process alignment parameters
5405 if (
$params[
'horizAlign'] ) {
5414 # Will the image be presented in a frame, with the caption below?
5420 # In the old days, [[Image:Foo|text...]] would set alt text. Later it
5421 # came to also set the caption, ordinary text after the image -- which
5422 # makes no sense, because that just repeats the text multiple times in
5423 # screen readers. It *also* came to set the title attribute.
5424 # Now that we have an alt attribute, we should not set the alt text to
5425 # equal the caption: that's worse than useless, it just repeats the
5426 # text. This is the framed/thumbnail case. If there's no caption, we
5427 # use the unnamed parameter for alt text as well, just for the time be-
5428 # ing, if the unnamed param is set and the alt param is not.
5429 # For the future, we need to figure out if we want to tweak this more,
5430 # e.g., introducing a title= parameter for the title; ignoring the un-
5431 # named parameter entirely for images without a caption; adding an ex-
5432 # plicit caption= parameter and preserving the old magic unnamed para-
5434 if ( $imageIsFramed ) { # Framed image
5435 if ( $caption ===
'' && !
isset(
$params[
'frame'][
'alt'] ) ) {
5436 # No caption or alt text, add the filename as the alt text so
5437 # that screen readers at least get some description of the image
5438 $params[
'frame'][
'alt'] = $title->getText();
5440 # Do not set $params['frame']['title'] because tooltips don't make sense
5442 }
else { # Inline image
5444 # No alt text, use the "caption" for the alt text
5445 if ( $caption !==
'' ) {
5446 $params[
'frame'][
'alt'] = $this->stripAltText( $caption, $holders );
5448 # No caption, fall back to using the filename for the
5450 $params[
'frame'][
'alt'] = $title->getText();
5453 # Use the "caption" for the tooltip text
5454 $params[
'frame'][
'title'] = $this->stripAltText( $caption, $holders );
5456 $params[
'handler'][
'targetlang'] = $this->getTargetLanguage()->getCode();
5458 Hooks::run(
'ParserMakeImageParams', [ $title, $file, &
$params, $this ] );
5460 # Linker does the rest
5463 $time, $descQuery, $this->mOptions->getThumbSize() );
5465 # Give the handler a chance to modify the parser object
5467 $handler->parserTransformHook( $this, $file );
5492 $chars = self::EXT_LINK_URL_CLASS;
5493 $addr = self::EXT_LINK_ADDR;
5494 $prots = $this->mUrlProtocols;
5501 $this->mOutput->addExternalLink(
$value );
5506 $linkTitle = Title::newFromText(
$value );
5508 $this->mOutput->addLink( $linkTitle );
5509 $type =
'link-title';
5513 return [
$type, $target ];
5522 # Strip bad stuff out of the title (tooltip). We can't just use
5523 # replaceLinkHoldersText() here, because if this function is called
5524 # from replaceInternalLinks2(), mLinkHolders won't be up-to-date.
5526 $tooltip = $holders->replaceText( $caption );
5528 $tooltip = $this->replaceLinkHoldersText( $caption );
5531 # make sure there are no placeholders in thumbnail attributes
5532 # that are later expanded to html- so expand them now and
5534 $tooltip = $this->mStripState->unstripBoth( $tooltip );
5535 # Compatibility hack! In HTML certain entity references not terminated
5536 # by a semicolon are decoded (but not if we're in an attribute; that's
5537 # how link URLs get away without properly escaping & in queries).
5538 # But wikitext has always required semicolon-termination of entities,
5539 # so encode & where needed to avoid decode of semicolon-less entities.
5542 # T210437 discusses moving this workaround to Sanitizer::stripAllTags.
5544 & # 1. entity prefix
5545 (?= # 2. followed by:
5546 (?: # a. one of the legacy semicolon-less named entities
5547 A(?:Elig|MP|acute|circ|grave|ring|tilde|uml)|
5548 C(?:OPY|cedil)|E(?:TH|acute|circ|grave|uml)|
5549 GT|I(?:acute|circ|grave|uml)|LT|Ntilde|
5550 O(?:acute|circ|grave|slash|tilde|uml)|QUOT|REG|THORN|
5551 U(?:acute|circ|grave|uml)|Yacute|
5552 a(?:acute|c(?:irc|ute)|elig|grave|mp|ring|tilde|uml)|brvbar|
5553 c(?:cedil|edil|urren)|cent(?!erdot;)|copy(?!sr;)|deg|
5554 divide(?!ontimes;)|e(?:acute|circ|grave|th|uml)|
5555 frac(?:1(?:2|4)|34)|
5556 gt(?!c(?:c|ir)|dot|lPar|quest|r(?:a(?:pprox|rr)|dot|eq(?:less|qless)|less|sim);)|
5557 i(?:acute|circ|excl|grave|quest|uml)|laquo|
5558 lt(?!c(?:c|ir)|dot|hree|imes|larr|quest|r(?:Par|i(?:e|f|));)|
5559 m(?:acr|i(?:cro|ddot))|n(?:bsp|tilde)|
5560 not(?!in(?:E|dot|v(?:a|b|c)|)|ni(?:v(?:a|b|c)|);)|
5561 o(?:acute|circ|grave|rd(?:f|m)|slash|tilde|uml)|
5562 p(?:lusmn|ound)|para(?!llel;)|quot|r(?:aquo|eg)|
5563 s(?:ect|hy|up(?:1|2|3)|zlig)|thorn|times(?!b(?:ar|)|d;)|
5564 u(?:acute|circ|grave|ml|uml)|y(?:acute|en|uml)
5566 (?:[^;]|$)) # b. and not followed by a semicolon
5567 # S = study, for efficiency
5568 /Sx",
'&', $tooltip );
5569 $tooltip = Sanitizer::stripAllTags( $tooltip );
5580 wfDebug(
"Parser output marked as uncacheable.\n" );
5581 if ( !$this->mOutput ) {
5583 " can only be called when actually parsing something" );
5585 $this->mOutput->updateCacheExpiry( 0 );
5597 $text = $this->replaceVariables( $text, $frame );
5598 $text = $this->mStripState->unstripBoth( $text );
5608 $this->firstCallInit();
5621 $this->firstCallInit();
5622 return $this->mFunctionSynonyms;
5630 return $this->mUrlProtocols;
5645 $elements =
array_keys( $this->mTransparentTagHooks );
5646 $text = self::extractTagsAndParams( $elements, $text,
$matches );
5652 if (
isset( $this->mTransparentTagHooks[$tagName] ) ) {
5654 $this->mTransparentTagHooks[$tagName],
5662 return strtr( $text, $replacements );
5695 global
$wgTitle; # not generally used but removes an ugly failure mode
5697 $magicScopeVariable = $this->lock();
5700 $frame = $this->getPreprocessor()->newFrame();
5702 # Process section extraction flags
5704 $sectionParts = explode(
'-', $sectionId );
5705 $sectionIndex =
array_pop( $sectionParts );
5706 foreach ( $sectionParts as $part ) {
5707 if ( $part ===
'T' ) {
5708 $flags |= self::PTD_FOR_INCLUSION;
5712 # Check for empty input
5713 if (
strval( $text ) ===
'' ) {
5714 # Only sections 0 and T-0 exist in an empty document
5715 if ( $sectionIndex == 0 ) {
5716 if ( $mode ===
'get' ) {
5722 if ( $mode ===
'get' ) {
5730 # Preprocess the text
5731 $root = $this->preprocessToDom( $text, $flags );
5733 # <h> nodes indicate section breaks
5734 # They can only occur at the top level, so we can find them by iterating the root's children
5735 $node = $root->getFirstChild();
5737 # Find the target section
5738 if ( $sectionIndex == 0 ) {
5739 # Section zero doesn't nest, level=big
5740 $targetLevel = 1000;
5743 if ( $node->getName() ===
'h' ) {
5744 $bits = $node->splitHeading();
5745 if ( $bits[
'i'] == $sectionIndex ) {
5746 $targetLevel = $bits[
'level'];
5750 if ( $mode ===
'replace' ) {
5751 $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5753 $node = $node->getNextSibling();
5759 if ( $mode ===
'get' ) {
5766 # Find the end of the section, including nested sections
5768 if ( $node->getName() ===
'h' ) {
5769 $bits = $node->splitHeading();
5770 $curLevel = $bits[
'level'];
5771 if ( $bits[
'i'] != $sectionIndex && $curLevel <= $targetLevel ) {
5775 if ( $mode ===
'get' ) {
5776 $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5778 $node = $node->getNextSibling();
5781 # Write out the remainder (in replace mode only)
5782 if ( $mode ===
'replace' ) {
5783 # Output the replacement text
5784 # Add two newlines on -- trailing whitespace in $newText is conventionally
5785 # stripped by the editor, so we need both newlines to restore the paragraph gap
5786 # Only add trailing whitespace if there is newText
5787 if ( $newText !=
"" ) {
5788 $outText .= $newText .
"\n\n";
5792 $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5793 $node = $node->getNextSibling();
5798 # Re-insert stripped tags
5799 $outText =
rtrim( $this->mStripState->unstripBoth( $outText ) );
5819 public function getSection( $text, $sectionId, $defaultText =
'' ) {
5820 return $this->extractSections( $text, $sectionId,
'get', $defaultText );
5836 return $this->extractSections( $oldText, $sectionId,
'replace', $newText );
5845 return $this->mRevisionId;
5855 if ( !
is_null( $this->mRevisionObject ) ) {
5856 return $this->mRevisionObject;
5867 $this->mOptions->getCurrentRevisionCallback(), $this->getTitle(), $this
5870 if ( $this->mRevisionId ===
null &&
$rev &&
$rev->getId() ) {
5882 if ( $this->mRevisionId &&
$rev &&
$rev->getId() != $this->mRevisionId ) {
5886 $this->mRevisionObject =
$rev;
5888 return $this->mRevisionObject;
5897 if (
is_null( $this->mRevisionTimestamp ) ) {
5898 $revObject = $this->getRevisionObject();
5899 $timestamp = $revObject ? $revObject->getTimestamp() :
wfTimestampNow();
5901 # The cryptic '' timezone parameter tells to use the site-default
5902 # timezone offset instead of the user settings.
5903 # Since this value will be saved into the parser cache, served
5904 # to other users, and potentially even used inside links and such,
5905 # it needs to be consistent for all visitors.
5906 $this->mRevisionTimestamp = $this->contLang->userAdjust( $timestamp,
'' );
5908 return $this->mRevisionTimestamp;
5917 if (
is_null( $this->mRevisionUser ) ) {
5918 $revObject = $this->getRevisionObject();
5920 # if this template is subst: the revision id will be blank,
5921 # so just use the current user's name
5923 $this->mRevisionUser = $revObject->getUserText();
5924 }
elseif ( $this->ot[
'wiki'] || $this->mOptions->getIsPreview() ) {
5925 $this->mRevisionUser = $this->getUser()->getName();
5928 return $this->mRevisionUser;
5937 if (
is_null( $this->mRevisionSize ) ) {
5938 $revObject = $this->getRevisionObject();
5940 # if this variable is subst: the revision id will be blank,
5941 # so just use the parser input size, because the own substituation
5942 # will change the size.
5944 $this->mRevisionSize = $revObject->getSize();
5946 $this->mRevisionSize = $this->mInputSize;
5949 return $this->mRevisionSize;
5958 $this->mDefaultSort =
$sort;
5959 $this->mOutput->setProperty(
'defaultsort',
$sort );
5973 if ( $this->mDefaultSort !==
false ) {
5974 return $this->mDefaultSort;
5987 return $this->mDefaultSort;
5991 $text = Sanitizer::normalizeSectionNameWhitespace( $text );
5992 $text = Sanitizer::decodeCharReferences( $text );
5993 $text = self::normalizeSectionName( $text );
5998 return '#' . Sanitizer::escapeIdForLink( $sectionName );
6002 $fragmentMode = $this->siteConfig->get(
'FragmentMode' );
6003 if (
isset( $fragmentMode[1] ) && $fragmentMode[1] ===
'legacy' ) {
6005 $id = Sanitizer::escapeIdForAttribute( $sectionName, Sanitizer::ID_FALLBACK );
6007 $id = Sanitizer::escapeIdForLink( $sectionName );
6022 # Strip out wikitext links(they break the anchor)
6023 $text = $this->stripSectionName( $text );
6024 $sectionName = self::getSectionNameFromStrippedText( $text );
6025 return self::makeAnchor( $sectionName );
6038 # Strip out wikitext links(they break the anchor)
6039 $text = $this->stripSectionName( $text );
6040 $sectionName = self::getSectionNameFromStrippedText( $text );
6041 return $this->makeLegacyAnchor( $sectionName );
6050 $sectionName = self::getSectionNameFromStrippedText( $text );
6051 return self::makeAnchor( $sectionName );
6061 # T90902: ensure the same normalization is applied for IDs as to links
6062 $titleParser = MediaWikiServices::getInstance()->getTitleParser();
6065 $parts = $titleParser->splitTitleString(
"#$text" );
6069 return $parts[
'fragment'];
6087 # Strip internal link markup
6088 $text =
preg_replace(
'/\[\[:?([^[|]+)\|([^[]+)\]\]/',
'$2', $text );
6089 $text =
preg_replace(
'/\[\[:?([^[]+)\|?\]\]/',
'$1', $text );
6091 # Strip external link markup
6092 # @todo FIXME: Not tolerant to blank link text
6094 # on how many empty links there are on the page - need to figure that out.
6095 $text =
preg_replace(
'/\[(?i:' . $this->mUrlProtocols .
')([^ ]+?) ([^[]+)\]/',
'$2', $text );
6097 # Parse wikitext quotes (italics & bold)
6098 $text = $this->doQuotes( $text );
6101 $text = StringUtils::delimiterReplace(
'<',
'>',
'', $text );
6116 $outputType = self::OT_HTML
6118 $magicScopeVariable = $this->lock();
6119 $this->startParse( $title,
$options, $outputType,
true );
6121 $text = $this->replaceVariables( $text );
6122 $text = $this->mStripState->unstripBoth( $text );
6123 $text = Sanitizer::removeHTMLtags( $text );
6134 return $this->preSaveTransform( $text, $title,
$options->getUser(),
$options );
6144 return $this->testSrvus( $text, $title,
$options, self::OT_PREPROCESS );
6167 $markerStart =
strpos(
$s, self::MARKER_PREFIX, $i );
6168 if ( $markerStart ===
false ) {
6173 $markerEnd =
strpos(
$s, self::MARKER_SUFFIX, $markerStart );
6174 if ( $markerEnd ===
false ) {
6178 $markerEnd +=
strlen( self::MARKER_SUFFIX );
6179 $out .=
substr(
$s, $markerStart, $markerEnd - $markerStart );
6194 return $this->mStripState->killMarkers( $text );
6218 'version' => self::HALF_PARSED_VERSION,
6219 'stripState' => $this->mStripState->getSubState( $text ),
6220 'linkHolders' => $this->mLinkHolders->getSubArray( $text )
6243 if ( !
isset(
$data[
'version'] ) ||
$data[
'version'] != self::HALF_PARSED_VERSION ) {
6244 throw new MWException( __METHOD__ .
': invalid version' );
6247 # First, extract the strip state.
6248 $texts = [
$data[
'text'] ];
6249 $texts = $this->mStripState->merge(
$data[
'stripState'], $texts );
6251 # Now renumber links
6252 $texts = $this->mLinkHolders->mergeForeign(
$data[
'linkHolders'], $texts );
6254 # Should be good to go.
6270 return isset(
$data[
'version'] ) &&
$data[
'version'] == self::HALF_PARSED_VERSION;
6283 $parsedWidthParam = [];
6288 # (T15500) In both cases (width/height and width only),
6289 # permit trailing "px" for backward compatibility.
6290 if ( $parseHeight &&
preg_match(
'/^([0-9]*)x([0-9]*)\s*(?:px)?\s*$/',
$value, $m ) ) {
6291 $width =
intval( $m[1] );
6292 $height =
intval( $m[2] );
6293 $parsedWidthParam[
'width'] = $width;
6294 $parsedWidthParam[
'height'] = $height;
6297 $parsedWidthParam[
'width'] = $width;
6312 if ( $this->mInParse ) {
6313 throw new MWException(
"Parser state cleared while parsing. "
6314 .
"Did you call Parser::parse recursively? Lock is held by: " . $this->mInParse );
6320 $this->mInParse =
$e->getTraceAsString();
6322 $recursiveCheck =
new ScopedCallback(
function () {
6323 $this->mInParse =
false;
6359 if ( $this->mInParse ) {
6360 return $this->factory->create();
6373 OutputPage::setupOOUI();
6374 $this->mOutput->setEnableOOUI(
true );
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for and distribution as defined by Sections through of this document Licensor shall mean the copyright owner or entity authorized by the copyright owner that is granting the License Legal Entity shall mean the union of the acting entity and all other entities that control are controlled by or are under common control with that entity For the purposes of this definition control direct or to cause the direction or management of such whether by contract or including but not limited to software source documentation and configuration files Object form shall mean any form resulting from mechanical transformation or translation of a Source including but not limited to compiled object generated and conversions to other media types Work shall mean the work of whether in Source or Object made available under the as indicated by a copyright notice that is included in or attached to the whether in Source or Object that is based or other modifications as a an original work of authorship For the purposes of this Derivative Works shall not include works that remain separable or merely the Work and Derivative Works thereof Contribution shall mean any work of including the original version of the Work and any modifications or additions to that Work or Derivative Works that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner For the purposes of this submitted means any form of or written communication sent to the Licensor or its including but not limited to communication on electronic mailing source code control and issue tracking systems that are managed or on behalf the Licensor for the purpose of discussing and improving the but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as Not a Contribution Contributor shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work Grant of Copyright License Subject to the terms and conditions of this each Contributor hereby grants to You a non no royalty irrevocable copyright license to prepare Derivative Works publicly display
This list may contain false positives That usually means there is additional text with links below the first Each row contains links to the first and second as well as the first line of the second redirect text
and that you know you can do these things To protect your we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights These restrictions translate to certain responsibilities for you if you distribute copies of the or if you modify it For if you distribute copies of such a whether gratis or for a you must give the recipients all the rights that you have You must make sure that receive or can get the source code And you must show them these terms so they know their rights We protect your rights with two and(2) offer you this license which gives you legal permission to copy
we sometimes make exceptions for this Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally NO WARRANTY BECAUSE THE PROGRAM IS LICENSED FREE OF THERE IS NO WARRANTY FOR THE TO THE EXTENT PERMITTED BY APPLICABLE LAW EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND OR OTHER PARTIES PROVIDE THE PROGRAM AS IS WITHOUT WARRANTY OF ANY EITHER EXPRESSED OR BUT NOT LIMITED THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU SHOULD THE PROGRAM PROVE YOU ASSUME THE COST OF ALL NECESSARY REPAIR OR CORRECTION IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT OR ANY OTHER PARTY WHO MAY MODIFY AND OR REDISTRIBUTE THE PROGRAM AS PERMITTED BE LIABLE TO YOU FOR INCLUDING ANY INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new and you want it to be of the greatest possible use to the public
$wgNoFollowNsExceptions
Namespaces in which $wgNoFollowLinks doesn't apply.
$wgNoFollowLinks
If true, external URL links in wiki text will be given the rel="nofollow" attribute as a hint to sear...
$wgNoFollowDomainExceptions
If this is set to an array of domains, external links to these domain names (or any subdomains) will ...
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,...
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
wfUrlProtocolsWithoutProtRel()
Like wfUrlProtocols(), but excludes '//' from the protocol list.
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfHostname()
Fetch server name for use in error reporting etc.
wfFindFile( $title, $options=[])
Find a file.
wfSetVar(&$dest, $source, $force=false)
Sets dest to source and returns the original value of dest If source is NULL, it just returns the val...
wfUrlProtocols( $includeProtocolRelative=true)
Returns a regular expression of url protocols.
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
wfMatchesDomainList( $url, $domains)
Check whether a given URL has a domain that occurs in a given set of domains.
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
wfIsHHVM()
Check if we are running under HHVM.
if(defined( 'MW_SETUP_CALLBACK')) $fname
Customization point after all loading (constants, functions, classes, DefaultSettings,...
if(! $wgRequest->checkUrlExtension()) if(isset( $_SERVER['PATH_INFO']) && $_SERVER['PATH_INFO'] !='') $wgTitle
static doBlockLevels( $text, $lineStart)
Make lists from lines starting with ':', '*', '#', etc.
static cascadingsources( $parser, $title='')
Returns the sources of any cascading protection acting on a specified page.
static register( $parser)
static register( $parser)
WebRequest clone which takes values from a provided array.
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Marks HTML that shouldn't be escaped.
static factory( $mode=false, IContextSource $context=null)
Get a new image gallery.
Internationalisation code.
static makeMediaLinkFile(Title $title, $file, $html='')
Create a direct link to a given uploaded file.
static tocLine( $anchor, $tocline, $tocnumber, $level, $sectionIndex=false)
parameter level defines if we are on an indentation level
static makeImageLink(Parser $parser, Title $title, $file, $frameParams=[], $handlerParams=[], $time=false, $query="", $widthOption=null)
Given parameters derived from [[Image:Foo|options...]], generate the HTML that that syntax inserts in...
static makeExternalImage( $url, $alt='')
Return the code for images which were added via external links, via Parser::maybeMakeExternalImage().
static normalizeSubpageLink( $contextTitle, $target, &$text)
static tocList( $toc, $lang=null)
Wraps the TOC in a table and provides the hide/collapse javascript.
static makeSelfLinkObj( $nt, $html='', $query='', $trail='', $prefix='')
Make appropriate markup for a link to the current article.
static tocIndent()
Add another level to the Table of Contents.
static splitTrail( $trail)
Split a link trail, return the "inside" portion and the remainder of the trail as a two-element array...
static makeExternalLink( $url, $text, $escape=true, $linktype='', $attribs=[], $title=null)
Make an external link.
static makeHeadline( $level, $attribs, $anchor, $html, $link, $fallbackAnchor=false)
Create a headline for content.
static tocUnindent( $level)
Finish one or more sublevels on the Table of Contents.
static tocLineEnd()
End a Table Of Contents line.
static tidy( $text)
Interface with Remex tidy.
Class for handling an array of magic words.
A factory that stores information about MagicWords, and creates them on demand with caching.
Handles a simple LRU key/value map with a maximum number of entries.
Factory for handling the special page list and generating SpecialPage objects.
Set options of the Parser.
PHP Parser - Processes wiki markup (which uses a more user-friendly syntax, such as "[[link]]" for ma...
addTrackingCategory( $msg)
getTargetLanguage()
Get the target language for the content being parsed.
getRevisionTimestamp()
Get the timestamp associated with the current revision, adjusted for the default server-local timesta...
static normalizeUrlComponent( $component, $unsafe)
bool string $mInParse
Recursive call protection.
extensionSubstitution( $params, $frame)
Return the text to be used for a given extension tag.
setDefaultSort( $sort)
Mutator for $mDefaultSort.
static stripOuterParagraph( $html)
Strip outer.
static normalizeLinkUrl( $url)
Replace unusual escape codes in a URL with their equivalent characters.
getPreloadText( $text, Title $title, ParserOptions $options, $params=[])
Process the wikitext for the "?preload=" feature.
MagicWordFactory $magicWordFactory
areSubpagesAllowed()
Return true if subpage links should be expanded on this page.
__clone()
Allow extensions to clean up when the parser is cloned.
maybeMakeExternalImage( $url)
make an image if it's allowed, either through the global option, through the exception,...
static cleanSigInSig( $text)
Strip 3, 4 or 5 tildes out of signatures.
fetchFileNoRegister( $title, $options=[])
Helper function for fetchFileAndTitle.
getMagicWordFactory()
Get the MagicWordFactory that this Parser is using.
LinkRenderer $mLinkRenderer
preprocess( $text, Title $title=null, ParserOptions $options, $revid=null, $frame=false)
Expand templates and variables in the text, producing valid, static wikitext.
renderImageGallery( $text, $params)
Renders an image gallery from a text with one line per image.
getCustomDefaultSort()
Accessor for $mDefaultSort Unlike getDefaultSort(), will return false if none is set.
static getSectionNameFromStrippedText( $text)
stripAltText( $caption, $holders)
getOptions()
Get the ParserOptions object.
cleanSig( $text, $parsing=false)
Clean up signature text.
preSaveTransform( $text, Title $title, User $user, ParserOptions $options, $clearState=true)
Transform wiki markup when saving a page by doing "\\r\\n" -> "\\n" conversion, substituting signatur...
startParse(Title $title=null, ParserOptions $options, $outputType, $clearState=true)
getRevisionUser()
Get the name of the user that edited the last revision.
static statelessFetchRevision(Title $title, $parser=false)
Wrapper around Revision::newFromTitle to allow passing additional parameters without passing them on ...
replaceExternalLinks( $text)
Replace external links (REL)
replaceVariables( $text, $frame=false, $argsOnly=false)
Replace magic variables, templates, and template arguments with the appropriate text.
const HALF_PARSED_VERSION
Update this version number when the output of serialiseHalfParsedText() changes in an incompatible wa...
makeKnownLinkHolder( $nt, $text='', $trail='', $prefix='')
Render a forced-blue link inline; protect against double expansion of URLs if we're in a mode that pr...
armorLinks( $text)
Insert a NOPARSE hacky thing into any inline links in a chunk that's going to go through further pars...
replaceInternalLinks2(&$s)
Process [[ ]] wikilinks (RIL)
setFunctionTagHook( $tag, callable $callback, $flags)
Create a tag function, e.g.
static guessSectionNameFromStrippedText( $text)
Like guessSectionNameFromWikiText(), but takes already-stripped text as input.
doMagicLinks( $text)
Replace special strings like "ISBN xxx" and "RFC xxx" with magic external links.
unserializeHalfParsedText( $data)
Load the parser state given in the $data array, which is assumed to have been generated by serializeH...
limitationWarn( $limitationType, $current='', $max='')
Warn the user when a parser limitation is reached Will warn at most once the user per limitation type...
LinkHolderArray $mLinkHolders
makeImage( $title, $options, $holders=false)
Parse image options text and use it to make an image.
disableCache()
Set a flag in the output object indicating that the content is dynamic and shouldn't be cached.
pstPass2( $text, $user)
Pre-save transform helper function.
transformMsg( $text, $options, $title=null)
Wrapper for preprocess()
getRevisionSize()
Get the size of the revision.
extractSections( $text, $sectionId, $mode, $newText='')
Break wikitext input into sections, and either pull or replace some particular section's text.
stripSectionName( $text)
Strips a text string of wikitext for use in a section anchor.
static normalizeSectionName( $text)
Apply the same normalization as code making links to this section would.
makeFreeExternalLink( $url, $numPostProto)
Make a free external link, given a user-supplied URL.
recursivePreprocess( $text, $frame=false)
Recursive parser entry point that can be called from an extension tag hook.
getRevisionTimestampSubstring( $start, $len, $mtts, $variable)
getImageParams( $handler)
serializeHalfParsedText( $text)
Save the parser state required to convert the given half-parsed text to HTML.
formatHeadings( $text, $origText, $isMain=true)
This function accomplishes several tasks: 1) Auto-number headings if that option is enabled 2) Add an...
internalParseHalfParsed( $text, $isMain=true, $linestart=true)
Helper function for parse() that transforms half-parsed HTML into fully parsed HTML.
setFunctionHook( $id, callable $callback, $flags=0)
Create a function, e.g.
getTemplateDom( $title)
Get the semi-parsed DOM representation of a template with a given title, and its redirect destination...
bool $mFirstCall
Whether firstCallInit still needs to be called.
__construct(array $parserConf=[], MagicWordFactory $magicWordFactory=null, Language $contLang=null, ParserFactory $factory=null, $urlProtocols=null, SpecialPageFactory $spFactory=null, Config $siteConfig=null, LinkRendererFactory $linkRendererFactory=null)
doTableStuff( $text)
parse the wiki syntax used to render tables
getTitle()
Accessor for the Title object.
static getExternalLinkRel( $url=false, $title=null)
Get the rel attribute for a particular external link.
testSrvus( $text, Title $title, ParserOptions $options, $outputType=self::OT_HTML)
strip/replaceVariables/unstrip for preprocessor regression testing
const VERSION
Update this version number when the ParserOutput format changes in an incompatible way,...
MagicWordArray $mSubstWords
replaceTransparentTags( $text)
Replace transparent tags in $text with the values given by the callbacks.
MapCacheLRU null $currentRevisionCache
getLinkRenderer()
Get a LinkRenderer instance to make links with.
static splitWhitespace( $s)
Return a three-element array: leading whitespace, string contents, trailing whitespace.
getExternalLinkAttribs( $url)
Get an associative array of additional HTML attributes appropriate for a particular external link.
setOutputType( $ot)
Set the output type.
makeLegacyAnchor( $sectionName)
getFunctionLang()
Get a language object for use in parser functions such as {{FORMATNUM:}}.
getVariableValue( $index, $frame=false)
Return value of a magic variable (like PAGENAME)
makeLimitReport()
Set the limit report data in the current ParserOutput, and return the limit report HTML comment.
MagicWordArray $mVariables
getStripList()
Get a list of strippable XML-like elements.
setUser( $user)
Set the current user.
markerSkipCallback( $s, $callback)
Call a callback function on all regions of the given text that are not inside strip markers,...
setHook( $tag, callable $callback)
Create an HTML-style tag, e.g.
doDoubleUnderscore( $text)
Strip double-underscore items like NOGALLERY and NOTOC Fills $this->mDoubleUnderscores,...
argSubstitution( $piece, $frame)
Triple brace replacement – used for template arguments.
replaceInternalLinks( $s)
Process [[ ]] wikilinks.
array $mLangLinkLanguages
Array with the language name of each language link (i.e.
LinkRendererFactory $linkRendererFactory
recursiveTagParse( $text, $frame=false)
Half-parse wikitext to half-parsed HTML.
string $mUniqPrefix
Deprecated accessor for the strip marker prefix.
fetchFileAndTitle( $title, $options=[])
Fetch a file and its title and register a reference to it.
getSection( $text, $sectionId, $defaultText='')
This function returns the text of a section, specified by a number ($section).
internalParse( $text, $isMain=true, $frame=false)
Helper function for parse() that transforms wiki markup into half-parsed HTML.
static extractTagsAndParams( $elements, $text, &$matches)
Replaces all occurrences of HTML-style comments and the given tags in the text with a random marker a...
replaceSection( $oldText, $sectionId, $newText)
This function returns $oldtext after the content of the section specified by $section has been replac...
interwikiTransclude( $title, $action)
Transclude an interwiki link.
setTitle( $t)
Set the context title.
incrementIncludeSize( $type, $size)
Increment an include size counter.
maybeDoSubpageLink( $target, &$text)
Handle link to subpage if necessary.
getRevisionObject()
Get the revision object for $this->mRevisionId.
doBlockLevels( $text, $linestart)
Make lists from lines starting with ':', '*', '#', etc.
fetchTemplateAndTitle( $title)
Fetch the unparsed text of a template and register a reference to it.
getDefaultSort()
Accessor for $mDefaultSort Will use the empty string if none is set.
replaceLinkHoldersText( $text)
Replace "<!--LINK-->" link placeholders with plain text of links (not HTML-formatted).
getFunctionHooks()
Get all registered function hook identifiers.
doAllQuotes( $text)
Replace single quotes with HTML markup.
getContentLanguage()
Get the content language that this Parser is using.
Preprocessor $mPreprocessor
fetchCurrentRevisionOfTitle( $title)
Fetch the current revision of a given title.
getOutput()
Get the ParserOutput object.
Options( $x=null)
Accessor/mutator for the ParserOptions object.
Title( $x=null)
Accessor/mutator for the Title object.
recursiveTagParseFully( $text, $frame=false)
Fully parse wikitext to fully parsed HTML.
guessSectionNameFromWikiText( $text)
Try to guess the section anchor name based on a wikitext fragment presumably extracted from a heading...
clearTagHooks()
Remove all tag hooks.
replaceLinkHolders(&$text, $options=0)
Replace "<!--LINK-->" link placeholders with actual links, in the buffer Placeholders created in Link...
getRevisionId()
Get the ID of the revision we are parsing.
static parseWidthParam( $value, $parseHeight=true)
Parsed a width param of imagelink like 300px or 200x300px.
validateSig( $text)
Check that the user's signature contains no bad XML.
getPreprocessor()
Get a preprocessor object.
parseLinkParameter( $value)
Parse the value of 'link' parameter in image syntax ([[File:Foo.jpg|link=<value>]]).
guessLegacySectionNameFromWikiText( $text)
Same as guessSectionNameFromWikiText(), but produces legacy anchors instead, if possible.
OutputType( $x=null)
Accessor/mutator for the output type.
__destruct()
Reduce memory usage to reduce the impact of circular references.
fetchFile( $title, $options=[])
Fetch a file and its title and register a reference to it.
getUser()
Get a User object either from $this->mUser, if set, or from the ParserOptions object otherwise.
doQuotes( $text)
Helper function for doAllQuotes()
fetchTemplate( $title)
Fetch the unparsed text of a template and register a reference to it.
doHeadings( $text)
Parse headers and return html.
insertStripItem( $text)
Add an item to the strip state Returns the unique tag which must be inserted into the stripped text T...
setTransparentTagHook( $tag, callable $callback)
As setHook(), but letting the contents be parsed.
incrementExpensiveFunctionCount()
Increment the expensive function count.
testPreprocess( $text, Title $title, ParserOptions $options)
static makeAnchor( $sectionName)
SpecialPageFactory $specialPageFactory
firstCallInit()
Do various kinds of initialisation on the first call of the parser.
preprocessToDom( $text, $flags=0)
Preprocess some wikitext and return the document tree.
clearState()
Clear Parser state.
killMarkers( $text)
Remove any strip markers found in the given text.
enableOOUI()
Set's up the PHP implementation of OOUI for use in this request and instructs OutputPage to enable OO...
getUserSig(&$user, $nickname=false, $fancySig=null)
Fetch the user's signature text, if any, and normalize to validated, ready-to-insert wikitext.
braceSubstitution( $piece, $frame)
Return the text of a template, after recursively replacing any variables or templates within the temp...
attributeStripCallback(&$text, $frame=false)
Callback from the Sanitizer for expanding items found in HTML attribute values, so they can be safely...
initialiseVariables()
initialise the magic variables (like CURRENTMONTHNAME) and substitution modifiers
isValidHalfParsedText( $data)
Returns true if the given array, presumed to be generated by serializeHalfParsedText(),...
lock()
Lock the current instance of the parser.
static createAssocArgs( $args)
Clean up argument array - refactored in 1.9 so parserfunctions can use it, too.
parse( $text, Title $title, ParserOptions $options, $linestart=true, $clearState=true, $revid=null)
Convert wikitext to HTML Do not call this function recursively.
testPst( $text, Title $title, ParserOptions $options)
startExternalParse(Title $title=null, ParserOptions $options, $outputType, $clearState=true)
Set up some variables which are usually set up in parse() so that an external function can call some ...
getFreshParser()
Return this parser if it is not doing anything, otherwise get a fresh parser.
callParserFunction( $frame, $function, array $args=[])
Call a parser function and return an array with text and flags.
SectionProfiler $mProfiler
getConverterLanguage()
Get the language object for language conversion.
static statelessFetchTemplate( $title, $parser=false)
Static function to get a template Can be overridden via ParserOptions::setTemplateCallback().
Variant of the Message class.
static singleton()
Get a RepoGroup instance.
Group all the pieces relevant to the context of a request into one instance.
static newKnownCurrent(IDatabase $db, $pageIdOrTitle, $revId=0)
Load a revision based on a known page ID and current revision ID from the DB.
static newFromTitle(LinkTarget $linkTarget, $id=0, $flags=0)
Load either the current, or a specified, revision that's attached to a given link target.
static newFromId( $id, $flags=0)
Load a page revision from a given revision ID number.
Custom PHP profiler for parser/DB type section names that xhprof/xdebug can't handle.
static numberingroup( $group)
Find the number of users in a given user group.
static getVersion( $flags='', $lang=null)
Return a string of the MediaWiki version with Git revision if available.
Represents a title within MediaWiki.
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a save
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global then executing the whole list after the page is displayed We don t do anything smart like collating updates to the same table or such because the list is almost always going to have just one item on if so it s not worth the trouble Since there is a job queue in the jobs table
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such and we might be restricted by PHP settings such as safe mode or open_basedir We cannot assume that the software even has read access anywhere useful Many shared hosts run all users web applications under the same so they can t rely on Unix and must forbid reads to even standard directories like tmp lest users read each others files We cannot assume that the user has the ability to install or run any programs not written as web accessible PHP scripts Since anything that works on cheap shared hosting will work if you have shell or root access MediaWiki s design is based around catering to the lowest common denominator Although we support higher end setups as the way many things work by default is tailored toward shared hosting These defaults are unconventional from the point of view of normal(non-web) applications -- they might conflict with distributors' policies
this hook is for auditing only $req
see documentation in includes Linker php for Linker::makeImageLink & $time
namespace being checked & $result
see documentation in includes Linker php for Linker::makeImageLink or false for current used if you return false $parser
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not it can be in the form of< username >< more info > e g for bot passwords intended to be added to log contexts Fields it might only if the login was with a bot password it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output $out
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not it can be in the form of< username >< more info > e g for bot passwords intended to be added to log contexts Fields it might only if the login was with a bot password it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable modifiable after all normalizations have been except for the $wgMaxImageArea check set to true or false to override the $wgMaxImageArea check result gives extension the possibility to transform it themselves $handler
presenting them properly to the user as errors is done by the caller return true use this to change the list i e etc next in line in page history
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
null means default in associative array form
either a unescaped string or a HtmlArmor object after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation use $formDescriptor instead default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message key
usually copyright or history_copyright This message must be in HTML not wikitext if the section is included from a template to be included in the link
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction you ll probably need to make sure the header is varied on and they can depend only on the ResourceLoaderContext $context
namespace and then decline to actually register it file or subcat img or subcat $title
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not null
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return true
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped broken
static configuration should be added through ResourceLoaderGetConfigVars instead can be used to get the real title e g db for database replication lag or jobqueue for job queue size converted to pseudo seconds It is possible to add more fields and they will be returned to the user in the API response after the basic globals have been set but before ordinary actions take place or wrap services the preferred way to define a new service is the $wgServiceWiringFiles array $services
if the prop value should be in the metadata multi language array format
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses & $ret
either a unescaped string or a HtmlArmor object after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation use $formDescriptor instead default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock() - offset Set to overwrite offset parameter in $wgRequest set to '' to unset offset - wrap String Wrap the message in html(usually something like "<div ...>$1</div>"). - flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException':Called before an exception(or PHP error) is logged. This is meant for integration with external error aggregation services
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses & $html
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Allows to change the fields on the form that will be generated $name
this hook is for auditing only or null if authentication failed before getting that far $username
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses after processing & $attribs
Status::newGood()` to allow deletion, and then `return false` from the hook function. Ensure you consume the 'ChangeTagAfterDelete' hook to carry out custom deletion actions. $tag:name of the tag $user:user initiating the action & $status:Status object. See above. 'ChangeTagsListActive':Allows you to nominate which of the tags your extension uses are in active use. & $tags:list of all active tags. Append to this array. 'ChangeTagsAfterUpdateTags':Called after tags have been updated with the ChangeTags::updateTags function. Params:$addedTags:tags effectively added in the update $removedTags:tags effectively removed in the update $prevTags:tags that were present prior to the update $rc_id:recentchanges table id $rev_id:revision table id $log_id:logging table id $params:tag params $rc:RecentChange being tagged when the tagging accompanies the action, or null $user:User who performed the tagging when the tagging is subsequent to the action, or null 'ChangeTagsAllowedAdd':Called when checking if a user can add tags to a change. & $allowedTags:List of all the tags the user is allowed to add. Any tags the user wants to add( $addTags) that are not in this array will cause it to fail. You may add or remove tags to this array as required. $addTags:List of tags user intends to add. $user:User who is adding the tags. 'ChangeUserGroups':Called before user groups are changed. $performer:The User who will perform the change $user:The User whose groups will be changed & $add:The groups that will be added & $remove:The groups that will be removed 'Collation::factory':Called if $wgCategoryCollation is an unknown collation. $collationName:Name of the collation in question & $collationObject:Null. Replace with a subclass of the Collation class that implements the collation given in $collationName. 'ConfirmEmailComplete':Called after a user 's email has been confirmed successfully. $user:user(object) whose email is being confirmed 'ContentAlterParserOutput':Modify parser output for a given content object. Called by Content::getParserOutput after parsing has finished. Can be used for changes that depend on the result of the parsing but have to be done before LinksUpdate is called(such as adding tracking categories based on the rendered HTML). $content:The Content to render $title:Title of the page, as context $parserOutput:ParserOutput to manipulate 'ContentGetParserOutput':Customize parser output for a given content object, called by AbstractContent::getParserOutput. May be used to override the normal model-specific rendering of page content. $content:The Content to render $title:Title of the page, as context $revId:The revision ID, as context $options:ParserOptions for rendering. To avoid confusing the parser cache, the output can only depend on parameters provided to this hook function, not on global state. $generateHtml:boolean, indicating whether full HTML should be generated. If false, generation of HTML may be skipped, but other information should still be present in the ParserOutput object. & $output:ParserOutput, to manipulate or replace 'ContentHandlerDefaultModelFor':Called when the default content model is determined for a given title. May be used to assign a different model for that title. $title:the Title in question & $model:the model name. Use with CONTENT_MODEL_XXX constants. 'ContentHandlerForModelID':Called when a ContentHandler is requested for a given content model name, but no entry for that model exists in $wgContentHandlers. Note:if your extension implements additional models via this hook, please use GetContentModels hook to make them known to core. $modeName:the requested content model name & $handler:set this to a ContentHandler object, if desired. 'ContentModelCanBeUsedOn':Called to determine whether that content model can be used on a given page. This is especially useful to prevent some content models to be used in some special location. $contentModel:ID of the content model in question $title:the Title in question. & $ok:Output parameter, whether it is OK to use $contentModel on $title. Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok. 'ContribsPager::getQueryInfo':Before the contributions query is about to run & $pager:Pager object for contributions & $queryInfo:The query for the contribs Pager 'ContribsPager::reallyDoQuery':Called before really executing the query for My Contributions & $data:an array of results of all contribs queries $pager:The ContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'ContributionsLineEnding':Called before a contributions HTML line is finished $page:SpecialPage object for contributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'ContributionsToolLinks':Change tool links above Special:Contributions $id:User identifier $title:User page title & $tools:Array of tool links $specialPage:SpecialPage instance for context and services. Can be either SpecialContributions or DeletedContributionsPage. Extensions should type hint against a generic SpecialPage though. 'ConvertContent':Called by AbstractContent::convert when a conversion to another content model is requested. Handler functions that modify $result should generally return false to disable further attempts at conversion. $content:The Content object to be converted. $toModel:The ID of the content model to convert to. $lossy: boolean indicating whether lossy conversion is allowed. & $result:Output parameter, in case the handler function wants to provide a converted Content object. Note that $result->getContentModel() must return $toModel. 'ContentSecurityPolicyDefaultSource':Modify the allowed CSP load sources. This affects all directives except for the script directive. If you want to add a script source, see ContentSecurityPolicyScriptSource hook. & $defaultSrc:Array of Content-Security-Policy allowed sources $policyConfig:Current configuration for the Content-Security-Policy header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'ContentSecurityPolicyDirectives':Modify the content security policy directives. Use this only if ContentSecurityPolicyDefaultSource and ContentSecurityPolicyScriptSource do not meet your needs. & $directives:Array of CSP directives $policyConfig:Current configuration for the CSP header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'ContentSecurityPolicyScriptSource':Modify the allowed CSP script sources. Note that you also have to use ContentSecurityPolicyDefaultSource if you want non-script sources to be loaded from whatever you add. & $scriptSrc:Array of CSP directives $policyConfig:Current configuration for the CSP header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'CustomEditor':When invoking the page editor Return true to allow the normal editor to be used, or false if implementing a custom editor, e.g. for a special namespace, etc. $article:Article being edited $user:User performing the edit 'DatabaseOraclePostInit':Called after initialising an Oracle database $db:the DatabaseOracle object 'DeletedContribsPager::reallyDoQuery':Called before really executing the query for Special:DeletedContributions Similar to ContribsPager::reallyDoQuery & $data:an array of results of all contribs queries $pager:The DeletedContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'DeletedContributionsLineEnding':Called before a DeletedContributions HTML line is finished. Similar to ContributionsLineEnding $page:SpecialPage object for DeletedContributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'DeleteUnknownPreferences':Called by the cleanupPreferences.php maintenance script to build a WHERE clause with which to delete preferences that are not known about. This hook is used by extensions that have dynamically-named preferences that should not be deleted in the usual cleanup process. For example, the Gadgets extension creates preferences prefixed with 'gadget-', and so anything with that prefix is excluded from the deletion. &where:An array that will be passed as the $cond parameter to IDatabase::select() to determine what will be deleted from the user_properties table. $db:The IDatabase object, useful for accessing $db->buildLike() etc. 'DifferenceEngineAfterLoadNewText':called in DifferenceEngine::loadNewText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before returning true from this function. $differenceEngine:DifferenceEngine object 'DifferenceEngineLoadTextAfterNewContentIsLoaded':called in DifferenceEngine::loadText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before checking if the variable 's value is null. This hook can be used to inject content into said class member variable. $differenceEngine:DifferenceEngine object 'DifferenceEngineMarkPatrolledLink':Allows extensions to change the "mark as
patrolled" link which is shown both on the diff header as well as on the bottom of a page, usually wrapped in a span element which has class="patrollink". $differenceEngine:DifferenceEngine object & $markAsPatrolledLink:The "mark as patrolled" link HTML(string) $rcid:Recent change ID(rc_id) for this change(int) 'DifferenceEngineMarkPatrolledRCID':Allows extensions to possibly change the rcid parameter. For example the rcid might be set to zero due to the user being the same as the performer of the change but an extension might still want to show it under certain conditions. & $rcid:rc_id(int) of the change or 0 $differenceEngine:DifferenceEngine object $change:RecentChange object $user:User object representing the current user 'DifferenceEngineNewHeader':Allows extensions to change the $newHeader variable, which contains information about the new revision, such as the revision 's author, whether the revision was marked as a minor edit or not, etc. $differenceEngine:DifferenceEngine object & $newHeader:The string containing the various #mw-diff-otitle[1-5] divs, which include things like revision author info, revision comment, RevisionDelete link and more $formattedRevisionTools:Array containing revision tools, some of which may have been injected with the DiffRevisionTools hook $nextlink:String containing the link to the next revision(if any) $status
return true to allow those checks to and false if checking is done & $user
static configuration should be added through ResourceLoaderGetConfigVars instead can be used to get the real title e g db for database replication lag or jobqueue for job queue size converted to pseudo seconds It is possible to add more fields and they will be returned to the user in the API response after the basic globals have been set but before ordinary actions take place $output
presenting them properly to the user as errors is done by the caller return true use this to change the list i e etc $rev
processing should stop and the error should be shown to the user * false
returning false will NOT prevent logging $e
!hooks source !endhooks !test Non existent language !input< source lang="doesnotexist"> foobar</source > ! result< div class="mw-highlight mw-content-ltr" dir="ltr">< pre > foobar</pre ></div > !end !test No language specified ! wikitext< source > foo</source > ! html< div class="mw-highlight mw-content-ltr" dir="ltr">< pre > foo</pre ></div > !end !test No language specified(no wellformed xml) !! config !! wikitext< source > bar</source > !! html< div class
$data
Utility to generate mapping file used in mw.Title (phpCharToUpper.json)
globals will be eliminated from MediaWiki replaced by an application object which would be passed to constructors Whether that would be an convenient solution remains to be but certainly PHP makes such object oriented programming models easier than they were in previous versions For the time being MediaWiki programmers will have to work in an environment with some global context At the time of globals were initialised on startup by MediaWiki of these were configuration which are documented in DefaultSettings php There is no comprehensive documentation for the remaining however some of the most important ones are listed below They are typically initialised either in index php or in Setup php $wgTitle Title object created from the request URL $wgOut OutputPage object for HTTP response $wgUser User object for the user associated with the current request $wgLang Language object selected by user preferences $wgContLang Language object associated with the wiki being viewed $wgParser Parser object Parser extensions register their hooks here $wgRequest WebRequest object
Interface for configuration instances.
There are three types of nodes:
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))
This document describes the state of Postgres support in and is fairly well maintained The main code is very well while extensions are very hit and miss it is probably the most supported database after MySQL Much of the work in making MediaWiki database agnostic came about through the work of creating Postgres but without copying over all the usage comments General notes on the but these can almost always be programmed around *Although Postgres has a true BOOLEAN type
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file