25use Wikimedia\ScopedCallback;
84 # Flags for Parser::setFunctionHook
88 # Constants needed for external link processing
89 # Everything except bracket, space, or control characters
90 # \p{Zs} is unicode 'separator, space' category. It covers the space 0x20
91 # as well as U+3000 is IDEOGRAPHIC SPACE for T21052
92 # \x{FFFD} is the Unicode replacement character, which Preprocessor_DOM
93 # uses to replace invalid HTML characters.
95 # Simplified expression to match an IPv4 or IPv6 address, or
96 # at least one character of a host name (embeds EXT_LINK_URL_CLASS)
97 const EXT_LINK_ADDR =
'(?:[0-9.]+|\\[(?i:[0-9a-f:.]+)\\]|[^][<>"\\x00-\\x20\\x7F\p{Zs}\x{FFFD}])';
98 # RegExp to make image URLs (embeds IPv6 part of EXT_LINK_ADDR)
100 const EXT_IMAGE_REGEX =
'/^(http:\/\/|https:\/\/)((?:\\[(?i:[0-9a-f:.]+)\\])?[^][<>"\\x00-\\x20\\x7F\p{Zs}\x{FFFD}]+)
101 \\/([A-Za-z0-9_.,~%\\-+&;#*?!=()@\\x80-\\xFF]+)\\.((?i)gif|png|jpg|jpeg)$/Sxu';
104 # Regular expression for a non-newline space
107 # Flags for preprocessToDom
110 # Allowed values for $this->mOutputType
111 # Parameter to startExternalParse().
116 const
OT_PLAIN = 4;
# like extractSections() - portions of the original are returned unchanged.
135 const MARKER_SUFFIX =
"-QINU`\"'\x7f";
138 # Markers used for wrapping the table of contents
156 # Initialised by initialiseVariables()
167 # Initialised in constructor
168 public $mConf, $mExtLinkBracketedRegex, $mUrlProtocols;
170 # Initialized in getPreprocessor()
174 # Cleared with clearState():
205 # These are variables reset at least once per parse regardless of $clearState
215 public $mTitle; #
Title context, used
for self-link rendering and similar things
218 public $mRevisionObject;
# The revision object of the specified revision ID
223 public $mRevIdForTs; # The revision ID which was used to fetch the timestamp
230 public $mUniqPrefix = self::MARKER_PREFIX;
251 public $mInParse =
false;
265 $this->mConf = $conf;
267 $this->mExtLinkBracketedRegex =
'/\[(((?i)' . $this->mUrlProtocols .
')' .
268 self::EXT_LINK_ADDR .
269 self::EXT_LINK_URL_CLASS .
'*)\p{Zs}*([^\]\\x00-\\x08\\x0a-\\x1F\\x{FFFD}]*?)\]/Su';
270 if ( isset( $conf[
'preprocessorClass'] ) ) {
271 $this->mPreprocessorClass = $conf[
'preprocessorClass'];
272 } elseif ( defined(
'HPHP_VERSION' ) ) {
273 # Preprocessor_Hash is much faster than Preprocessor_DOM under HipHop
274 $this->mPreprocessorClass =
'Preprocessor_Hash';
275 } elseif ( extension_loaded(
'domxml' ) ) {
276 # PECL extension that conflicts with the core DOM extension (T15770)
277 wfDebug(
"Warning: you have the obsolete domxml extension for PHP. Please remove it!\n" );
278 $this->mPreprocessorClass =
'Preprocessor_Hash';
279 } elseif ( extension_loaded(
'dom' ) ) {
280 $this->mPreprocessorClass =
'Preprocessor_DOM';
282 $this->mPreprocessorClass =
'Preprocessor_Hash';
284 wfDebug( __CLASS__ .
": using preprocessor: {$this->mPreprocessorClass}\n" );
291 if ( isset( $this->mLinkHolders ) ) {
292 unset( $this->mLinkHolders );
294 foreach ( $this as $name =>
$value ) {
295 unset( $this->$name );
303 $this->mInParse =
false;
311 foreach ( [
'mStripState',
'mVarCache' ] as $k ) {
319 Hooks::run(
'ParserCloned', [ $this ] );
326 if ( !$this->mFirstCall ) {
329 $this->mFirstCall =
false;
337 Hooks::run(
'ParserFirstCallInit', [ &
$parser ] );
346 if ( $this->mFirstCall ) {
350 $this->mOptions->registerWatcher( [ $this->mOutput,
'recordOption' ] );
351 $this->mAutonumber = 0;
352 $this->mIncludeCount = [];
355 $this->mRevisionObject = $this->mRevisionTimestamp =
356 $this->mRevisionId = $this->mRevisionUser = $this->mRevisionSize =
null;
357 $this->mVarCache = [];
359 $this->mLangLinkLanguages = [];
360 $this->currentRevisionCache =
null;
364 # Clear these on every parse, T6549
365 $this->mTplRedirCache = $this->mTplDomCache = [];
367 $this->mShowToc =
true;
368 $this->mForceTocPosition =
false;
369 $this->mIncludeSizes = [
373 $this->mPPNodeCount = 0;
374 $this->mGeneratedPPNodeCount = 0;
375 $this->mHighestExpansionDepth = 0;
376 $this->mDefaultSort =
false;
377 $this->mHeadings = [];
378 $this->mDoubleUnderscores = [];
379 $this->mExpensiveFunctionCount = 0;
382 if ( isset( $this->mPreprocessor ) && $this->mPreprocessor->parser !== $this ) {
383 $this->mPreprocessor =
null;
390 Hooks::run(
'ParserClearState', [ &
$parser ] );
407 $linestart =
true, $clearState =
true, $revid =
null
419 $text = strtr( $text,
"\x7f",
"?" );
420 $magicScopeVariable = $this->
lock();
423 $text = str_replace(
"\000",
'', $text );
427 $this->currentRevisionCache =
null;
428 $this->mInputSize = strlen( $text );
429 if ( $this->mOptions->getEnableLimitReport() ) {
430 $this->mOutput->resetParseStartTime();
433 $oldRevisionId = $this->mRevisionId;
434 $oldRevisionObject = $this->mRevisionObject;
435 $oldRevisionTimestamp = $this->mRevisionTimestamp;
436 $oldRevisionUser = $this->mRevisionUser;
437 $oldRevisionSize = $this->mRevisionSize;
438 if ( $revid !==
null ) {
439 $this->mRevisionId = $revid;
440 $this->mRevisionObject =
null;
441 $this->mRevisionTimestamp =
null;
442 $this->mRevisionUser =
null;
443 $this->mRevisionSize =
null;
448 Hooks::run(
'ParserBeforeStrip', [ &
$parser, &$text, &$this->mStripState ] );
450 Hooks::run(
'ParserAfterStrip', [ &
$parser, &$text, &$this->mStripState ] );
452 Hooks::run(
'ParserAfterParse', [ &
$parser, &$text, &$this->mStripState ] );
463 if ( !(
$options->getDisableTitleConversion()
464 || isset( $this->mDoubleUnderscores[
'nocontentconvert'] )
465 || isset( $this->mDoubleUnderscores[
'notitleconvert'] )
466 || $this->mOutput->getDisplayTitle() !==
false )
469 if ( $convruletitle ) {
470 $this->mOutput->setTitleText( $convruletitle );
473 $this->mOutput->setTitleText( $titleText );
477 # Done parsing! Compute runtime adaptive expiry if set
478 $this->mOutput->finalizeAdaptiveCacheExpiry();
480 # Warn if too many heavyweight parser functions were used
481 if ( $this->mExpensiveFunctionCount > $this->mOptions->getExpensiveParserFunctionLimit() ) {
483 $this->mExpensiveFunctionCount,
484 $this->mOptions->getExpensiveParserFunctionLimit()
488 # Information on include size limits, for the benefit of users who try to skirt them
489 if ( $this->mOptions->getEnableLimitReport() ) {
490 $max = $this->mOptions->getMaxIncludeSize();
492 $cpuTime = $this->mOutput->getTimeSinceStart(
'cpu' );
493 if ( $cpuTime !==
null ) {
494 $this->mOutput->setLimitReportData(
'limitreport-cputime',
495 sprintf(
"%.3f", $cpuTime )
499 $wallTime = $this->mOutput->getTimeSinceStart(
'wall' );
500 $this->mOutput->setLimitReportData(
'limitreport-walltime',
501 sprintf(
"%.3f", $wallTime )
504 $this->mOutput->setLimitReportData(
'limitreport-ppvisitednodes',
505 [ $this->mPPNodeCount, $this->mOptions->getMaxPPNodeCount() ]
507 $this->mOutput->setLimitReportData(
'limitreport-ppgeneratednodes',
508 [ $this->mGeneratedPPNodeCount, $this->mOptions->getMaxGeneratedPPNodeCount() ]
510 $this->mOutput->setLimitReportData(
'limitreport-postexpandincludesize',
511 [ $this->mIncludeSizes[
'post-expand'], $max ]
513 $this->mOutput->setLimitReportData(
'limitreport-templateargumentsize',
514 [ $this->mIncludeSizes[
'arg'], $max ]
516 $this->mOutput->setLimitReportData(
'limitreport-expansiondepth',
517 [ $this->mHighestExpansionDepth, $this->mOptions->getMaxPPExpandDepth() ]
519 $this->mOutput->setLimitReportData(
'limitreport-expensivefunctioncount',
520 [ $this->mExpensiveFunctionCount, $this->mOptions->getExpensiveParserFunctionLimit() ]
522 Hooks::run(
'ParserLimitReportPrepare', [ $this, $this->mOutput ] );
524 $limitReport =
"NewPP limit report\n";
526 $limitReport .=
'Parsed by ' .
wfHostname() .
"\n";
528 $limitReport .=
'Cached time: ' . $this->mOutput->getCacheTime() .
"\n";
529 $limitReport .=
'Cache expiry: ' . $this->mOutput->getCacheExpiry() .
"\n";
530 $limitReport .=
'Dynamic content: ' .
531 ( $this->mOutput->hasDynamicContent() ?
'true' :
'false' ) .
534 foreach ( $this->mOutput->getLimitReportData() as $key =>
$value ) {
535 if ( Hooks::run(
'ParserLimitReportFormat',
536 [ $key, &
$value, &$limitReport,
false,
false ]
538 $keyMsg =
wfMessage( $key )->inLanguage(
'en' )->useDatabase(
false );
539 $valueMsg =
wfMessage( [
"$key-value-text",
"$key-value" ] )
540 ->inLanguage(
'en' )->useDatabase(
false );
541 if ( !$valueMsg->exists() ) {
544 if ( !$keyMsg->isDisabled() && !$valueMsg->isDisabled() ) {
545 $valueMsg->params(
$value );
546 $limitReport .=
"{$keyMsg->text()}: {$valueMsg->text()}\n";
552 $limitReport = htmlspecialchars_decode( $limitReport );
554 Hooks::run(
'ParserLimitReport', [ $this, &$limitReport ],
'1.22' );
558 $limitReport = str_replace( [
'-',
'&' ], [
'‐',
'&' ], $limitReport );
559 $text .=
"\n<!-- \n$limitReport-->\n";
562 $dataByFunc = $this->mProfiler->getFunctionStats();
563 uasort( $dataByFunc,
function ( $a, $b ) {
564 return $a[
'real'] < $b[
'real'];
567 foreach ( array_slice( $dataByFunc, 0, 10 ) as $item ) {
568 $profileReport[] = sprintf(
"%6.2f%% %8.3f %6d %s",
569 $item[
'%real'], $item[
'real'], $item[
'calls'],
570 htmlspecialchars( $item[
'name'] ) );
572 $text .=
"<!--\nTransclusion expansion time report (%,ms,calls,template)\n";
573 $text .= implode(
"\n", $profileReport ) .
"\n-->\n";
575 $this->mOutput->setLimitReportData(
'limitreport-timingprofile', $profileReport );
579 $this->mOutput->setLimitReportData(
'cachereport-origin',
wfHostname() );
581 $this->mOutput->setLimitReportData(
'cachereport-timestamp',
582 $this->mOutput->getCacheTime() );
583 $this->mOutput->setLimitReportData(
'cachereport-ttl',
584 $this->mOutput->getCacheExpiry() );
585 $this->mOutput->setLimitReportData(
'cachereport-transientcontent',
586 $this->mOutput->hasDynamicContent() );
588 if ( $this->mGeneratedPPNodeCount > $this->mOptions->getMaxGeneratedPPNodeCount() / 10 ) {
589 wfDebugLog(
'generated-pp-node-count', $this->mGeneratedPPNodeCount .
' ' .
590 $this->mTitle->getPrefixedDBkey() );
594 # Wrap non-interface parser output in a <div> so it can be targeted
596 $class = $this->mOptions->getWrapOutputClass();
597 if ( $class !==
false && !$this->mOptions->getInterfaceMessage() ) {
598 $text = Html::rawElement(
'div', [
'class' => $class ], $text );
601 $this->mOutput->setText( $text );
603 $this->mRevisionId = $oldRevisionId;
604 $this->mRevisionObject = $oldRevisionObject;
605 $this->mRevisionTimestamp = $oldRevisionTimestamp;
606 $this->mRevisionUser = $oldRevisionUser;
607 $this->mRevisionSize = $oldRevisionSize;
608 $this->mInputSize =
false;
609 $this->currentRevisionCache =
null;
611 return $this->mOutput;
639 Hooks::run(
'ParserBeforeStrip', [ &
$parser, &$text, &$this->mStripState ] );
640 Hooks::run(
'ParserAfterStrip', [ &
$parser, &$text, &$this->mStripState ] );
682 $magicScopeVariable = $this->
lock();
684 if ( $revid !==
null ) {
685 $this->mRevisionId = $revid;
689 Hooks::run(
'ParserBeforeStrip', [ &
$parser, &$text, &$this->mStripState ] );
690 Hooks::run(
'ParserAfterStrip', [ &
$parser, &$text, &$this->mStripState ] );
692 $text = $this->mStripState->unstripBoth( $text );
707 $text = $this->mStripState->unstripBoth( $text );
726 $text = $msg->params(
$params )->plain();
728 # Parser (re)initialisation
729 $magicScopeVariable = $this->
lock();
735 $text = $this->mStripState->unstripBoth( $text );
746 $this->mUser =
$user;
756 $t = Title::newFromText(
'NO TITLE' );
759 if (
$t->hasFragment() ) {
760 # Strip the fragment to avoid various odd effects
761 $this->mTitle =
$t->createFragmentTarget(
'' );
773 return $this->mTitle;
782 public function Title( $x =
null ) {
783 return wfSetVar( $this->mTitle, $x );
792 $this->mOutputType =
$ot;
809 return wfSetVar( $this->mOutputType, $x );
818 return $this->mOutput;
827 return $this->mOptions;
837 return wfSetVar( $this->mOptions, $x );
844 return $this->mLinkID++;
851 $this->mLinkID = $id;
872 $target = $this->mOptions->getTargetLanguage();
874 if ( $target !==
null ) {
876 } elseif ( $this->mOptions->getInterfaceMessage() ) {
877 return $this->mOptions->getUserLangObj();
878 } elseif ( is_null( $this->mTitle ) ) {
879 throw new MWException( __METHOD__ .
': $this->mTitle is null' );
882 return $this->mTitle->getPageLanguage();
900 if ( !is_null( $this->mUser ) ) {
903 return $this->mOptions->getUser();
912 if ( !isset( $this->mPreprocessor ) ) {
913 $class = $this->mPreprocessorClass;
914 $this->mPreprocessor =
new $class( $this );
916 return $this->mPreprocessor;
926 if ( !$this->mLinkRenderer ) {
927 $this->mLinkRenderer = MediaWikiServices::getInstance()
928 ->getLinkRendererFactory()->create();
929 $this->mLinkRenderer->setStubThreshold(
934 return $this->mLinkRenderer;
961 $taglist = implode(
'|', $elements );
962 $start =
"/<($taglist)(\\s+[^>]*?|\\s*?)(\/?" .
">)|<(!--)/i";
964 while ( $text !=
'' ) {
965 $p = preg_split( $start, $text, 2, PREG_SPLIT_DELIM_CAPTURE );
967 if ( count( $p ) < 5 ) {
970 if ( count( $p ) > 5 ) {
984 $marker = self::MARKER_PREFIX .
"-$element-" . sprintf(
'%08X', $n++ ) . self::MARKER_SUFFIX;
985 $stripped .= $marker;
987 if ( $close ===
'/>' ) {
988 # Empty element tag, <tag />
993 if ( $element ===
'!--' ) {
996 $end =
"/(<\\/$element\\s*>)/i";
998 $q = preg_split( $end, $inside, 2, PREG_SPLIT_DELIM_CAPTURE );
1000 if ( count( $q ) < 3 ) {
1001 # No end tag -- let it run out to the end of the text.
1012 Sanitizer::decodeTagAttributes( $attributes ),
1013 "<$element$attributes$close$content$tail" ];
1024 return $this->mStripList;
1037 $marker = self::MARKER_PREFIX .
"-item-{$this->mMarkerIndex}-" . self::MARKER_SUFFIX;
1038 $this->mMarkerIndex++;
1039 $this->mStripState->addGeneral( $marker, $text );
1053 $td_history = []; # Is currently a td tag open?
1054 $last_tag_history = []; # Save
history of last lag activated (td, th or caption)
1055 $tr_history = []; # Is currently a tr tag open?
1056 $tr_attributes = []; #
history of tr attributes
1057 $has_opened_tr = []; # Did
this table open a <tr> element?
1058 $indent_level = 0; # indent level of the
table
1060 foreach (
$lines as $outLine ) {
1061 $line = trim( $outLine );
1064 $out .= $outLine .
"\n";
1068 $first_character =
$line[0];
1069 $first_two = substr(
$line, 0, 2 );
1072 if ( preg_match(
'/^(:*)\s*\{\|(.*)$/',
$line,
$matches ) ) {
1073 # First check if we are starting a new table
1074 $indent_level = strlen(
$matches[1] );
1076 $attributes = $this->mStripState->unstripBoth(
$matches[2] );
1077 $attributes = Sanitizer::fixTagAttributes( $attributes,
'table' );
1079 $outLine = str_repeat(
'<dl><dd>', $indent_level ) .
"<table{$attributes}>";
1080 array_push( $td_history,
false );
1081 array_push( $last_tag_history,
'' );
1082 array_push( $tr_history,
false );
1083 array_push( $tr_attributes,
'' );
1084 array_push( $has_opened_tr,
false );
1085 } elseif ( count( $td_history ) == 0 ) {
1086 # Don't do any of the following
1087 $out .= $outLine .
"\n";
1089 } elseif ( $first_two ===
'|}' ) {
1090 # We are ending a table
1092 $last_tag = array_pop( $last_tag_history );
1094 if ( !array_pop( $has_opened_tr ) ) {
1095 $line =
"<tr><td></td></tr>{$line}";
1098 if ( array_pop( $tr_history ) ) {
1099 $line =
"</tr>{$line}";
1102 if ( array_pop( $td_history ) ) {
1103 $line =
"</{$last_tag}>{$line}";
1105 array_pop( $tr_attributes );
1106 $outLine =
$line . str_repeat(
'</dd></dl>', $indent_level );
1107 } elseif ( $first_two ===
'|-' ) {
1108 # Now we have a table row
1109 $line = preg_replace(
'#^\|-+#',
'',
$line );
1111 # Whats after the tag is now only attributes
1112 $attributes = $this->mStripState->unstripBoth(
$line );
1113 $attributes = Sanitizer::fixTagAttributes( $attributes,
'tr' );
1114 array_pop( $tr_attributes );
1115 array_push( $tr_attributes, $attributes );
1118 $last_tag = array_pop( $last_tag_history );
1119 array_pop( $has_opened_tr );
1120 array_push( $has_opened_tr,
true );
1122 if ( array_pop( $tr_history ) ) {
1126 if ( array_pop( $td_history ) ) {
1127 $line =
"</{$last_tag}>{$line}";
1131 array_push( $tr_history,
false );
1132 array_push( $td_history,
false );
1133 array_push( $last_tag_history,
'' );
1134 } elseif ( $first_character ===
'|'
1135 || $first_character ===
'!'
1136 || $first_two ===
'|+'
1138 # This might be cell elements, td, th or captions
1139 if ( $first_two ===
'|+' ) {
1140 $first_character =
'+';
1147 if ( $first_character ===
'!' ) {
1151 # Split up multiple cells on the same line.
1152 # FIXME : This can result in improper nesting of tags processed
1153 # by earlier parser steps.
1154 $cells = explode(
'||',
$line );
1158 # Loop through each table cell
1159 foreach ( $cells as $cell ) {
1161 if ( $first_character !==
'+' ) {
1162 $tr_after = array_pop( $tr_attributes );
1163 if ( !array_pop( $tr_history ) ) {
1164 $previous =
"<tr{$tr_after}>\n";
1166 array_push( $tr_history,
true );
1167 array_push( $tr_attributes,
'' );
1168 array_pop( $has_opened_tr );
1169 array_push( $has_opened_tr,
true );
1172 $last_tag = array_pop( $last_tag_history );
1174 if ( array_pop( $td_history ) ) {
1175 $previous =
"</{$last_tag}>\n{$previous}";
1178 if ( $first_character ===
'|' ) {
1180 } elseif ( $first_character ===
'!' ) {
1182 } elseif ( $first_character ===
'+' ) {
1183 $last_tag =
'caption';
1188 array_push( $last_tag_history, $last_tag );
1190 # A cell could contain both parameters and data
1191 $cell_data = explode(
'|', $cell, 2 );
1193 # T2553: Note that a '|' inside an invalid link should not
1194 # be mistaken as delimiting cell parameters
1195 # Bug T153140: Neither should language converter markup.
1196 if ( preg_match(
'/\[\[|-\{/', $cell_data[0] ) === 1 ) {
1197 $cell =
"{$previous}<{$last_tag}>{$cell}";
1198 } elseif ( count( $cell_data ) == 1 ) {
1199 $cell =
"{$previous}<{$last_tag}>{$cell_data[0]}";
1201 $attributes = $this->mStripState->unstripBoth( $cell_data[0] );
1202 $attributes = Sanitizer::fixTagAttributes( $attributes, $last_tag );
1203 $cell =
"{$previous}<{$last_tag}{$attributes}>{$cell_data[1]}";
1207 array_push( $td_history,
true );
1210 $out .= $outLine .
"\n";
1213 # Closing open td, tr && table
1214 while ( count( $td_history ) > 0 ) {
1215 if ( array_pop( $td_history ) ) {
1218 if ( array_pop( $tr_history ) ) {
1221 if ( !array_pop( $has_opened_tr ) ) {
1222 $out .=
"<tr><td></td></tr>\n";
1225 $out .=
"</table>\n";
1228 # Remove trailing line-ending (b/c)
1229 if ( substr(
$out, -1 ) ===
"\n" ) {
1233 # special case: don't return empty table
1234 if (
$out ===
"<table>\n<tr><td></td></tr>\n</table>" ) {
1259 # Hook to suspend the parser in this state
1260 if ( !Hooks::run(
'ParserBeforeInternalParse', [ &
$parser, &$text, &$this->mStripState ] ) ) {
1264 # if $frame is provided, then use $frame for replacing any variables
1266 # use frame depth to infer how include/noinclude tags should be handled
1267 # depth=0 means this is the top-level document; otherwise it's an included document
1268 if ( !$frame->depth ) {
1271 $flag = self::PTD_FOR_INCLUSION;
1274 $text = $frame->expand( $dom );
1276 # if $frame is not provided, then use old-style replaceVariables
1280 Hooks::run(
'InternalParseBeforeSanitize', [ &
$parser, &$text, &$this->mStripState ] );
1281 $text = Sanitizer::removeHTMLtags(
1283 [ $this,
'attributeStripCallback' ],
1285 array_keys( $this->mTransparentTagHooks ),
1287 [ $this,
'addTrackingCategory' ]
1289 Hooks::run(
'InternalParseBeforeLinks', [ &
$parser, &$text, &$this->mStripState ] );
1291 # Tables need to come after variable replacement for things to work
1292 # properly; putting them before other transformations should keep
1293 # exciting things like link expansions from showing up in surprising
1297 $text = preg_replace(
'/(^|\n)-----*/',
'\\1<hr />', $text );
1306 # replaceInternalLinks may sometimes leave behind
1307 # absolute URLs, which have to be masked to hide them from replaceExternalLinks
1308 $text = str_replace( self::MARKER_PREFIX .
'NOPARSE',
'', $text );
1326 $text = $this->mStripState->unstripGeneral( $text );
1332 Hooks::run(
'ParserAfterUnstrip', [ &
$parser, &$text ] );
1335 # Clean up special characters, only run once, next-to-last before doBlockLevels
1337 # French spaces, last one Guillemet-left
1338 # only if there is something before the space
1339 '/(.) (?=\\?|:|;|!|%|\\302\\273)/' =>
'\\1 ',
1340 # french spaces, Guillemet-right
1341 '/(\\302\\253) /' =>
'\\1 ',
1342 '/ (!\s*important)/' =>
' \\1', # Beware of CSS magic word !important, T13874.
1344 $text = preg_replace( array_keys( $fixtags ), array_values( $fixtags ), $text );
1357 if ( !( $this->mOptions->getDisableContentConversion()
1358 || isset( $this->mDoubleUnderscores[
'nocontentconvert'] ) )
1360 if ( !$this->mOptions->getInterfaceMessage() ) {
1361 # The position of the convert() call should not be changed. it
1362 # assumes that the links are all replaced and the only thing left
1363 # is the <nowiki> mark.
1368 $text = $this->mStripState->unstripNoWiki( $text );
1371 Hooks::run(
'ParserBeforeTidy', [ &
$parser, &$text ] );
1375 $text = $this->mStripState->unstripGeneral( $text );
1377 $text = Sanitizer::normalizeCharReferences( $text );
1380 if ( $this->mOptions->getTidy() ) {
1384 # attempt to sanitize at least some nesting problems
1385 # (T4702 and quite a few others)
1387 # ''Something [http:
1388 # <i>Something</i><a href="http://www.cool.com"..><i>cool></i></a>
1389 '/(<([bi])>)(<([bi])>)?([^<]*)(<\/?a[^<]*>)([^<]*)(<\/\\4>)?(<\/\\2>)/' =>
1390 '\\1\\3\\5\\8\\9\\6\\1\\3\\7\\8\\9',
1391 # fix up an anchor inside another anchor, only
1392 # at least for a single single nested link (T5695)
1393 '/(<a[^>]+>)([^<]*)(<a[^>]+>[^<]*)<\/a>(.*)<\/a>/' =>
1394 '\\1\\2</a>\\3</a>\\1\\4</a>',
1395 # fix div inside inline elements- doBlockLevels won't wrap a line which
1396 # contains a div, so fix it up here; replace
1397 # div with escaped text
1398 '/(<([aib]) [^>]+>)([^<]*)(<div([^>]*)>)(.*)(<\/div>)([^<]*)(<\/\\2>)/' =>
1399 '\\1\\3<div\\5>\\6</div>\\8\\9',
1400 # remove empty italic or bold tag pairs, some
1401 # introduced by rules above
1402 '/<([bi])><\/\\1>/' =>
'',
1405 $text = preg_replace(
1406 array_keys( $tidyregs ),
1407 array_values( $tidyregs ),
1412 Hooks::run(
'ParserAfterTidy', [ &
$parser, &$text ] );
1431 $urlChar = self::EXT_LINK_URL_CLASS;
1432 $addr = self::EXT_LINK_ADDR;
1433 $space = self::SPACE_NOT_NL; # non-newline space
1434 $spdash =
"(?:-|$space)"; # a dash or a non-newline space
1435 $spaces =
"$space++"; # possessive match of 1 or more spaces
1436 $text = preg_replace_callback(
1438 (<a[ \t\r\n>].*?</a>) | # m[1]: Skip link text
1439 (<.*?>) | # m[2]: Skip stuff inside HTML elements' .
"
1440 (\b # m[3]: Free external links
1442 ($addr$urlChar*) # m[4]: Post-protocol path
1444 \b(?:RFC|PMID) $spaces # m[5]: RFC or PMID, capture number
1446 \bISBN $spaces ( # m[6]: ISBN, capture number
1447 (?: 97[89] $spdash? )? # optional 13-digit ISBN prefix
1448 (?: [0-9] $spdash? ){9} # 9 digits with opt. delimiters
1449 [0-9Xx] # check digit
1451 )!xu", [ $this,
'magicLinkCallback' ], $text );
1461 if ( isset( $m[1] ) && $m[1] !==
'' ) {
1464 } elseif ( isset( $m[2] ) && $m[2] !==
'' ) {
1467 } elseif ( isset( $m[3] ) && $m[3] !==
'' ) {
1468 # Free external link
1470 } elseif ( isset( $m[5] ) && $m[5] !==
'' ) {
1472 if ( substr( $m[0], 0, 3 ) ===
'RFC' ) {
1473 if ( !$this->mOptions->getMagicRFCLinks() ) {
1478 $cssClass =
'mw-magiclink-rfc';
1479 $trackingCat =
'magiclink-tracking-rfc';
1481 } elseif ( substr( $m[0], 0, 4 ) ===
'PMID' ) {
1482 if ( !$this->mOptions->getMagicPMIDLinks() ) {
1486 $urlmsg =
'pubmedurl';
1487 $cssClass =
'mw-magiclink-pmid';
1488 $trackingCat =
'magiclink-tracking-pmid';
1491 throw new MWException( __METHOD__ .
': unrecognised match type "' .
1492 substr( $m[0], 0, 20 ) .
'"' );
1494 $url =
wfMessage( $urlmsg, $id )->inContentLanguage()->text();
1497 } elseif ( isset( $m[6] ) && $m[6] !==
''
1498 && $this->mOptions->getMagicISBNLinks()
1502 $space = self::SPACE_NOT_NL; # non-newline space
1503 $isbn = preg_replace(
"/$space/",
' ', $isbn );
1504 $num = strtr( $isbn, [
1514 'class' =>
'internal mw-magiclink-isbn',
1535 # The characters '<' and '>' (which were escaped by
1536 # removeHTMLtags()) should not be included in
1537 # URLs, per RFC 2396.
1538 # Make terminate a URL as well (bug T84937)
1541 '/&(lt|gt|nbsp|#x0*(3[CcEe]|[Aa]0)|#0*(60|62|160));/',
1546 $trail = substr( $url, $m2[0][1] ) . $trail;
1547 $url = substr( $url, 0, $m2[0][1] );
1550 # Move trailing punctuation to $trail
1552 # If there is no left bracket, then consider right brackets fair game too
1553 if ( strpos( $url,
'(' ) ===
false ) {
1557 $urlRev = strrev( $url );
1558 $numSepChars = strspn( $urlRev, $sep );
1559 # Don't break a trailing HTML entity by moving the ; into $trail
1560 # This is in hot code, so use substr_compare to avoid having to
1561 # create a new string object for the comparison
1562 if ( $numSepChars && substr_compare( $url,
";", -$numSepChars, 1 ) === 0 ) {
1563 # more optimization: instead of running preg_match with a $
1564 # anchor, which can be slow, do the match on the reversed
1565 # string starting at the desired offset.
1566 # un-reversed regexp is: /&([a-z]+|#x[\da-f]+|#\d+)$/i
1567 if ( preg_match(
'/\G([a-z]+|[\da-f]+x#|\d+#)&/i', $urlRev, $m2, 0, $numSepChars ) ) {
1571 if ( $numSepChars ) {
1572 $trail = substr( $url, -$numSepChars ) . $trail;
1573 $url = substr( $url, 0, -$numSepChars );
1576 # Verify that we still have a real URL after trail removal, and
1577 # not just lone protocol
1578 if ( strlen( $trail ) >= $numPostProto ) {
1579 return $url . $trail;
1582 $url = Sanitizer::cleanUrl( $url );
1584 # Is this an external image?
1586 if ( $text ===
false ) {
1587 # Not an image, make a link
1592 # Register it in the output object...
1593 $this->mOutput->addExternalLink( $url );
1595 return $text . $trail;
1608 for ( $i = 6; $i >= 1; --$i ) {
1609 $h = str_repeat(
'=', $i );
1610 $text = preg_replace(
"/^$h(.+)$h\\s*$/m",
"<h$i>\\1</h$i>", $text );
1629 $outtext = substr( $outtext, 0, -1 );
1641 $arr = preg_split(
"/(''+)/", $text, -1, PREG_SPLIT_DELIM_CAPTURE );
1642 $countarr = count( $arr );
1643 if ( $countarr == 1 ) {
1652 for ( $i = 1; $i < $countarr; $i += 2 ) {
1653 $thislen = strlen( $arr[$i] );
1657 if ( $thislen == 4 ) {
1658 $arr[$i - 1] .=
"'";
1661 } elseif ( $thislen > 5 ) {
1665 $arr[$i - 1] .= str_repeat(
"'", $thislen - 5 );
1670 if ( $thislen == 2 ) {
1672 } elseif ( $thislen == 3 ) {
1674 } elseif ( $thislen == 5 ) {
1684 if ( ( $numbold % 2 == 1 ) && ( $numitalics % 2 == 1 ) ) {
1685 $firstsingleletterword = -1;
1686 $firstmultiletterword = -1;
1688 for ( $i = 1; $i < $countarr; $i += 2 ) {
1689 if ( strlen( $arr[$i] ) == 3 ) {
1690 $x1 = substr( $arr[$i - 1], -1 );
1691 $x2 = substr( $arr[$i - 1], -2, 1 );
1692 if ( $x1 ===
' ' ) {
1693 if ( $firstspace == -1 ) {
1696 } elseif ( $x2 ===
' ' ) {
1697 $firstsingleletterword = $i;
1702 if ( $firstmultiletterword == -1 ) {
1703 $firstmultiletterword = $i;
1710 if ( $firstsingleletterword > -1 ) {
1711 $arr[$firstsingleletterword] =
"''";
1712 $arr[$firstsingleletterword - 1] .=
"'";
1713 } elseif ( $firstmultiletterword > -1 ) {
1715 $arr[$firstmultiletterword] =
"''";
1716 $arr[$firstmultiletterword - 1] .=
"'";
1717 } elseif ( $firstspace > -1 ) {
1721 $arr[$firstspace] =
"''";
1722 $arr[$firstspace - 1] .=
"'";
1731 foreach ( $arr as $r ) {
1732 if ( ( $i % 2 ) == 0 ) {
1733 if ( $state ===
'both' ) {
1739 $thislen = strlen( $r );
1740 if ( $thislen == 2 ) {
1741 if ( $state ===
'i' ) {
1744 } elseif ( $state ===
'bi' ) {
1747 } elseif ( $state ===
'ib' ) {
1750 } elseif ( $state ===
'both' ) {
1757 } elseif ( $thislen == 3 ) {
1758 if ( $state ===
'b' ) {
1761 } elseif ( $state ===
'bi' ) {
1764 } elseif ( $state ===
'ib' ) {
1767 } elseif ( $state ===
'both' ) {
1774 } elseif ( $thislen == 5 ) {
1775 if ( $state ===
'b' ) {
1778 } elseif ( $state ===
'i' ) {
1781 } elseif ( $state ===
'bi' ) {
1784 } elseif ( $state ===
'ib' ) {
1787 } elseif ( $state ===
'both' ) {
1799 if ( $state ===
'b' || $state ===
'ib' ) {
1802 if ( $state ===
'i' || $state ===
'bi' || $state ===
'ib' ) {
1805 if ( $state ===
'bi' ) {
1809 if ( $state ===
'both' &&
$buffer ) {
1829 $bits = preg_split( $this->mExtLinkBracketedRegex, $text, -1, PREG_SPLIT_DELIM_CAPTURE );
1830 if ( $bits ===
false ) {
1831 throw new MWException(
"PCRE needs to be compiled with "
1832 .
"--enable-unicode-properties in order for MediaWiki to function" );
1834 $s = array_shift( $bits );
1837 while ( $i < count( $bits ) ) {
1840 $text = $bits[$i++];
1841 $trail = $bits[$i++];
1843 # The characters '<' and '>' (which were escaped by
1844 # removeHTMLtags()) should not be included in
1845 # URLs, per RFC 2396.
1847 if ( preg_match(
'/&(lt|gt);/', $url, $m2, PREG_OFFSET_CAPTURE ) ) {
1848 $text = substr( $url, $m2[0][1] ) .
' ' . $text;
1849 $url = substr( $url, 0, $m2[0][1] );
1852 # If the link text is an image URL, replace it with an <img> tag
1853 # This happened by accident in the original parser, but some people used it extensively
1855 if ( $img !==
false ) {
1861 # Set linktype for CSS - if URL==text, link is essentially free
1862 $linktype = ( $text === $url ) ?
'free' :
'text';
1864 # No link text, e.g. [http:
1865 if ( $text ==
'' ) {
1868 $text =
'[' . $langObj->formatNum( ++$this->mAutonumber ) .
']';
1869 $linktype =
'autonumber';
1871 # Have link text, e.g. [http:
1878 $url = Sanitizer::cleanUrl( $url );
1880 # Use the encoded URL
1881 # This means that users can paste URLs directly into the text
1882 # Funny characters like ö aren't valid in URLs anyway
1883 # This was changed in August 2004
1887 # Register link in the output object.
1888 $this->mOutput->addExternalLink( $url );
1905 $ns = $title ? $title->getNamespace() :
false;
1926 $rel = self::getExternalLinkRel( $url, $this->mTitle );
1928 $target = $this->mOptions->getExternalLinkTarget();
1931 if ( !in_array( $target, [
'_self',
'_parent',
'_top' ] ) ) {
1935 if ( $rel !==
'' ) {
1938 $rel .=
'noreferrer noopener';
1955 # First, make sure unsafe characters are encoded
1956 $url = preg_replace_callback(
'/[\x00-\x20"<>\[\\\\\]^`{|}\x7F-\xFF]/',
1958 return rawurlencode( $m[0] );
1964 $end = strlen( $url );
1966 # Fragment part - 'fragment'
1967 $start = strpos( $url,
'#' );
1968 if ( $start !==
false && $start < $end ) {
1969 $ret = self::normalizeUrlComponent(
1970 substr( $url, $start, $end - $start ),
'"#%<>[\]^`{|}' ) .
$ret;
1974 # Query part - 'query' minus &=+;
1975 $start = strpos( $url,
'?' );
1976 if ( $start !==
false && $start < $end ) {
1977 $ret = self::normalizeUrlComponent(
1978 substr( $url, $start, $end - $start ),
'"#%<>[\]^`{|}&=+;' ) .
$ret;
1982 # Scheme and path part - 'pchar'
1983 # (we assume no userinfo or encoded colons in the host)
1984 $ret = self::normalizeUrlComponent(
1985 substr( $url, 0, $end ),
'"#%<>[\]^`{|}/?' ) .
$ret;
1991 $callback =
function (
$matches ) use ( $unsafe ) {
1993 $ord = ord( $char );
1994 if ( $ord > 32 && $ord < 127 && strpos( $unsafe, $char ) ===
false ) {
1998 # Leave it escaped, but use uppercase for a-f
2002 return preg_replace_callback(
'/%[0-9A-Fa-f]{2}/', $callback, $component );
2014 $imagesfrom = $this->mOptions->getAllowExternalImagesFrom();
2015 $imagesexception = !empty( $imagesfrom );
2017 # $imagesfrom could be either a single string or an array of strings, parse out the latter
2018 if ( $imagesexception && is_array( $imagesfrom ) ) {
2019 $imagematch =
false;
2020 foreach ( $imagesfrom as $match ) {
2021 if ( strpos( $url, $match ) === 0 ) {
2026 } elseif ( $imagesexception ) {
2027 $imagematch = ( strpos( $url, $imagesfrom ) === 0 );
2029 $imagematch =
false;
2032 if ( $this->mOptions->getAllowExternalImages()
2033 || ( $imagesexception && $imagematch )
2035 if ( preg_match( self::EXT_IMAGE_REGEX, $url ) ) {
2040 if ( !$text && $this->mOptions->getEnableImageWhitelist()
2041 && preg_match( self::EXT_IMAGE_REGEX, $url )
2043 $whitelist = explode(
2045 wfMessage(
'external_image_whitelist' )->inContentLanguage()->
text()
2048 foreach ( $whitelist as $entry ) {
2049 # Sanitize the regex fragment, make it case-insensitive, ignore blank entries/comments
2050 if ( strpos( $entry,
'#' ) === 0 || $entry ===
'' ) {
2053 if ( preg_match(
'/' . str_replace(
'/',
'\\/', $entry ) .
'/i', $url ) ) {
2054 # Image matches a whitelist entry
2088 static $tc =
false, $e1, $e1_img;
2089 # the % is needed to support urlencoded titles as well
2091 $tc = Title::legalChars() .
'#%';
2092 # Match a link having the form [[namespace:link|alternate]]trail
2093 $e1 =
"/^([{$tc}]+)(?:\\|(.+?))?]](.*)\$/sD";
2094 # Match cases where there is no "]]", which might still be images
2095 $e1_img =
"/^([{$tc}]+)\\|(.*)\$/sD";
2100 # split the entire text string on occurrences of [[
2102 # get the first element (all text up to first [[), and remove the space we added
2105 $line = $a->current(); # Workaround
for broken ArrayIterator::next() that returns
"void"
2106 $s = substr(
$s, 1 );
2110 if ( $useLinkPrefixExtension ) {
2111 # Match the end of a line for a word that's not followed by whitespace,
2112 # e.g. in the case of 'The Arab al[[Razi]]', 'al' will be matched
2115 $e2 =
"/^((?>.*[^$charset]|))(.+)$/sDu";
2118 if ( is_null( $this->mTitle ) ) {
2119 throw new MWException( __METHOD__ .
": \$this->mTitle is null\n" );
2121 $nottalk = !$this->mTitle->isTalkPage();
2123 if ( $useLinkPrefixExtension ) {
2125 if ( preg_match( $e2,
$s, $m ) ) {
2126 $first_prefix = $m[2];
2128 $first_prefix =
false;
2137 # Loop for each link
2138 for ( ;
$line !==
false &&
$line !==
null; $a->next(),
$line = $a->current() ) {
2141 # Check for excessive memory usage
2142 if ( $holders->isBig() ) {
2144 # Do the existence check, replace the link holders and clear the array
2145 $holders->replace(
$s );
2149 if ( $useLinkPrefixExtension ) {
2150 if ( preg_match( $e2,
$s, $m ) ) {
2157 if ( $first_prefix ) {
2158 $prefix = $first_prefix;
2159 $first_prefix =
false;
2163 $might_be_img =
false;
2165 if ( preg_match( $e1,
$line, $m ) ) { # page with
normal text or alt
2167 # If we get a ] at the beginning of $m[3] that means we have a link that's something like:
2168 # [[Image:Foo.jpg|[http:
2169 # the real problem is with the $e1 regex
2171 # Still some problems for cases where the ] is meant to be outside punctuation,
2172 # and no image is in sight. See T4095.
2174 && substr( $m[3], 0, 1 ) ===
']'
2175 && strpos( $text,
'[' ) !==
false
2178 $m[3] = substr( $m[3], 1 );
2180 # fix up urlencoded title texts
2181 if ( strpos( $m[1],
'%' ) !==
false ) {
2182 # Should anchors '#' also be rejected?
2183 $m[1] = str_replace( [
'<',
'>' ], [
'<',
'>' ], rawurldecode( $m[1] ) );
2186 } elseif ( preg_match( $e1_img,
$line, $m ) ) {
2187 # Invalid, but might be an image with a link in its caption
2188 $might_be_img =
true;
2190 if ( strpos( $m[1],
'%' ) !==
false ) {
2191 $m[1] = str_replace( [
'<',
'>' ], [
'<',
'>' ], rawurldecode( $m[1] ) );
2199 $origLink = ltrim( $m[1],
' ' );
2201 # Don't allow internal links to pages containing
2202 # PROTO: where PROTO is a valid URL protocol; these
2203 # should be external links.
2204 if ( preg_match(
'/^(?i:' . $this->mUrlProtocols .
')/', $origLink ) ) {
2209 # Make subpage if necessary
2210 if ( $useSubpages ) {
2216 $unstrip = $this->mStripState->unstripNoWiki(
$link );
2217 $nt = is_string( $unstrip ) ? Title::newFromText( $unstrip ) :
null;
2218 if ( $nt ===
null ) {
2223 $ns = $nt->getNamespace();
2224 $iw = $nt->getInterwiki();
2226 $noforce = ( substr( $origLink, 0, 1 ) !==
':' );
2228 if ( $might_be_img ) { #
if this is actually an invalid link
2229 if ( $ns ==
NS_FILE && $noforce ) { # but might be an image
2232 # look at the next 'line' to see if we can close it there
2234 $next_line = $a->current();
2235 if ( $next_line ===
false || $next_line ===
null ) {
2238 $m = explode(
']]', $next_line, 3 );
2239 if ( count( $m ) == 3 ) {
2240 # the first ]] closes the inner link, the second the image
2242 $text .=
"[[{$m[0]}]]{$m[1]}";
2245 } elseif ( count( $m ) == 2 ) {
2246 # if there's exactly one ]] that's fine, we'll keep looking
2247 $text .=
"[[{$m[0]}]]{$m[1]}";
2249 # if $next_line is invalid too, we need look no further
2250 $text .=
'[[' . $next_line;
2255 # we couldn't find the end of this imageLink, so output it raw
2256 # but don't ignore what might be perfectly normal links in the text we've examined
2258 $s .=
"{$prefix}[[$link|$text";
2259 # note: no $trail, because without an end, there *is* no trail
2262 }
else { # it
's not an image, so output it raw
2263 $s .= "{$prefix}[[$link|$text";
2264 # note: no $trail, because without an end, there *is* no trail
2269 $wasblank = ( $text == '' );
2273 # Strip off leading ':
'
2274 $text = substr( $text, 1 );
2277 # T6598 madness. Handle the quotes only if they come from the alternate part
2278 # [[Lista d''e paise d''o munno]] -> <a href="...">Lista d''e paise d''o munno</a>
2279 # [[Criticism of Harry Potter|Criticism of ''Harry Potter'']]
2280 # -> <a href="Criticism of Harry Potter">Criticism of <i>Harry Potter</i></a>
2281 $text = $this->doQuotes( $text );
2284 # Link not escaped by : , create the various objects
2285 if ( $noforce && !$nt->wasLocalInterwiki() ) {
2288 $iw && $this->mOptions->getInterwikiMagic() && $nottalk && (
2289 Language::fetchLanguageName( $iw, null, 'mw
' ) ||
2290 in_array( $iw, $wgExtraInterlanguageLinkPrefixes )
2293 # T26502: filter duplicates
2294 if ( !isset( $this->mLangLinkLanguages[$iw] ) ) {
2295 $this->mLangLinkLanguages[$iw] = true;
2296 $this->mOutput->addLanguageLink( $nt->getFullText() );
2299 $s = rtrim( $s . $prefix );
2300 $s .= trim( $trail, "\n" ) == '' ? '' : $prefix . $trail;
2304 if ( $ns == NS_FILE ) {
2305 if ( !wfIsBadImage( $nt->getDBkey(), $this->mTitle ) ) {
2307 # if no parameters were passed, $text
2308 # becomes something like "File:Foo.png",
2309 # which we don't want to pass on to the
2313 # recursively parse links inside the image caption
2314 # actually, this will parse them in any other parameters, too,
2315 # but it might be hard to fix that, and it doesn't matter ATM
2319 # cloak any absolute URLs inside the image markup, so replaceExternalLinks() won't touch them
2321 $this->
makeImage( $nt, $text, $holders ) ) . $trail;
2325 $s = rtrim(
$s .
"\n" ); # T2087
2332 $sortkey = Sanitizer::decodeCharReferences( $sortkey );
2333 $sortkey = str_replace(
"\n",
'', $sortkey );
2335 $this->mOutput->addCategory( $nt->getDBkey(), $sortkey );
2340 $s .= trim( $prefix . $trail,
"\n" ) ==
'' ?
'' : $prefix . $trail;
2346 # Self-link checking. For some languages, variants of the title are checked in
2347 # LinkHolderArray::doVariants() to allow batching the existence checks necessary
2348 # for linking to a different variant.
2349 if ( $ns !=
NS_SPECIAL && $nt->equals( $this->mTitle ) && !$nt->hasFragment() ) {
2354 # NS_MEDIA is a pseudo-namespace for linking directly to a file
2355 # @todo FIXME: Should do batch file existence checks, see comment below
2357 # Give extensions a chance to select the file revision for us
2361 [ $this, $nt, &
$options, &$descQuery ] );
2362 # Fetch and register the file (file title may be different via hooks)
2364 # Cloak with NOPARSE to avoid replacement in replaceExternalLinks
2370 # Some titles, such as valid special pages or files in foreign repos, should
2371 # be shown as bluelinks even though they're not included in the page table
2372 # @todo FIXME: isAlwaysKnown() can be expensive for file links; we should really do
2373 # batch file existence checks for NS_FILE and NS_MEDIA
2374 if ( $iw ==
'' && $nt->isAlwaysKnown() ) {
2375 $this->mOutput->addLink( $nt );
2378 # Links will be added to the output link list after checking
2379 $s .= $holders->makeHolder( $nt, $text, [], $trail, $prefix );
2401 if ( $text ==
'' ) {
2402 $text = htmlspecialchars( $nt->getPrefixedText() );
2405 $link = $this->getLinkRenderer()->makeKnownLink(
2406 $nt,
new HtmlArmor(
"$prefix$text$inside" )
2409 return $this->armorLinks(
$link ) . $trail;
2423 return preg_replace(
'/\b((?i)' . $this->mUrlProtocols .
')/',
2424 self::MARKER_PREFIX .
"NOPARSE$1", $text );
2432 # Some namespaces don't allow subpages
2433 return MWNamespace::hasSubpages( $this->mTitle->getNamespace() );
2475 if ( is_null( $this->mTitle ) ) {
2480 throw new MWException( __METHOD__ .
' Should only be '
2481 .
' called while parsing (no title set)' );
2491 if ( Hooks::run(
'ParserGetVariableValueVarCache', [ &
$parser, &$this->mVarCache ] ) ) {
2492 if ( isset( $this->mVarCache[$index] ) ) {
2493 return $this->mVarCache[$index];
2497 $ts =
wfTimestamp( TS_UNIX, $this->mOptions->getTimestamp() );
2498 Hooks::run(
'ParserGetVariableValueTs', [ &
$parser, &$ts ] );
2500 $pageLang = $this->getFunctionLang();
2506 case 'currentmonth':
2509 case 'currentmonth1':
2512 case 'currentmonthname':
2515 case 'currentmonthnamegen':
2518 case 'currentmonthabbrev':
2533 case 'localmonthname':
2536 case 'localmonthnamegen':
2539 case 'localmonthabbrev':
2554 case 'fullpagename':
2557 case 'fullpagenamee':
2563 case 'subpagenamee':
2566 case 'rootpagename':
2569 case 'rootpagenamee':
2573 $this->mTitle->getRootText()
2576 case 'basepagename':
2579 case 'basepagenamee':
2583 $this->mTitle->getBaseText()
2586 case 'talkpagename':
2587 if ( $this->mTitle->canHaveTalkPage() ) {
2588 $talkPage = $this->mTitle->getTalkPage();
2594 case 'talkpagenamee':
2595 if ( $this->mTitle->canHaveTalkPage() ) {
2596 $talkPage = $this->mTitle->getTalkPage();
2602 case 'subjectpagename':
2603 $subjPage = $this->mTitle->getSubjectPage();
2606 case 'subjectpagenamee':
2607 $subjPage = $this->mTitle->getSubjectPage();
2611 $pageid = $this->getTitle()->getArticleID();
2612 if ( $pageid == 0 ) {
2613 # 0 means the page doesn't exist in the database,
2614 # which means the user is previewing a new page.
2615 # The vary-revision flag must be set, because the magic word
2616 # will have a different value once the page is saved.
2617 $this->mOutput->setFlag(
'vary-revision' );
2618 wfDebug( __METHOD__ .
": {{PAGEID}} used in a new page, setting vary-revision...\n" );
2620 $value = $pageid ? $pageid :
null;
2623 # Let the edit saving system know we should parse the page
2624 # *after* a revision ID has been assigned.
2625 $this->mOutput->setFlag(
'vary-revision-id' );
2626 wfDebug( __METHOD__ .
": {{REVISIONID}} used, setting vary-revision-id...\n" );
2627 $value = $this->mRevisionId;
2628 if ( !
$value && $this->mOptions->getSpeculativeRevIdCallback() ) {
2629 $value = call_user_func( $this->mOptions->getSpeculativeRevIdCallback() );
2630 $this->mOutput->setSpeculativeRevIdUsed(
$value );
2634 # Let the edit saving system know we should parse the page
2635 # *after* a revision ID has been assigned. This is for null edits.
2636 $this->mOutput->setFlag(
'vary-revision' );
2637 wfDebug( __METHOD__ .
": {{REVISIONDAY}} used, setting vary-revision...\n" );
2638 $value = intval( substr( $this->getRevisionTimestamp(), 6, 2 ) );
2640 case 'revisionday2':
2641 # Let the edit saving system know we should parse the page
2642 # *after* a revision ID has been assigned. This is for null edits.
2643 $this->mOutput->setFlag(
'vary-revision' );
2644 wfDebug( __METHOD__ .
": {{REVISIONDAY2}} used, setting vary-revision...\n" );
2645 $value = substr( $this->getRevisionTimestamp(), 6, 2 );
2647 case 'revisionmonth':
2648 # Let the edit saving system know we should parse the page
2649 # *after* a revision ID has been assigned. This is for null edits.
2650 $this->mOutput->setFlag(
'vary-revision' );
2651 wfDebug( __METHOD__ .
": {{REVISIONMONTH}} used, setting vary-revision...\n" );
2652 $value = substr( $this->getRevisionTimestamp(), 4, 2 );
2654 case 'revisionmonth1':
2655 # Let the edit saving system know we should parse the page
2656 # *after* a revision ID has been assigned. This is for null edits.
2657 $this->mOutput->setFlag(
'vary-revision' );
2658 wfDebug( __METHOD__ .
": {{REVISIONMONTH1}} used, setting vary-revision...\n" );
2659 $value = intval( substr( $this->getRevisionTimestamp(), 4, 2 ) );
2661 case 'revisionyear':
2662 # Let the edit saving system know we should parse the page
2663 # *after* a revision ID has been assigned. This is for null edits.
2664 $this->mOutput->setFlag(
'vary-revision' );
2665 wfDebug( __METHOD__ .
": {{REVISIONYEAR}} used, setting vary-revision...\n" );
2666 $value = substr( $this->getRevisionTimestamp(), 0, 4 );
2668 case 'revisiontimestamp':
2669 # Let the edit saving system know we should parse the page
2670 # *after* a revision ID has been assigned. This is for null edits.
2671 $this->mOutput->setFlag(
'vary-revision' );
2672 wfDebug( __METHOD__ .
": {{REVISIONTIMESTAMP}} used, setting vary-revision...\n" );
2673 $value = $this->getRevisionTimestamp();
2675 case 'revisionuser':
2676 # Let the edit saving system know we should parse the page
2677 # *after* a revision ID has been assigned for null edits.
2678 $this->mOutput->setFlag(
'vary-user' );
2679 wfDebug( __METHOD__ .
": {{REVISIONUSER}} used, setting vary-user...\n" );
2680 $value = $this->getRevisionUser();
2682 case 'revisionsize':
2683 $value = $this->getRevisionSize();
2686 $value = str_replace(
'_',
' ',
$wgContLang->getNsText( $this->mTitle->getNamespace() ) );
2691 case 'namespacenumber':
2692 $value = $this->mTitle->getNamespace();
2695 $value = $this->mTitle->canHaveTalkPage()
2696 ? str_replace(
'_',
' ', $this->mTitle->getTalkNsText() )
2700 $value = $this->mTitle->canHaveTalkPage() ?
wfUrlencode( $this->mTitle->getTalkNsText() ) :
'';
2702 case 'subjectspace':
2703 $value = str_replace(
'_',
' ', $this->mTitle->getSubjectNsText() );
2705 case 'subjectspacee':
2708 case 'currentdayname':
2721 # @bug T6594 PHP5 has it zero padded, PHP4 does not, cast to
2722 # int to remove the padding
2728 case 'localdayname':
2729 $value = $pageLang->getWeekdayName(
2737 $value = $pageLang->time(
2747 # @bug T6594 PHP5 has it zero padded, PHP4 does not, cast to
2748 # int to remove the padding
2754 case 'numberofarticles':
2757 case 'numberoffiles':
2760 case 'numberofusers':
2763 case 'numberofactiveusers':
2766 case 'numberofpages':
2769 case 'numberofadmins':
2772 case 'numberofedits':
2775 case 'currenttimestamp':
2778 case 'localtimestamp':
2781 case 'currentversion':
2796 case 'directionmark':
2797 return $pageLang->getDirMark();
2798 case 'contentlanguage':
2801 case 'pagelanguage':
2802 $value = $pageLang->getCode();
2804 case 'cascadingsources':
2810 'ParserGetVariableValueSwitch',
2811 [ &
$parser, &$this->mVarCache, &$index, &
$ret, &$frame ]
2818 $this->mVarCache[$index] =
$value;
2860 $dom = $this->getPreprocessor()->preprocessToObj( $text,
$flags );
2872 $ltrimmed = ltrim(
$s );
2873 $w1 = substr(
$s, 0, strlen(
$s ) - strlen( $ltrimmed ) );
2874 $trimmed = rtrim( $ltrimmed );
2875 $diff = strlen( $ltrimmed ) - strlen( $trimmed );
2877 $w2 = substr( $ltrimmed, -$diff );
2881 return [ $w1, $trimmed, $w2 ];
2905 # Is there any text? Also, Prevent too big inclusions!
2906 $textSize = strlen( $text );
2907 if ( $textSize < 1 || $textSize > $this->mOptions->getMaxIncludeSize() ) {
2911 if ( $frame ===
false ) {
2912 $frame = $this->getPreprocessor()->newFrame();
2913 } elseif ( !( $frame instanceof
PPFrame ) ) {
2914 wfDebug( __METHOD__ .
" called using plain parameters instead of "
2915 .
"a PPFrame instance. Creating custom frame.\n" );
2916 $frame = $this->getPreprocessor()->newCustomFrame( $frame );
2919 $dom = $this->preprocessToDom( $text );
2921 $text = $frame->expand( $dom,
$flags );
2936 foreach (
$args as $arg ) {
2937 $eqpos = strpos( $arg,
'=' );
2938 if ( $eqpos ===
false ) {
2939 $assocArgs[$index++] = $arg;
2941 $name = trim( substr( $arg, 0, $eqpos ) );
2942 $value = trim( substr( $arg, $eqpos + 1 ) );
2943 if (
$value ===
false ) {
2946 if ( $name !==
false ) {
2982 # does no harm if $current and $max are present but are unnecessary for the message
2983 # Not doing ->inLanguage( $this->mOptions->getUserLangObj() ), since this is shown
2984 # only during preview, and that would split the parser cache unnecessarily.
2985 $warning =
wfMessage(
"$limitationType-warning" )->numParams( $current, $max )
2987 $this->mOutput->addWarning( $warning );
2988 $this->addTrackingCategory(
"$limitationType-category" );
3013 $forceRawInterwiki =
false;
3015 $isChildObj =
false;
3017 $isLocalObj =
false;
3019 # Title object, where $text came from
3022 # $part1 is the bit before the first |, and must contain only title characters.
3023 # Various prefixes will be stripped from it later.
3024 $titleWithSpaces = $frame->expand( $piece[
'title'] );
3025 $part1 = trim( $titleWithSpaces );
3028 # Original title text preserved for various purposes
3029 $originalTitle = $part1;
3031 # $args is a list of argument nodes, starting from index 0, not including $part1
3032 # @todo FIXME: If piece['parts'] is null then the call to getLength()
3033 # below won't work b/c this $args isn't an object
3034 $args = (
null == $piece[
'parts'] ) ? [] : $piece[
'parts'];
3036 $profileSection =
null;
3040 $substMatch = $this->mSubstWords->matchStartAndRemove( $part1 );
3042 # Possibilities for substMatch: "subst", "safesubst" or FALSE
3043 # Decide whether to expand template or keep wikitext as-is.
3044 if ( $this->ot[
'wiki'] ) {
3045 if ( $substMatch ===
false ) {
3046 $literal =
true; # literal when in PST with no prefix
3048 $literal =
false; # expand when in PST with subst: or safesubst:
3051 if ( $substMatch ==
'subst' ) {
3052 $literal =
true; # literal when not in PST with
plain subst:
3054 $literal =
false; # expand when not in PST with safesubst: or no prefix
3058 $text = $frame->virtualBracketedImplode(
'{{',
'|',
'}}', $titleWithSpaces,
$args );
3065 if ( !$found &&
$args->getLength() == 0 ) {
3066 $id = $this->mVariables->matchStartToEnd( $part1 );
3067 if ( $id !==
false ) {
3068 $text = $this->getVariableValue( $id, $frame );
3076 # MSG, MSGNW and RAW
3080 if ( $mwMsgnw->matchStartAndRemove( $part1 ) ) {
3083 # Remove obsolete MSG:
3085 $mwMsg->matchStartAndRemove( $part1 );
3090 if ( $mwRaw->matchStartAndRemove( $part1 ) ) {
3091 $forceRawInterwiki =
true;
3097 $colonPos = strpos( $part1,
':' );
3098 if ( $colonPos !==
false ) {
3099 $func = substr( $part1, 0, $colonPos );
3100 $funcArgs = [ trim( substr( $part1, $colonPos + 1 ) ) ];
3101 $argsLength =
$args->getLength();
3102 for ( $i = 0; $i < $argsLength; $i++ ) {
3103 $funcArgs[] =
$args->item( $i );
3106 $result = $this->callParserFunction( $frame, $func, $funcArgs );
3107 }
catch ( Exception $ex ) {
3111 # The interface for parser functions allows for extracting
3112 # flags into the local scope. Extract any forwarded flags
3118 # Finish mangling title and then check for loops.
3119 # Set $title to a Title object and $titleText to the PDBK
3122 # Split the title into page and subpage
3124 $relative = $this->maybeDoSubpageLink( $part1, $subpage );
3125 if ( $part1 !== $relative ) {
3127 $ns = $this->mTitle->getNamespace();
3129 $title = Title::newFromText( $part1, $ns );
3131 $titleText = $title->getPrefixedText();
3132 # Check for language variants if the template is not found
3133 if ( $this->getConverterLanguage()->hasVariants() && $title->getArticleID() == 0 ) {
3134 $this->getConverterLanguage()->findVariantLink( $part1, $title,
true );
3136 # Do recursion depth check
3137 $limit = $this->mOptions->getMaxTemplateDepth();
3138 if ( $frame->depth >= $limit ) {
3140 $text =
'<span class="error">'
3141 .
wfMessage(
'parser-template-recursion-depth-warning' )
3142 ->numParams( $limit )->inContentLanguage()->text()
3148 # Load from database
3149 if ( !$found && $title ) {
3150 $profileSection = $this->mProfiler->scopedProfileIn( $title->getPrefixedDBkey() );
3151 if ( !$title->isExternal() ) {
3152 if ( $title->isSpecialPage()
3153 && $this->mOptions->getAllowSpecialInclusion()
3154 && $this->ot[
'html']
3161 $argsLength =
$args->getLength();
3162 for ( $i = 0; $i < $argsLength; $i++ ) {
3163 $bits =
$args->item( $i )->splitArg();
3164 if ( strval( $bits[
'index'] ) ===
'' ) {
3166 $value = trim( $frame->expand( $bits[
'value'] ) );
3175 if ( $specialPage && $specialPage->maxIncludeCacheTime() === 0 ) {
3176 $context->setUser( $this->getUser() );
3179 $context->setUser( User::newFromName(
'127.0.0.1',
false ) );
3181 $context->setLanguage( $this->mOptions->getUserLangObj() );
3183 $title,
$context, $this->getLinkRenderer() );
3185 $text =
$context->getOutput()->getHTML();
3186 $this->mOutput->addOutputPageMetadata(
$context->getOutput() );
3189 if ( $specialPage && $specialPage->maxIncludeCacheTime() !==
false ) {
3190 $this->mOutput->updateRuntimeAdaptiveExpiry(
3191 $specialPage->maxIncludeCacheTime()
3195 } elseif ( MWNamespace::isNonincludable( $title->getNamespace() ) ) {
3196 $found =
false; # access denied
3197 wfDebug( __METHOD__ .
": template inclusion denied for " .
3198 $title->getPrefixedDBkey() .
"\n" );
3200 list( $text, $title ) = $this->getTemplateDom( $title );
3201 if ( $text !==
false ) {
3207 # If the title is valid but undisplayable, make a link to it
3208 if ( !$found && ( $this->ot[
'html'] || $this->ot[
'pre'] ) ) {
3209 $text =
"[[:$titleText]]";
3212 } elseif ( $title->isTrans() ) {
3213 # Interwiki transclusion
3214 if ( $this->ot[
'html'] && !$forceRawInterwiki ) {
3215 $text = $this->interwikiTransclude( $title,
'render' );
3218 $text = $this->interwikiTransclude( $title,
'raw' );
3219 # Preprocess it like a template
3220 $text = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
3226 # Do infinite loop check
3227 # This has to be done after redirect resolution to avoid infinite loops via redirects
3228 if ( !$frame->loopCheck( $title ) ) {
3230 $text =
'<span class="error">'
3231 .
wfMessage(
'parser-template-loop-warning', $titleText )->inContentLanguage()->text()
3233 $this->addTrackingCategory(
'template-loop-category' );
3234 $this->mOutput->addWarning(
wfMessage(
'template-loop-warning',
3236 wfDebug( __METHOD__ .
": template loop broken at '$titleText'\n" );
3240 # If we haven't found text to substitute by now, we're done
3241 # Recover the source wikitext and return it
3243 $text = $frame->virtualBracketedImplode(
'{{',
'|',
'}}', $titleWithSpaces,
$args );
3244 if ( $profileSection ) {
3245 $this->mProfiler->scopedProfileOut( $profileSection );
3247 return [
'object' => $text ];
3250 # Expand DOM-style return values in a child frame
3251 if ( $isChildObj ) {
3252 # Clean up argument array
3253 $newFrame = $frame->newChild(
$args, $title );
3257 } elseif ( $titleText !==
false && $newFrame->isEmpty() ) {
3258 # Expansion is eligible for the empty-frame cache
3259 $text = $newFrame->cachedExpand( $titleText, $text );
3261 # Uncached expansion
3262 $text = $newFrame->expand( $text );
3265 if ( $isLocalObj && $nowiki ) {
3267 $isLocalObj =
false;
3270 if ( $profileSection ) {
3271 $this->mProfiler->scopedProfileOut( $profileSection );
3274 # Replace raw HTML by a placeholder
3276 $text = $this->insertStripItem( $text );
3277 } elseif ( $nowiki && ( $this->ot[
'html'] || $this->ot[
'pre'] ) ) {
3278 # Escape nowiki-style return values
3280 } elseif ( is_string( $text )
3281 && !$piece[
'lineStart']
3282 && preg_match(
'/^(?:{\\||:|;|#|\*)/', $text )
3284 # T2529: if the template begins with a table or block-level
3285 # element, it should be treated as beginning a new line.
3286 # This behavior is somewhat controversial.
3287 $text =
"\n" . $text;
3290 if ( is_string( $text ) && !$this->incrementIncludeSize(
'post-expand', strlen( $text ) ) ) {
3291 # Error, oversize inclusion
3292 if ( $titleText !==
false ) {
3293 # Make a working, properly escaped link if possible (T25588)
3294 $text =
"[[:$titleText]]";
3296 # This will probably not be a working link, but at least it may
3297 # provide some hint of where the problem is
3298 preg_replace(
'/^:/',
'', $originalTitle );
3299 $text =
"[[:$originalTitle]]";
3301 $text .= $this->insertStripItem(
'<!-- WARNING: template omitted, '
3302 .
'post-expand include size too large -->' );
3303 $this->limitationWarn(
'post-expand-template-inclusion' );
3306 if ( $isLocalObj ) {
3307 $ret = [
'object' => $text ];
3309 $ret = [
'text' => $text ];
3337 # Case sensitive functions
3338 if ( isset( $this->mFunctionSynonyms[1][$function] ) ) {
3339 $function = $this->mFunctionSynonyms[1][$function];
3341 # Case insensitive functions
3343 if ( isset( $this->mFunctionSynonyms[0][$function] ) ) {
3344 $function = $this->mFunctionSynonyms[0][$function];
3346 return [
'found' =>
false ];
3350 list( $callback,
$flags ) = $this->mFunctionHooks[$function];
3356 if (
$flags & self::SFH_OBJECT_ARGS ) {
3357 # Convert arguments to PPNodes and collect for appending to $allArgs
3359 foreach (
$args as $k => $v ) {
3360 if ( $v instanceof
PPNode || $k === 0 ) {
3363 $funcArgs[] = $this->mPreprocessor->newPartNodeArray( [ $k => $v ] )->item( 0 );
3367 # Add a frame parameter, and pass the arguments as an array
3368 $allArgs[] = $frame;
3369 $allArgs[] = $funcArgs;
3371 # Convert arguments to plain text and append to $allArgs
3372 foreach (
$args as $k => $v ) {
3373 if ( $v instanceof
PPNode ) {
3374 $allArgs[] = trim( $frame->expand( $v ) );
3375 } elseif ( is_int( $k ) && $k >= 0 ) {
3376 $allArgs[] = trim( $v );
3378 $allArgs[] = trim(
"$k=$v" );
3383 $result = call_user_func_array( $callback, $allArgs );
3385 # The interface for function hooks allows them to return a wikitext
3386 # string or an array containing the string and any flags. This mungs
3387 # things around to match what this method should return.
3388 if ( !is_array( $result ) ) {
3394 if ( isset( $result[0] ) && !isset( $result[
'text'] ) ) {
3395 $result[
'text'] = $result[0];
3397 unset( $result[0] );
3404 $preprocessFlags = 0;
3405 if ( isset( $result[
'noparse'] ) ) {
3406 $noparse = $result[
'noparse'];
3408 if ( isset( $result[
'preprocessFlags'] ) ) {
3409 $preprocessFlags = $result[
'preprocessFlags'];
3413 $result[
'text'] = $this->preprocessToDom( $result[
'text'], $preprocessFlags );
3414 $result[
'isChildObj'] =
true;
3430 $titleText = $title->getPrefixedDBkey();
3432 if ( isset( $this->mTplRedirCache[$titleText] ) ) {
3433 list( $ns, $dbk ) = $this->mTplRedirCache[$titleText];
3434 $title = Title::makeTitle( $ns, $dbk );
3435 $titleText = $title->getPrefixedDBkey();
3437 if ( isset( $this->mTplDomCache[$titleText] ) ) {
3438 return [ $this->mTplDomCache[$titleText],
$title ];
3441 # Cache miss, go to the database
3442 list( $text, $title ) = $this->fetchTemplateAndTitle( $title );
3444 if ( $text ===
false ) {
3445 $this->mTplDomCache[$titleText] =
false;
3446 return [
false,
$title ];
3449 $dom = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
3450 $this->mTplDomCache[$titleText] = $dom;
3452 if ( !$title->equals( $cacheTitle ) ) {
3453 $this->mTplRedirCache[$cacheTitle->getPrefixedDBkey()] =
3454 [ $title->getNamespace(), $title->getDBkey() ];
3472 $cacheKey = $title->getPrefixedDBkey();
3473 if ( !$this->currentRevisionCache ) {
3474 $this->currentRevisionCache =
new MapCacheLRU( 100 );
3476 if ( !$this->currentRevisionCache->has( $cacheKey ) ) {
3477 $this->currentRevisionCache->set( $cacheKey,
3479 call_user_func( $this->mOptions->getCurrentRevisionCallback(), $title, $this )
3482 return $this->currentRevisionCache->get( $cacheKey );
3495 $pageId = $title->getArticleID();
3496 $revId = $title->getLatestRevID();
3500 $rev->setTitle( $title );
3513 $templateCb = $this->mOptions->getTemplateCallback();
3514 $stuff = call_user_func( $templateCb, $title, $this );
3516 $text = $stuff[
'text'];
3517 if ( is_string( $stuff[
'text'] ) ) {
3518 $text = strtr( $text,
"\x7f",
"?" );
3520 $finalTitle = isset( $stuff[
'finalTitle'] ) ? $stuff[
'finalTitle'] :
$title;
3521 if ( isset( $stuff[
'deps'] ) ) {
3522 foreach ( $stuff[
'deps'] as $dep ) {
3523 $this->mOutput->addTemplate( $dep[
'title'], $dep[
'page_id'], $dep[
'rev_id'] );
3524 if ( $dep[
'title']->equals( $this->getTitle() ) ) {
3527 $this->mOutput->setFlag(
'vary-revision' );
3531 return [ $text, $finalTitle ];
3540 return $this->fetchTemplateAndTitle( $title )[0];
3553 $text = $skip =
false;
3557 # Loop to fetch the article, with up to 1 redirect
3559 for ( $i = 0; $i < 2 && is_object( $title ); $i++ ) {
3561 # Give extensions a chance to select the revision instead
3562 $id =
false; # Assume current
3563 Hooks::run(
'BeforeParserFetchTemplateAndtitle',
3564 [
$parser, $title, &$skip, &$id ] );
3570 'page_id' => $title->getArticleID(),
3579 $rev =
$parser->fetchCurrentRevisionOfTitle( $title );
3583 $rev_id =
$rev ?
$rev->getId() : 0;
3584 # If there is no current revision, there is no page
3585 if ( $id ===
false && !
$rev ) {
3586 $linkCache = LinkCache::singleton();
3587 $linkCache->addBadLinkObj( $title );
3592 'page_id' => $title->getArticleID(),
3593 'rev_id' => $rev_id ];
3594 if (
$rev && !$title->equals(
$rev->getTitle() ) ) {
3595 # We fetched a rev from a different title; register it too...
3597 'title' =>
$rev->getTitle(),
3598 'page_id' =>
$rev->getPage(),
3599 'rev_id' => $rev_id ];
3603 $content =
$rev->getContent();
3604 $text = $content ? $content->getWikitextForTransclusion() :
null;
3606 Hooks::run(
'ParserFetchTemplate',
3609 if ( $text ===
false || $text ===
null ) {
3613 } elseif ( $title->getNamespace() == NS_MEDIAWIKI ) {
3616 if ( !$message->exists() ) {
3620 $content = $message->content();
3621 $text = $message->plain();
3630 $title = $content->getRedirectTarget();
3634 'finalTitle' => $finalTitle,
3646 return $this->fetchFileAndTitle( $title,
$options )[0];
3657 $file = $this->fetchFileNoRegister( $title,
$options );
3659 $time = $file ? $file->getTimestamp() :
false;
3660 $sha1 = $file ? $file->getSha1() :
false;
3661 # Register the file as a dependency...
3662 $this->mOutput->addImage( $title->getDBkey(),
$time, $sha1 );
3663 if ( $file && !$title->equals( $file->getTitle() ) ) {
3664 # Update fetched file title
3665 $title = $file->getTitle();
3666 $this->mOutput->addImage( $title->getDBkey(),
$time, $sha1 );
3668 return [ $file,
$title ];
3682 if ( isset(
$options[
'broken'] ) ) {
3684 } elseif ( isset(
$options[
'sha1'] ) ) {
3704 return wfMessage(
'scarytranscludedisabled' )->inContentLanguage()->text();
3707 $url = $title->getFullURL( [
'action' => $action ] );
3709 if ( strlen( $url ) > 255 ) {
3710 return wfMessage(
'scarytranscludetoolong' )->inContentLanguage()->text();
3712 return $this->fetchScaryTemplateMaybeFromCache( $url );
3723 $obj =
$dbr->selectRow(
'transcache', [
'tc_time',
'tc_contents' ],
3724 [
'tc_url' => $url,
"tc_time >= " .
$dbr->addQuotes( $tsCond ) ] );
3726 return $obj->tc_contents;
3729 $req = MWHttpRequest::factory( $url, [], __METHOD__ );
3732 $text =
$req->getContent();
3733 } elseif (
$req->getStatus() != 200 ) {
3735 return wfMessage(
'scarytranscludefailed-httpstatus' )
3736 ->params( $url,
$req->getStatus() )->inContentLanguage()->text();
3738 return wfMessage(
'scarytranscludefailed', $url )->inContentLanguage()->text();
3742 $dbw->replace(
'transcache', [
'tc_url' ], [
3744 'tc_time' => $dbw->timestamp( time() ),
3745 'tc_contents' => $text
3761 $parts = $piece[
'parts'];
3762 $nameWithSpaces = $frame->expand( $piece[
'title'] );
3763 $argName = trim( $nameWithSpaces );
3765 $text = $frame->getArgument( $argName );
3766 if ( $text ===
false && $parts->getLength() > 0
3767 && ( $this->ot[
'html']
3769 || ( $this->ot[
'wiki'] && $frame->isTemplate() )
3772 # No match in frame, use the supplied default
3773 $object = $parts->item( 0 )->getChildren();
3775 if ( !$this->incrementIncludeSize(
'arg', strlen( $text ) ) ) {
3776 $error =
'<!-- WARNING: argument omitted, expansion size too large -->';
3777 $this->limitationWarn(
'post-expand-template-argument' );
3780 if ( $text ===
false && $object ===
false ) {
3782 $object = $frame->virtualBracketedImplode(
'{{{',
'|',
'}}}', $nameWithSpaces, $parts );
3784 if ( $error !==
false ) {
3787 if ( $object !==
false ) {
3788 $ret = [
'object' => $object ];
3790 $ret = [
'text' => $text ];
3812 static $errorStr =
'<span class="error">';
3813 static $errorLen = 20;
3815 $name = $frame->expand(
$params[
'name'] );
3816 if ( substr( $name, 0, $errorLen ) === $errorStr ) {
3822 $attrText = !isset(
$params[
'attr'] ) ? null : $frame->expand(
$params[
'attr'] );
3823 if ( substr( $attrText, 0, $errorLen ) === $errorStr ) {
3831 $content = !isset(
$params[
'inner'] ) ? null : $frame->expand(
$params[
'inner'] );
3833 $marker = self::MARKER_PREFIX .
"-$name-"
3834 . sprintf(
'%08X', $this->mMarkerIndex++ ) . self::MARKER_SUFFIX;
3836 $isFunctionTag = isset( $this->mFunctionTagHooks[strtolower( $name )] ) &&
3837 ( $this->ot[
'html'] || $this->ot[
'pre'] );
3838 if ( $isFunctionTag ) {
3839 $markerType =
'none';
3841 $markerType =
'general';
3843 if ( $this->ot[
'html'] || $isFunctionTag ) {
3844 $name = strtolower( $name );
3845 $attributes = Sanitizer::decodeTagAttributes( $attrText );
3846 if ( isset(
$params[
'attributes'] ) ) {
3847 $attributes = $attributes +
$params[
'attributes'];
3850 if ( isset( $this->mTagHooks[$name] ) ) {
3851 $output = call_user_func_array( $this->mTagHooks[$name],
3852 [ $content, $attributes, $this, $frame ] );
3853 } elseif ( isset( $this->mFunctionTagHooks[$name] ) ) {
3854 list( $callback, ) = $this->mFunctionTagHooks[
$name];
3858 $output = call_user_func_array( $callback, [ &
$parser, $frame, $content, $attributes ] );
3860 $output =
'<span class="error">Invalid tag extension name: ' .
3861 htmlspecialchars( $name ) .
'</span>';
3865 # Extract flags to local scope (to override $markerType)
3872 if ( is_null( $attrText ) ) {
3875 if ( isset(
$params[
'attributes'] ) ) {
3876 foreach (
$params[
'attributes'] as $attrName => $attrValue ) {
3877 $attrText .=
' ' . htmlspecialchars( $attrName ) .
'="' .
3878 htmlspecialchars( $attrValue ) .
'"';
3881 if ( $content ===
null ) {
3882 $output =
"<$name$attrText/>";
3884 $close = is_null(
$params[
'close'] ) ?
'' : $frame->expand(
$params[
'close'] );
3885 if ( substr( $close, 0, $errorLen ) === $errorStr ) {
3889 $output =
"<$name$attrText>$content$close";
3893 if ( $markerType ===
'none' ) {
3895 } elseif ( $markerType ===
'nowiki' ) {
3896 $this->mStripState->addNoWiki( $marker,
$output );
3897 } elseif ( $markerType ===
'general' ) {
3898 $this->mStripState->addGeneral( $marker,
$output );
3900 throw new MWException( __METHOD__ .
': invalid marker type' );
3913 if ( $this->mIncludeSizes[
$type] + $size > $this->mOptions->getMaxIncludeSize() ) {
3916 $this->mIncludeSizes[
$type] += $size;
3927 $this->mExpensiveFunctionCount++;
3928 return $this->mExpensiveFunctionCount <= $this->mOptions->getExpensiveParserFunctionLimit();
3940 # The position of __TOC__ needs to be recorded
3942 if ( $mw->match( $text ) ) {
3943 $this->mShowToc =
true;
3944 $this->mForceTocPosition =
true;
3946 # Set a placeholder. At the end we'll fill it in with the TOC.
3947 $text = $mw->replace(
'<!--MWTOC-->', $text, 1 );
3949 # Only keep the first one.
3950 $text = $mw->replace(
'', $text );
3953 # Now match and remove the rest of them
3955 $this->mDoubleUnderscores = $mwa->matchAndRemove( $text );
3957 if ( isset( $this->mDoubleUnderscores[
'nogallery'] ) ) {
3958 $this->mOutput->mNoGallery =
true;
3960 if ( isset( $this->mDoubleUnderscores[
'notoc'] ) && !$this->mForceTocPosition ) {
3961 $this->mShowToc =
false;
3963 if ( isset( $this->mDoubleUnderscores[
'hiddencat'] )
3966 $this->addTrackingCategory(
'hidden-category-category' );
3968 # (T10068) Allow control over whether robots index a page.
3969 # __INDEX__ always overrides __NOINDEX__, see T16899
3970 if ( isset( $this->mDoubleUnderscores[
'noindex'] ) && $this->mTitle->canUseNoindex() ) {
3971 $this->mOutput->setIndexPolicy(
'noindex' );
3972 $this->addTrackingCategory(
'noindex-category' );
3974 if ( isset( $this->mDoubleUnderscores[
'index'] ) && $this->mTitle->canUseNoindex() ) {
3975 $this->mOutput->setIndexPolicy(
'index' );
3976 $this->addTrackingCategory(
'index-category' );
3979 # Cache all double underscores in the database
3980 foreach ( $this->mDoubleUnderscores as $key => $val ) {
3981 $this->mOutput->setProperty( $key,
'' );
3993 return $this->mOutput->addTrackingCategory( $msg, $this->mTitle );
4015 # Inhibit editsection links if requested in the page
4016 if ( isset( $this->mDoubleUnderscores[
'noeditsection'] ) ) {
4017 $maybeShowEditLink = $showEditLink =
false;
4019 $maybeShowEditLink =
true;
4020 $showEditLink = $this->mOptions->getEditSection();
4022 if ( $showEditLink ) {
4023 $this->mOutput->setEditSectionTokens(
true );
4026 # Get all headlines for numbering them and adding funky stuff like [edit]
4027 # links - this is for later, but we need the number of headlines right now
4029 $numMatches = preg_match_all(
4030 '/<H(?P<level>[1-6])(?P<attrib>.*?>)\s*(?P<header>[\s\S]*?)\s*<\/H[1-6] *>/i',
4035 # if there are fewer than 4 headlines in the article, do not show TOC
4036 # unless it's been explicitly enabled.
4037 $enoughToc = $this->mShowToc &&
4038 ( ( $numMatches >= 4 ) || $this->mForceTocPosition );
4040 # Allow user to stipulate that a page should have a "new section"
4041 # link added via __NEWSECTIONLINK__
4042 if ( isset( $this->mDoubleUnderscores[
'newsectionlink'] ) ) {
4043 $this->mOutput->setNewSection(
true );
4046 # Allow user to remove the "new section"
4047 # link via __NONEWSECTIONLINK__
4048 if ( isset( $this->mDoubleUnderscores[
'nonewsectionlink'] ) ) {
4049 $this->mOutput->hideNewSection(
true );
4052 # if the string __FORCETOC__ (not case-sensitive) occurs in the HTML,
4053 # override above conditions and always show TOC above first header
4054 if ( isset( $this->mDoubleUnderscores[
'forcetoc'] ) ) {
4055 $this->mShowToc =
true;
4063 # Ugh .. the TOC should have neat indentation levels which can be
4064 # passed to the skin functions. These are determined here
4068 $sublevelCount = [];
4074 $markerRegex = self::MARKER_PREFIX .
"-h-(\d+)-" . self::MARKER_SUFFIX;
4075 $baseTitleText = $this->mTitle->getPrefixedDBkey();
4076 $oldType = $this->mOutputType;
4077 $this->setOutputType( self::OT_WIKI );
4078 $frame = $this->getPreprocessor()->newFrame();
4079 $root = $this->preprocessToDom( $origText );
4080 $node = $root->getFirstChild();
4085 $headlines = $numMatches !==
false ?
$matches[3] : [];
4087 foreach ( $headlines as $headline ) {
4088 $isTemplate =
false;
4090 $sectionIndex =
false;
4092 $markerMatches = [];
4093 if ( preg_match(
"/^$markerRegex/", $headline, $markerMatches ) ) {
4094 $serial = $markerMatches[1];
4095 list( $titleText, $sectionIndex ) = $this->mHeadings[$serial];
4096 $isTemplate = ( $titleText != $baseTitleText );
4097 $headline = preg_replace(
"/^$markerRegex\\s*/",
"", $headline );
4101 $prevlevel = $level;
4103 $level =
$matches[1][$headlineCount];
4105 if ( $level > $prevlevel ) {
4106 # Increase TOC level
4108 $sublevelCount[$toclevel] = 0;
4110 $prevtoclevel = $toclevel;
4114 } elseif ( $level < $prevlevel && $toclevel > 1 ) {
4115 # Decrease TOC level, find level to jump to
4117 for ( $i = $toclevel; $i > 0; $i-- ) {
4118 if ( $levelCount[$i] == $level ) {
4119 # Found last matching level
4122 } elseif ( $levelCount[$i] < $level ) {
4123 # Found first matching level below current level
4133 # Unindent only if the previous toc level was shown :p
4135 $prevtoclevel = $toclevel;
4141 # No change in level, end TOC line
4147 $levelCount[$toclevel] = $level;
4149 # count number of headlines for each level
4150 $sublevelCount[$toclevel]++;
4152 for ( $i = 1; $i <= $toclevel; $i++ ) {
4153 if ( !empty( $sublevelCount[$i] ) ) {
4157 $numbering .= $this->getTargetLanguage()->formatNum( $sublevelCount[$i] );
4162 # The safe header is a version of the header text safe to use for links
4164 # Remove link placeholders by the link text.
4165 # <!--LINK number-->
4167 # link text with suffix
4168 # Do this before unstrip since link text can contain strip markers
4169 $safeHeadline = $this->replaceLinkHoldersText( $headline );
4171 # Avoid insertion of weird stuff like <math> by expanding the relevant sections
4172 $safeHeadline = $this->mStripState->unstripBoth( $safeHeadline );
4174 # Strip out HTML (first regex removes any tag not allowed)
4176 # * <sup> and <sub> (T10393)
4180 # * <span dir="rtl"> and <span dir="ltr"> (T37167)
4181 # * <s> and <strike> (T35715)
4182 # We strip any parameter from accepted tags (second regex), except dir="rtl|ltr" from <span>,
4183 # to allow setting directionality in toc items.
4184 $tocline = preg_replace(
4186 '#<(?!/?(span|sup|sub|bdi|i|b|s|strike)(?: [^>]*)?>).*?>#',
4187 '#<(/?(?:span(?: dir="(?:rtl|ltr)")?|sup|sub|bdi|i|b|s|strike))(?: .*?)?>#'
4193 # Strip '<span></span>', which is the result from the above if
4194 # <span id="foo"></span> is used to produce an additional anchor
4196 $tocline = str_replace(
'<span></span>',
'', $tocline );
4198 $tocline = trim( $tocline );
4200 # For the anchor, strip out HTML-y stuff period
4201 $safeHeadline = preg_replace(
'/<.*?>/',
'', $safeHeadline );
4202 $safeHeadline = Sanitizer::normalizeSectionNameWhitespace( $safeHeadline );
4204 # Save headline for section edit hint before it's escaped
4205 $headlineHint = $safeHeadline;
4207 # Decode HTML entities
4208 $safeHeadline = Sanitizer::decodeCharReferences( $safeHeadline );
4209 $fallbackHeadline = Sanitizer::escapeIdForAttribute( $safeHeadline, Sanitizer::ID_FALLBACK );
4210 $linkAnchor = Sanitizer::escapeIdForLink( $safeHeadline );
4211 $safeHeadline = Sanitizer::escapeIdForAttribute( $safeHeadline, Sanitizer::ID_PRIMARY );
4212 if ( $fallbackHeadline === $safeHeadline ) {
4213 # No reason to have both (in fact, we can't)
4214 $fallbackHeadline =
false;
4217 # HTML IDs must be case-insensitively unique for IE compatibility (T12721).
4218 # @todo FIXME: We may be changing them depending on the current locale.
4219 $arrayKey = strtolower( $safeHeadline );
4220 if ( $fallbackHeadline ===
false ) {
4221 $fallbackArrayKey =
false;
4223 $fallbackArrayKey = strtolower( $fallbackHeadline );
4226 # Create the anchor for linking from the TOC to the section
4227 $anchor = $safeHeadline;
4228 $fallbackAnchor = $fallbackHeadline;
4229 if ( isset( $refers[$arrayKey] ) ) {
4231 for ( $i = 2; isset( $refers[
"${arrayKey}_$i"] ); ++$i );
4234 $linkAnchor .=
"_$i";
4235 $refers[
"${arrayKey}_$i"] =
true;
4237 $refers[$arrayKey] =
true;
4239 if ( $fallbackHeadline !==
false && isset( $refers[$fallbackArrayKey] ) ) {
4241 for ( $i = 2; isset( $refers[
"${fallbackArrayKey}_$i"] ); ++$i );
4243 $fallbackAnchor .=
"_$i";
4244 $refers[
"${fallbackArrayKey}_$i"] =
true;
4246 $refers[$fallbackArrayKey] =
true;
4249 # Don't number the heading if it is the only one (looks silly)
4250 if ( count(
$matches[3] ) > 1 && $this->mOptions->getNumberHeadings() ) {
4251 # the two are different if the line contains a link
4252 $headline = Html::element(
4254 [
'class' =>
'mw-headline-number' ],
4256 ) .
' ' . $headline;
4261 $numbering, $toclevel, ( $isTemplate ?
false : $sectionIndex ) );
4264 # Add the section to the section tree
4265 # Find the DOM node for this header
4266 $noOffset = ( $isTemplate || $sectionIndex ===
false );
4267 while ( $node && !$noOffset ) {
4268 if ( $node->getName() ===
'h' ) {
4269 $bits = $node->splitHeading();
4270 if ( $bits[
'i'] == $sectionIndex ) {
4274 $byteOffset += mb_strlen( $this->mStripState->unstripBoth(
4276 $node = $node->getNextSibling();
4279 'toclevel' => $toclevel,
4282 'number' => $numbering,
4283 'index' => ( $isTemplate ?
'T-' :
'' ) . $sectionIndex,
4284 'fromtitle' => $titleText,
4285 'byteoffset' => ( $noOffset ?
null : $byteOffset ),
4286 'anchor' => $anchor,
4289 # give headline the correct <h#> tag
4290 if ( $maybeShowEditLink && $sectionIndex !==
false ) {
4292 if ( $isTemplate ) {
4293 # Put a T flag in the section identifier, to indicate to extractSections()
4294 # that sections inside <includeonly> should be counted.
4295 $editsectionPage = $titleText;
4296 $editsectionSection =
"T-$sectionIndex";
4297 $editsectionContent =
null;
4299 $editsectionPage = $this->mTitle->getPrefixedText();
4300 $editsectionSection = $sectionIndex;
4301 $editsectionContent = $headlineHint;
4315 $editlink =
'<mw:editsection page="' . htmlspecialchars( $editsectionPage );
4316 $editlink .=
'" section="' . htmlspecialchars( $editsectionSection ) .
'"';
4317 if ( $editsectionContent !==
null ) {
4318 $editlink .=
'>' . $editsectionContent .
'</mw:editsection>';
4326 $matches[
'attrib'][$headlineCount], $anchor, $headline,
4327 $editlink, $fallbackAnchor );
4332 $this->setOutputType( $oldType );
4334 # Never ever show TOC if no headers
4335 if ( $numVisible < 1 ) {
4344 $this->mOutput->setTOCHTML( $toc );
4345 $toc = self::TOC_START . $toc . self::TOC_END;
4349 $this->mOutput->setSections( $tocraw );
4352 # split up and insert constructed headlines
4353 $blocks = preg_split(
'/<H[1-6].*?>[\s\S]*?<\/H[1-6]>/i', $text );
4358 foreach ( $blocks as $block ) {
4360 if ( empty( $head[$i - 1] ) ) {
4361 $sections[$i] = $block;
4363 $sections[$i] = $head[$i - 1] . $block;
4376 Hooks::run(
'ParserSectionCreate', [ $this, $i, &$sections[$i], $showEditLink ] );
4381 if ( $enoughToc && $isMain && !$this->mForceTocPosition ) {
4384 $sections[0] = $sections[0] . $toc .
"\n";
4387 $full .= implode(
'', $sections );
4389 if ( $this->mForceTocPosition ) {
4390 return str_replace(
'<!--MWTOC-->', $toc, $full );
4410 if ( $clearState ) {
4411 $magicScopeVariable = $this->lock();
4413 $this->startParse( $title,
$options, self::OT_WIKI, $clearState );
4414 $this->setUser( $user );
4417 $text = str_replace(
"\000",
'', $text );
4424 if (
$options->getPreSaveTransform() ) {
4425 $text = $this->pstPass2( $text, $user );
4427 $text = $this->mStripState->unstripBoth( $text );
4429 $this->setUser(
null ); # Reset
4445 # Note: This is the timestamp saved as hardcoded wikitext to
4446 # the database, we use $wgContLang here in order to give
4447 # everyone the same signature and use the default one rather
4448 # than the one selected in each user's preferences.
4450 $ts = $this->mOptions->getTimestamp();
4452 $ts = $timestamp->format(
'YmdHis' );
4453 $tzMsg = $timestamp->getTimezoneMessage()->inContentLanguage()->text();
4455 $d =
$wgContLang->timeanddate( $ts,
false,
false ) .
" ($tzMsg)";
4457 # Variable replacement
4458 # Because mOutputType is OT_WIKI, this will only process {{subst:xxx}} type tags
4459 $text = $this->replaceVariables( $text );
4461 # This works almost by chance, as the replaceVariables are done before the getUserSig(),
4462 # which may corrupt this parser instance via its wfMessage()->text() call-
4465 if ( strpos( $text,
'~~~' ) !==
false ) {
4466 $sigText = $this->getUserSig( $user );
4467 $text = strtr( $text, [
4469 '~~~~' =>
"$sigText $d",
4472 # The main two signature forms used above are time-sensitive
4473 $this->mOutput->setFlag(
'user-signature' );
4476 # Context links ("pipe tricks"): [[|name]] and [[name (context)|]]
4477 $tc =
'[' . Title::legalChars() .
']';
4478 $nc =
'[ _0-9A-Za-z\x80-\xff-]'; # Namespaces can use non-ascii!
4481 $p1 =
"/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\))\\|]]/";
4483 $p4 =
"/\[\[(:?$nc+:|:|)($tc+?)( ?($tc+))\\|]]/";
4485 $p3 =
"/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\)|)((?:, |,)$tc+|)\\|]]/";
4487 $p2 =
"/\[\[\\|($tc+)]]/";
4489 # try $p1 first, to turn "[[A, B (C)|]]" into "[[A, B (C)|A, B]]"
4490 $text = preg_replace( $p1,
'[[\\1\\2\\3|\\2]]', $text );
4491 $text = preg_replace( $p4,
'[[\\1\\2\\3|\\2]]', $text );
4492 $text = preg_replace( $p3,
'[[\\1\\2\\3\\4|\\2]]', $text );
4494 $t = $this->mTitle->getText();
4496 if ( preg_match(
"/^($nc+:|)$tc+?( \\($tc+\\))$/",
$t, $m ) ) {
4497 $text = preg_replace( $p2,
"[[$m[1]\\1$m[2]|\\1]]", $text );
4498 } elseif ( preg_match(
"/^($nc+:|)$tc+?(, $tc+|)$/",
$t, $m ) &&
"$m[1]$m[2]" !=
'' ) {
4499 $text = preg_replace( $p2,
"[[$m[1]\\1$m[2]|\\1]]", $text );
4501 # if there's no context, don't bother duplicating the title
4502 $text = preg_replace( $p2,
'[[\\1]]', $text );
4522 public function getUserSig( &$user, $nickname =
false, $fancySig =
null ) {
4527 # If not given, retrieve from the user object.
4528 if ( $nickname ===
false ) {
4529 $nickname = $user->getOption(
'nickname' );
4532 if ( is_null( $fancySig ) ) {
4533 $fancySig = $user->getBoolOption(
'fancysig' );
4536 $nickname = $nickname ==
null ?
$username : $nickname;
4540 wfDebug( __METHOD__ .
": $username has overlong signature.\n" );
4541 } elseif ( $fancySig !==
false ) {
4542 # Sig. might contain markup; validate this
4543 if ( $this->validateSig( $nickname ) !==
false ) {
4544 # Validated; clean up (if needed) and return it
4545 return $this->cleanSig( $nickname,
true );
4547 # Failed to validate; fall back to the default
4549 wfDebug( __METHOD__ .
": $username has bad XML tags in signature.\n" );
4553 # Make sure nickname doesnt get a sig in a sig
4554 $nickname = self::cleanSigInSig( $nickname );
4556 # If we're still here, make it a link to the user page
4559 $msgName = $user->isAnon() ?
'signature-anon' :
'signature';
4561 return wfMessage( $msgName, $userText, $nickText )->inContentLanguage()
4562 ->title( $this->getTitle() )->text();
4572 return Xml::isWellFormedXmlFragment( $text ) ? $text :
false;
4588 $magicScopeVariable = $this->lock();
4592 # Option to disable this feature
4593 if ( !$this->mOptions->getCleanSignatures() ) {
4597 # @todo FIXME: Regex doesn't respect extension tags or nowiki
4598 # => Move this logic to braceSubstitution()
4600 $substRegex =
'/\{\{(?!(?:' . $substWord->getBaseRegex() .
'))/x' . $substWord->getRegexCase();
4601 $substText =
'{{' . $substWord->getSynonym( 0 );
4603 $text = preg_replace( $substRegex, $substText, $text );
4604 $text = self::cleanSigInSig( $text );
4605 $dom = $this->preprocessToDom( $text );
4606 $frame = $this->getPreprocessor()->newFrame();
4607 $text = $frame->expand( $dom );
4610 $text = $this->mStripState->unstripBoth( $text );
4623 $text = preg_replace(
'/~{3,5}/',
'', $text );
4637 $outputType, $clearState =
true
4639 $this->startParse( $title,
$options, $outputType, $clearState );
4649 $outputType, $clearState =
true
4651 $this->setTitle( $title );
4653 $this->setOutputType( $outputType );
4654 if ( $clearState ) {
4655 $this->clearState();
4668 static $executing =
false;
4670 # Guard against infinite recursion
4681 $text = $this->preprocess( $text, $title,
$options );
4711 public function setHook( $tag, callable $callback ) {
4712 $tag = strtolower( $tag );
4713 if ( preg_match(
'/[<>\r\n]/', $tag, $m ) ) {
4714 throw new MWException(
"Invalid character {$m[0]} in setHook('$tag', ...) call" );
4716 $oldVal = isset( $this->mTagHooks[$tag] ) ? $this->mTagHooks[$tag] :
null;
4717 $this->mTagHooks[$tag] = $callback;
4718 if ( !in_array( $tag, $this->mStripList ) ) {
4719 $this->mStripList[] = $tag;
4743 $tag = strtolower( $tag );
4744 if ( preg_match(
'/[<>\r\n]/', $tag, $m ) ) {
4745 throw new MWException(
"Invalid character {$m[0]} in setTransparentHook('$tag', ...) call" );
4747 $oldVal = isset( $this->mTransparentTagHooks[$tag] ) ? $this->mTransparentTagHooks[$tag] :
null;
4748 $this->mTransparentTagHooks[$tag] = $callback;
4757 $this->mTagHooks = [];
4758 $this->mFunctionTagHooks = [];
4759 $this->mStripList = $this->mDefaultStripList;
4808 $oldVal = isset( $this->mFunctionHooks[$id] ) ? $this->mFunctionHooks[$id][0] :
null;
4809 $this->mFunctionHooks[$id] = [ $callback,
$flags ];
4811 # Add to function cache
4814 throw new MWException( __METHOD__ .
'() expecting a magic word identifier.' );
4817 $synonyms = $mw->getSynonyms();
4818 $sensitive = intval( $mw->isCaseSensitive() );
4820 foreach ( $synonyms as $syn ) {
4822 if ( !$sensitive ) {
4826 if ( !(
$flags & self::SFH_NO_HASH ) ) {
4829 # Remove trailing colon
4830 if ( substr( $syn, -1, 1 ) ===
':' ) {
4831 $syn = substr( $syn, 0, -1 );
4833 $this->mFunctionSynonyms[$sensitive][$syn] = $id;
4844 return array_keys( $this->mFunctionHooks );
4858 $tag = strtolower( $tag );
4859 if ( preg_match(
'/[<>\r\n]/', $tag, $m ) ) {
4860 throw new MWException(
"Invalid character {$m[0]} in setFunctionTagHook('$tag', ...) call" );
4862 $old = isset( $this->mFunctionTagHooks[$tag] ) ?
4863 $this->mFunctionTagHooks[$tag] :
null;
4864 $this->mFunctionTagHooks[$tag] = [ $callback,
$flags ];
4866 if ( !in_array( $tag, $this->mStripList ) ) {
4867 $this->mStripList[] = $tag;
4881 $this->mLinkHolders->replace( $text );
4892 return $this->mLinkHolders->replaceText( $text );
4910 if ( isset(
$params[
'mode'] ) ) {
4916 }
catch ( Exception
$e ) {
4921 $ig->setContextTitle( $this->mTitle );
4922 $ig->setShowBytes(
false );
4923 $ig->setShowDimensions(
false );
4924 $ig->setShowFilename(
false );
4925 $ig->setParser( $this );
4926 $ig->setHideBadImages();
4927 $ig->setAttributes( Sanitizer::validateTagAttributes(
$params,
'ul' ) );
4929 if ( isset(
$params[
'showfilename'] ) ) {
4930 $ig->setShowFilename(
true );
4932 $ig->setShowFilename(
false );
4934 if ( isset(
$params[
'caption'] ) ) {
4935 $caption =
$params[
'caption'];
4936 $caption = htmlspecialchars( $caption );
4937 $caption = $this->replaceInternalLinks( $caption );
4938 $ig->setCaptionHtml( $caption );
4940 if ( isset(
$params[
'perrow'] ) ) {
4941 $ig->setPerRow(
$params[
'perrow'] );
4943 if ( isset(
$params[
'widths'] ) ) {
4944 $ig->setWidths(
$params[
'widths'] );
4946 if ( isset(
$params[
'heights'] ) ) {
4947 $ig->setHeights(
$params[
'heights'] );
4949 $ig->setAdditionalOptions(
$params );
4953 Hooks::run(
'BeforeParserrenderImageGallery', [ &
$parser, &$ig ] );
4957 # match lines like these:
4958 # Image:someimage.jpg|This is some image
4966 if ( strpos(
$matches[0],
'%' ) !==
false ) {
4970 if ( is_null( $title ) ) {
4971 # Bogus title. Ignore these so we don't bomb out later.
4975 # We need to get what handler the file uses, to figure out parameters.
4976 # Note, a hook can overide the file name, and chose an entirely different
4977 # file (which potentially could be of a different type and have different handler).
4980 Hooks::run(
'BeforeParserFetchFileAndTitle',
4981 [ $this, $title, &
$options, &$descQuery ] );
4982 # Don't register it now, as TraditionalImageGallery does that later.
4983 $file = $this->fetchFileNoRegister( $title,
$options );
4984 $handler = $file ? $file->getHandler() :
false;
4987 'img_alt' =>
'gallery-internal-alt',
4988 'img_link' =>
'gallery-internal-link',
4991 $paramMap = $paramMap +
$handler->getParamMap();
4994 unset( $paramMap[
'img_width'] );
5002 $handlerOptions = [];
5016 foreach ( $parameterMatches as $parameterMatch ) {
5017 list( $magicName, $match ) = $mwArray->matchVariableStartToEnd( $parameterMatch );
5019 $paramName = $paramMap[$magicName];
5021 switch ( $paramName ) {
5022 case 'gallery-internal-alt':
5023 $alt = $this->stripAltText( $match,
false );
5025 case 'gallery-internal-link':
5026 $linkValue = strip_tags( $this->replaceLinkHoldersText( $match ) );
5027 $chars = self::EXT_LINK_URL_CLASS;
5028 $addr = self::EXT_LINK_ADDR;
5029 $prots = $this->mUrlProtocols;
5031 if ( preg_match(
'/^-{R|(.*)}-$/', $linkValue ) ) {
5034 $linkValue = substr( $linkValue, 4, -2 );
5036 if ( preg_match(
"/^($prots)$addr$chars*$/u", $linkValue ) ) {
5038 $this->mOutput->addExternalLink(
$link );
5040 $localLinkTitle = Title::newFromText( $linkValue );
5041 if ( $localLinkTitle !==
null ) {
5042 $this->mOutput->addLink( $localLinkTitle );
5043 $link = $localLinkTitle->getLinkURL();
5049 if (
$handler->validateParam( $paramName, $match ) ) {
5050 $handlerOptions[$paramName] = $match;
5053 wfDebug(
"$parameterMatch failed parameter validation\n" );
5054 $label =
'|' . $parameterMatch;
5060 $label =
'|' . $parameterMatch;
5064 $label = substr( $label, 1 );
5067 $ig->add( $title, $label, $alt,
$link, $handlerOptions );
5069 $html = $ig->toHTML();
5070 Hooks::run(
'AfterParserFetchFileAndTitle', [ $this, $ig, &
$html ] );
5080 $handlerClass = get_class(
$handler );
5084 if ( !isset( $this->mImageParams[$handlerClass] ) ) {
5085 # Initialise static lists
5086 static $internalParamNames = [
5087 'horizAlign' => [
'left',
'right',
'center',
'none' ],
5088 'vertAlign' => [
'baseline',
'sub',
'super',
'top',
'text-top',
'middle',
5089 'bottom',
'text-bottom' ],
5090 'frame' => [
'thumbnail',
'manualthumb',
'framed',
'frameless',
5091 'upright',
'border',
'link',
'alt',
'class' ],
5093 static $internalParamMap;
5094 if ( !$internalParamMap ) {
5095 $internalParamMap = [];
5096 foreach ( $internalParamNames as
$type => $names ) {
5097 foreach ( $names as $name ) {
5103 $magicName = str_replace(
'-',
'_',
"img_$name" );
5104 $internalParamMap[$magicName] = [
$type,
$name ];
5109 # Add handler params
5110 $paramMap = $internalParamMap;
5112 $handlerParamMap =
$handler->getParamMap();
5113 foreach ( $handlerParamMap as $magic => $paramName ) {
5114 $paramMap[$magic] = [
'handler', $paramName ];
5117 $this->mImageParams[$handlerClass] = $paramMap;
5118 $this->mImageParamsMagicArray[$handlerClass] =
new MagicWordArray( array_keys( $paramMap ) );
5120 return [ $this->mImageParams[$handlerClass], $this->mImageParamsMagicArray[$handlerClass] ];
5132 # Check if the options text is of the form "options|alt text"
5134 # * thumbnail make a thumbnail with enlarge-icon and caption, alignment depends on lang
5135 # * left no resizing, just left align. label is used for alt= only
5136 # * right same, but right aligned
5137 # * none same, but not aligned
5138 # * ___px scale to ___ pixels width, no aligning. e.g. use in taxobox
5139 # * center center the image
5140 # * frame Keep original image size, no magnify-button.
5141 # * framed Same as "frame"
5142 # * frameless like 'thumb' but without a frame. Keeps user preferences for width
5143 # * upright reduce width for upright images, rounded to full __0 px
5144 # * border draw a 1px border around the image
5145 # * alt Text for HTML alt attribute (defaults to empty)
5146 # * class Set a class for img node
5147 # * link Set the target of the image link. Can be external, interwiki, or local
5148 # vertical-align values (no % or length right now):
5158 # Protect LanguageConverter markup when splitting into parts
5163 # Give extensions a chance to select the file revision for us
5166 Hooks::run(
'BeforeParserFetchFileAndTitle',
5167 [ $this, $title, &
$options, &$descQuery ] );
5168 # Fetch and register the file (file title may be different via hooks)
5169 list( $file, $title ) = $this->fetchFileAndTitle( $title,
$options );
5172 $handler = $file ? $file->getHandler() :
false;
5174 list( $paramMap, $mwArray ) = $this->getImageParams(
$handler );
5177 $this->addTrackingCategory(
'broken-file-category' );
5180 # Process the input parameters
5182 $params = [
'frame' => [],
'handler' => [],
5183 'horizAlign' => [],
'vertAlign' => [] ];
5184 $seenformat =
false;
5185 foreach ( $parts as $part ) {
5186 $part = trim( $part );
5187 list( $magicName,
$value ) = $mwArray->matchVariableStartToEnd( $part );
5189 if ( isset( $paramMap[$magicName] ) ) {
5190 list(
$type, $paramName ) = $paramMap[$magicName];
5192 # Special case; width and height come in one variable together
5193 if (
$type ===
'handler' && $paramName ===
'width' ) {
5194 $parsedWidthParam = $this->parseWidthParam(
$value );
5195 if ( isset( $parsedWidthParam[
'width'] ) ) {
5196 $width = $parsedWidthParam[
'width'];
5197 if (
$handler->validateParam(
'width', $width ) ) {
5202 if ( isset( $parsedWidthParam[
'height'] ) ) {
5203 $height = $parsedWidthParam[
'height'];
5204 if (
$handler->validateParam(
'height', $height ) ) {
5209 # else no validation -- T15436
5211 if (
$type ===
'handler' ) {
5212 # Validate handler parameter
5215 # Validate internal parameters
5216 switch ( $paramName ) {
5220 # @todo FIXME: Possibly check validity here for
5221 # manualthumb? downstream behavior seems odd with
5222 # missing manual thumbs.
5227 $chars = self::EXT_LINK_URL_CLASS;
5228 $addr = self::EXT_LINK_ADDR;
5229 $prots = $this->mUrlProtocols;
5231 $paramName =
'no-link';
5234 } elseif ( preg_match(
"/^((?i)$prots)/",
$value ) ) {
5235 if ( preg_match(
"/^((?i)$prots)$addr$chars*$/u",
$value, $m ) ) {
5236 $paramName =
'link-url';
5237 $this->mOutput->addExternalLink(
$value );
5238 if ( $this->mOptions->getExternalLinkTarget() ) {
5239 $params[
$type][
'link-target'] = $this->mOptions->getExternalLinkTarget();
5244 $linkTitle = Title::newFromText(
$value );
5246 $paramName =
'link-title';
5248 $this->mOutput->addLink( $linkTitle );
5257 $validated = !$seenformat;
5261 # Most other things appear to be empty or numeric...
5262 $validated = (
$value ===
false || is_numeric( trim(
$value ) ) );
5271 if ( !$validated ) {
5276 # Process alignment parameters
5277 if (
$params[
'horizAlign'] ) {
5284 $params[
'frame'][
'caption'] = $caption;
5286 # Will the image be presented in a frame, with the caption below?
5287 $imageIsFramed = isset(
$params[
'frame'][
'frame'] )
5288 || isset(
$params[
'frame'][
'framed'] )
5289 || isset(
$params[
'frame'][
'thumbnail'] )
5290 || isset(
$params[
'frame'][
'manualthumb'] );
5292 # In the old days, [[Image:Foo|text...]] would set alt text. Later it
5293 # came to also set the caption, ordinary text after the image -- which
5294 # makes no sense, because that just repeats the text multiple times in
5295 # screen readers. It *also* came to set the title attribute.
5296 # Now that we have an alt attribute, we should not set the alt text to
5297 # equal the caption: that's worse than useless, it just repeats the
5298 # text. This is the framed/thumbnail case. If there's no caption, we
5299 # use the unnamed parameter for alt text as well, just for the time be-
5300 # ing, if the unnamed param is set and the alt param is not.
5301 # For the future, we need to figure out if we want to tweak this more,
5302 # e.g., introducing a title= parameter for the title; ignoring the un-
5303 # named parameter entirely for images without a caption; adding an ex-
5304 # plicit caption= parameter and preserving the old magic unnamed para-
5306 if ( $imageIsFramed ) { # Framed image
5307 if ( $caption ===
'' && !isset(
$params[
'frame'][
'alt'] ) ) {
5308 # No caption or alt text, add the filename as the alt text so
5309 # that screen readers at least get some description of the image
5310 $params[
'frame'][
'alt'] = $title->getText();
5312 # Do not set $params['frame']['title'] because tooltips don't make sense
5314 }
else { # Inline image
5315 if ( !isset(
$params[
'frame'][
'alt'] ) ) {
5316 # No alt text, use the "caption" for the alt text
5317 if ( $caption !==
'' ) {
5318 $params[
'frame'][
'alt'] = $this->stripAltText( $caption, $holders );
5320 # No caption, fall back to using the filename for the
5322 $params[
'frame'][
'alt'] = $title->getText();
5325 # Use the "caption" for the tooltip text
5326 $params[
'frame'][
'title'] = $this->stripAltText( $caption, $holders );
5329 Hooks::run(
'ParserMakeImageParams', [ $title, $file, &
$params, $this ] );
5331 # Linker does the rest
5334 $time, $descQuery, $this->mOptions->getThumbSize() );
5336 # Give the handler a chance to modify the parser object
5338 $handler->parserTransformHook( $this, $file );
5350 # Strip bad stuff out of the title (tooltip). We can't just use
5351 # replaceLinkHoldersText() here, because if this function is called
5352 # from replaceInternalLinks2(), mLinkHolders won't be up-to-date.
5354 $tooltip = $holders->replaceText( $caption );
5356 $tooltip = $this->replaceLinkHoldersText( $caption );
5359 # make sure there are no placeholders in thumbnail attributes
5360 # that are later expanded to html- so expand them now and
5362 $tooltip = $this->mStripState->unstripBoth( $tooltip );
5363 $tooltip = Sanitizer::stripAllTags( $tooltip );
5374 wfDebug(
"Parser output marked as uncacheable.\n" );
5375 if ( !$this->mOutput ) {
5377 " can only be called when actually parsing something" );
5379 $this->mOutput->updateCacheExpiry( 0 );
5391 $text = $this->replaceVariables( $text, $frame );
5392 $text = $this->mStripState->unstripBoth( $text );
5403 array_keys( $this->mTransparentTagHooks ),
5404 array_keys( $this->mTagHooks ),
5405 array_keys( $this->mFunctionTagHooks )
5421 $elements = array_keys( $this->mTransparentTagHooks );
5422 $text = self::extractTagsAndParams( $elements, $text,
$matches );
5425 foreach (
$matches as $marker => $data ) {
5426 list( $element, $content,
$params, $tag ) = $data;
5427 $tagName = strtolower( $element );
5428 if ( isset( $this->mTransparentTagHooks[$tagName] ) ) {
5429 $output = call_user_func_array(
5430 $this->mTransparentTagHooks[$tagName],
5436 $replacements[$marker] =
$output;
5438 return strtr( $text, $replacements );
5471 global
$wgTitle; # not generally used but removes an ugly failure mode
5473 $magicScopeVariable = $this->lock();
5476 $frame = $this->getPreprocessor()->newFrame();
5478 # Process section extraction flags
5480 $sectionParts = explode(
'-', $sectionId );
5481 $sectionIndex = array_pop( $sectionParts );
5482 foreach ( $sectionParts as $part ) {
5483 if ( $part ===
'T' ) {
5484 $flags |= self::PTD_FOR_INCLUSION;
5488 # Check for empty input
5489 if ( strval( $text ) ===
'' ) {
5490 # Only sections 0 and T-0 exist in an empty document
5491 if ( $sectionIndex == 0 ) {
5492 if ( $mode ===
'get' ) {
5498 if ( $mode ===
'get' ) {
5506 # Preprocess the text
5507 $root = $this->preprocessToDom( $text,
$flags );
5509 # <h> nodes indicate section breaks
5510 # They can only occur at the top level, so we can find them by iterating the root's children
5511 $node = $root->getFirstChild();
5513 # Find the target section
5514 if ( $sectionIndex == 0 ) {
5515 # Section zero doesn't nest, level=big
5516 $targetLevel = 1000;
5519 if ( $node->getName() ===
'h' ) {
5520 $bits = $node->splitHeading();
5521 if ( $bits[
'i'] == $sectionIndex ) {
5522 $targetLevel = $bits[
'level'];
5526 if ( $mode ===
'replace' ) {
5529 $node = $node->getNextSibling();
5535 if ( $mode ===
'get' ) {
5542 # Find the end of the section, including nested sections
5544 if ( $node->getName() ===
'h' ) {
5545 $bits = $node->splitHeading();
5546 $curLevel = $bits[
'level'];
5547 if ( $bits[
'i'] != $sectionIndex && $curLevel <= $targetLevel ) {
5551 if ( $mode ===
'get' ) {
5554 $node = $node->getNextSibling();
5557 # Write out the remainder (in replace mode only)
5558 if ( $mode ===
'replace' ) {
5559 # Output the replacement text
5560 # Add two newlines on -- trailing whitespace in $newText is conventionally
5561 # stripped by the editor, so we need both newlines to restore the paragraph gap
5562 # Only add trailing whitespace if there is newText
5563 if ( $newText !=
"" ) {
5564 $outText .= $newText .
"\n\n";
5569 $node = $node->getNextSibling();
5573 if ( is_string( $outText ) ) {
5574 # Re-insert stripped tags
5575 $outText = rtrim( $this->mStripState->unstripBoth( $outText ) );
5595 public function getSection( $text, $sectionId, $defaultText =
'' ) {
5596 return $this->extractSections( $text, $sectionId,
'get', $defaultText );
5612 return $this->extractSections( $oldText, $sectionId,
'replace', $newText );
5621 return $this->mRevisionId;
5631 if ( !is_null( $this->mRevisionObject ) ) {
5632 return $this->mRevisionObject;
5634 if ( is_null( $this->mRevisionId ) ) {
5638 $rev = call_user_func(
5639 $this->mOptions->getCurrentRevisionCallback(), $this->getTitle(), $this
5642 # If the parse is for a new revision, then the callback should have
5643 # already been set to force the object and should match mRevisionId.
5644 # If not, try to fetch by mRevisionId for sanity.
5645 if (
$rev &&
$rev->getId() != $this->mRevisionId ) {
5649 $this->mRevisionObject =
$rev;
5651 return $this->mRevisionObject;
5660 if ( is_null( $this->mRevisionTimestamp ) ) {
5663 $revObject = $this->getRevisionObject();
5664 $timestamp = $revObject ? $revObject->getTimestamp() :
wfTimestampNow();
5666 # The cryptic '' timezone parameter tells to use the site-default
5667 # timezone offset instead of the user settings.
5668 # Since this value will be saved into the parser cache, served
5669 # to other users, and potentially even used inside links and such,
5670 # it needs to be consistent for all visitors.
5671 $this->mRevisionTimestamp =
$wgContLang->userAdjust( $timestamp,
'' );
5674 return $this->mRevisionTimestamp;
5683 if ( is_null( $this->mRevisionUser ) ) {
5684 $revObject = $this->getRevisionObject();
5686 # if this template is subst: the revision id will be blank,
5687 # so just use the current user's name
5689 $this->mRevisionUser = $revObject->getUserText();
5690 } elseif ( $this->ot[
'wiki'] || $this->mOptions->getIsPreview() ) {
5691 $this->mRevisionUser = $this->getUser()->getName();
5694 return $this->mRevisionUser;
5703 if ( is_null( $this->mRevisionSize ) ) {
5704 $revObject = $this->getRevisionObject();
5706 # if this variable is subst: the revision id will be blank,
5707 # so just use the parser input size, because the own substituation
5708 # will change the size.
5710 $this->mRevisionSize = $revObject->getSize();
5712 $this->mRevisionSize = $this->mInputSize;
5715 return $this->mRevisionSize;
5724 $this->mDefaultSort =
$sort;
5725 $this->mOutput->setProperty(
'defaultsort',
$sort );
5739 if ( $this->mDefaultSort !==
false ) {
5740 return $this->mDefaultSort;
5753 return $this->mDefaultSort;
5766 # Strip out wikitext links(they break the anchor)
5767 $text = $this->stripSectionName( $text );
5768 $text = Sanitizer::normalizeSectionNameWhitespace( $text );
5769 $text = Sanitizer::decodeCharReferences( $text );
5770 return '#' . Sanitizer::escapeIdForLink( $text );
5785 # Strip out wikitext links(they break the anchor)
5786 $text = $this->stripSectionName( $text );
5787 $text = Sanitizer::normalizeSectionNameWhitespace( $text );
5788 $text = Sanitizer::decodeCharReferences( $text );
5792 $id = Sanitizer::escapeIdForAttribute( $text, Sanitizer::ID_FALLBACK );
5794 $id = Sanitizer::escapeIdForLink( $text );
5815 # Strip internal link markup
5816 $text = preg_replace(
'/\[\[:?([^[|]+)\|([^[]+)\]\]/',
'$2', $text );
5817 $text = preg_replace(
'/\[\[:?([^[]+)\|?\]\]/',
'$1', $text );
5819 # Strip external link markup
5820 # @todo FIXME: Not tolerant to blank link text
5822 # on how many empty links there are on the page - need to figure that out.
5823 $text = preg_replace(
'/\[(?i:' . $this->mUrlProtocols .
')([^ ]+?) ([^[]+)\]/',
'$2', $text );
5825 # Parse wikitext quotes (italics & bold)
5826 $text = $this->doQuotes( $text );
5844 $outputType = self::OT_HTML
5846 $magicScopeVariable = $this->lock();
5847 $this->startParse( $title,
$options, $outputType,
true );
5849 $text = $this->replaceVariables( $text );
5850 $text = $this->mStripState->unstripBoth( $text );
5851 $text = Sanitizer::removeHTMLtags( $text );
5862 return $this->preSaveTransform( $text, $title,
$options->getUser(),
$options );
5872 return $this->testSrvus( $text, $title,
$options, self::OT_PREPROCESS );
5894 while ( $i < strlen(
$s ) ) {
5895 $markerStart = strpos(
$s, self::MARKER_PREFIX, $i );
5896 if ( $markerStart ===
false ) {
5897 $out .= call_user_func( $callback, substr(
$s, $i ) );
5900 $out .= call_user_func( $callback, substr(
$s, $i, $markerStart - $i ) );
5901 $markerEnd = strpos(
$s, self::MARKER_SUFFIX, $markerStart );
5902 if ( $markerEnd ===
false ) {
5903 $out .= substr(
$s, $markerStart );
5906 $markerEnd += strlen( self::MARKER_SUFFIX );
5907 $out .= substr(
$s, $markerStart, $markerEnd - $markerStart );
5922 return $this->mStripState->killMarkers( $text );
5944 'version' => self::HALF_PARSED_VERSION,
5945 'stripState' => $this->mStripState->getSubState( $text ),
5946 'linkHolders' => $this->mLinkHolders->getSubArray( $text )
5967 if ( !isset( $data[
'version'] ) || $data[
'version'] != self::HALF_PARSED_VERSION ) {
5968 throw new MWException( __METHOD__ .
': invalid version' );
5971 # First, extract the strip state.
5972 $texts = [ $data[
'text'] ];
5973 $texts = $this->mStripState->merge( $data[
'stripState'], $texts );
5975 # Now renumber links
5976 $texts = $this->mLinkHolders->mergeForeign( $data[
'linkHolders'], $texts );
5978 # Should be good to go.
5992 return isset( $data[
'version'] ) && $data[
'version'] == self::HALF_PARSED_VERSION;
6004 $parsedWidthParam = [];
6006 return $parsedWidthParam;
6009 # (T15500) In both cases (width/height and width only),
6010 # permit trailing "px" for backward compatibility.
6011 if ( preg_match(
'/^([0-9]*)x([0-9]*)\s*(?:px)?\s*$/',
$value, $m ) ) {
6012 $width = intval( $m[1] );
6013 $height = intval( $m[2] );
6014 $parsedWidthParam[
'width'] = $width;
6015 $parsedWidthParam[
'height'] = $height;
6016 } elseif ( preg_match(
'/^[0-9]*\s*(?:px)?\s*$/',
$value ) ) {
6017 $width = intval(
$value );
6018 $parsedWidthParam[
'width'] = $width;
6020 return $parsedWidthParam;
6033 if ( $this->mInParse ) {
6034 throw new MWException(
"Parser state cleared while parsing. "
6035 .
"Did you call Parser::parse recursively? Lock is held by: " . $this->mInParse );
6041 $this->mInParse =
$e->getTraceAsString();
6043 $recursiveCheck =
new ScopedCallback(
function () {
6044 $this->mInParse =
false;
6047 return $recursiveCheck;
6062 if ( preg_match(
'/^<p>(.*)\n?<\/p>\n?$/sU',
$html, $m ) ) {
6063 if ( strpos( $m[1],
'</p>' ) ===
false ) {
6083 if ( $this->mInParse ) {
6098 $this->mOutput->setEnableOOUI(
true );
If you want to remove the page from your watchlist later
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
$wgLanguageCode
Site language code.
$wgNoFollowNsExceptions
Namespaces in which $wgNoFollowLinks doesn't apply.
$wgServerName
Server name.
$wgExtraInterlanguageLinkPrefixes
List of additional interwiki prefixes that should be treated as interlanguage links (i....
$wgNoFollowLinks
If true, external URL links in wiki text will be given the rel="nofollow" attribute as a hint to sear...
$wgShowHostnames
Expose backend server host names through the API and various HTML comments.
$wgSitename
Name of the site.
$wgNoFollowDomainExceptions
If this is set to an array of domains, external links to these domain names (or any subdomains) will ...
$wgScriptPath
The path we should point to.
$wgFragmentMode
How should section IDs be encoded? This array can contain 1 or 2 elements, each of them can be one of...
$wgTranscludeCacheExpiry
Expiry time for transcluded templates cached in transcache database table.
$wgParserConf
Parser configuration.
$wgMaxSigChars
Maximum number of Unicode characters in signature.
$wgEnableScaryTranscluding
Enable interwiki transcluding.
$wgStylePath
The URL path of the skins directory.
$wgServer
URL of the server.
$wgMaxTocLevel
Maximum indent level of toc.
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,...
if(! $wgRequest->checkUrlExtension()) if(isset($_SERVER[ 'PATH_INFO']) &&$_SERVER[ 'PATH_INFO'] !='') if(! $wgEnableAPI) $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.
static tocList( $toc, $lang=false)
Wraps the TOC in a table and provides the hide/collapse javascript.
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 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 html tidy.
static getInstance( $ts=false)
Get a timestamp instance in GMT.
static getLocalInstance( $ts=false)
Get a timestamp instance in the server local timezone ($wgLocaltimezone)
Class for handling an array of magic words.
static getCacheTTL( $id)
Allow external reads of TTL array.
static getVariableIDs()
Get an array of parser variable IDs.
static & get( $id)
Factory: creates an object representing an ID.
static getDoubleUnderscoreArray()
Get a MagicWordArray of double-underscore entities.
static getSubstIDs()
Get an array of parser substitution modifier IDs.
Handles a simple LRU key/value map with a maximum number of entries.
static setupOOUI( $skinName='default', $dir='ltr')
Helper function to setup the PHP implementation of OOUI to use in this request.
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.
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.
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.
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)
fetchScaryTemplateMaybeFromCache( $url)
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.
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.
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.
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...
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.
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)
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.
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.
parseWidthParam( $value)
Parsed a width param of imagelink like 300px or 200x300px.
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.
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.
validateSig( $text)
Check that the user's signature contains no bad XML.
getPreprocessor()
Get a preprocessor object.
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)
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, $pageId, $revId)
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 capturePath(Title $title, IContextSource $context, LinkRenderer $linkRenderer=null)
Just like executePath() but will override global variables and execute the page in "inclusion" mode.
static getPage( $name)
Find the object with a given name and return it (or NULL)
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
static getVersion( $flags='', $lang=null)
Return a string of the MediaWiki version with Git revision if available.
static delimiterReplace( $startDelim, $endDelim, $replace, $subject, $flags='')
Perform an operation equivalent to preg_replace() with flags.
static replaceMarkup( $search, $replace, $text)
More or less "markup-safe" str_replace() Ignores any instances of the separator inside <....
static explode( $separator, $subject)
Workalike for explode() with limited memory usage.
static delimiterExplode( $startDelim, $endDelim, $separator, $subject, $nested=false)
Explode a string, but ignore any instances of the separator inside the given start and end delimiters...
static normalizeLineEndings( $text)
Do a "\\r\\n" -> "\\n" and "\\r" -> "\\n" transformation as well as trim trailing whitespace.
Represents a title within MediaWiki.
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
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
I won t presume to tell you how to I m just describing the methods I chose to use for myself If you do choose to follow these it will probably be easier for you to collaborate with others on the but if you want to contribute without by all means do which work well I also use K &R brace matching style I know that s a religious issue for so if you want to use a style that puts opening braces on the next line
this class mediates it Skin Encapsulates a look and feel for the wiki All of the functions that render HTML and make choices about how to render it are here and are called from various other places when and is meant to be subclassed with other skins that may override some of its functions The User object contains a reference to a and so rather than having a global skin object we just rely on the global User and get the skin with $wgUser and also has some character encoding functions and other locale stuff The current user interface language is instantiated as and the local content language as $wgContLang
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add in any and then calling output() to send it all. It could be easily changed to send incrementally if that becomes useful
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add text
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add in any and then calling but I prefer the flexibility This should also do the output encoding The system allocates a global one in $wgOut Title Represents the title of an and does all the work of translating among various forms such as plain database key
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
namespace being checked & $result
do that in ParserLimitReportFormat instead $parser
see documentation in includes Linker php for Linker::makeImageLink & $time
static configuration should be added through ResourceLoaderGetConfigVars instead can be used to get the real title 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 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
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
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 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
it s the revision text itself In either if gzip is the revision text is gzipped $flags
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
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
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub 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
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
this hook is for auditing only or null if authentication failed before getting that far $username
Allows to change the fields on the form that will be generated $name
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. '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). '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
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub 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 $rev
processing should stop and the error should be shown to the user * false
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a local account $user
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
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 For a description of the see design txt $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