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';
103 # Regular expression for a non-newline space
106 # Flags for preprocessToDom
109 # Allowed values for $this->mOutputType
110 # Parameter to startExternalParse().
115 const
OT_PLAIN = 4;
# like extractSections() - portions of the original are returned unchanged.
134 const MARKER_SUFFIX =
"-QINU`\"'\x7f";
137 # Markers used for wrapping the table of contents
155 # Initialised by initialiseVariables()
166 # Initialised in constructor
167 public $mConf, $mExtLinkBracketedRegex, $mUrlProtocols;
169 # Initialized in getPreprocessor()
173 # Cleared with clearState():
204 # These are variables reset at least once per parse regardless of $clearState
217 public $mRevisionObject;
# The revision object of the specified revision ID
229 public $mUniqPrefix = self::MARKER_PREFIX;
250 public $mInParse =
false;
264 $this->mConf = $conf;
266 $this->mExtLinkBracketedRegex =
'/\[(((?i)' . $this->mUrlProtocols .
')' .
267 self::EXT_LINK_ADDR .
268 self::EXT_LINK_URL_CLASS .
'*)\p{Zs}*([^\]\\x00-\\x08\\x0a-\\x1F\\x{FFFD}]*?)\]/Su';
269 if ( isset( $conf[
'preprocessorClass'] ) ) {
270 $this->mPreprocessorClass = $conf[
'preprocessorClass'];
271 } elseif ( defined(
'HPHP_VERSION' ) ) {
272 # Preprocessor_Hash is much faster than Preprocessor_DOM under HipHop
273 $this->mPreprocessorClass = Preprocessor_Hash::class;
274 } elseif ( extension_loaded(
'domxml' ) ) {
275 # PECL extension that conflicts with the core DOM extension (T15770)
276 wfDebug(
"Warning: you have the obsolete domxml extension for PHP. Please remove it!\n" );
277 $this->mPreprocessorClass = Preprocessor_Hash::class;
278 } elseif ( extension_loaded(
'dom' ) ) {
279 $this->mPreprocessorClass = Preprocessor_DOM::class;
281 $this->mPreprocessorClass = Preprocessor_Hash::class;
283 wfDebug( __CLASS__ .
": using preprocessor: {$this->mPreprocessorClass}\n" );
290 if ( isset( $this->mLinkHolders ) ) {
291 unset( $this->mLinkHolders );
294 unset( $this->
$name );
302 $this->mInParse =
false;
310 foreach ( [
'mStripState',
'mVarCache' ]
as $k ) {
318 Hooks::run(
'ParserCloned', [ $this ] );
325 if ( !$this->mFirstCall ) {
328 $this->mFirstCall =
false;
336 Hooks::run(
'ParserFirstCallInit', [ &
$parser ] );
345 if ( $this->mFirstCall ) {
349 $this->mOptions->registerWatcher( [ $this->mOutput,
'recordOption' ] );
350 $this->mAutonumber = 0;
351 $this->mIncludeCount = [];
354 $this->mRevisionObject = $this->mRevisionTimestamp =
355 $this->mRevisionId = $this->mRevisionUser = $this->mRevisionSize =
null;
356 $this->mVarCache = [];
358 $this->mLangLinkLanguages = [];
359 $this->currentRevisionCache =
null;
363 # Clear these on every parse, T6549
364 $this->mTplRedirCache = $this->mTplDomCache = [];
366 $this->mShowToc =
true;
367 $this->mForceTocPosition =
false;
368 $this->mIncludeSizes = [
372 $this->mPPNodeCount = 0;
373 $this->mGeneratedPPNodeCount = 0;
374 $this->mHighestExpansionDepth = 0;
375 $this->mDefaultSort =
false;
376 $this->mHeadings = [];
377 $this->mDoubleUnderscores = [];
378 $this->mExpensiveFunctionCount = 0;
381 if ( isset( $this->mPreprocessor ) && $this->mPreprocessor->parser !== $this ) {
382 $this->mPreprocessor =
null;
389 Hooks::run(
'ParserClearState', [ &
$parser ] );
406 $linestart =
true, $clearState =
true, $revid =
null
411 $text = strtr( $text,
"\x7f",
"?" );
412 $magicScopeVariable = $this->
lock();
415 $text = str_replace(
"\000",
'', $text );
419 $this->currentRevisionCache =
null;
420 $this->mInputSize = strlen( $text );
421 if ( $this->mOptions->getEnableLimitReport() ) {
422 $this->mOutput->resetParseStartTime();
425 $oldRevisionId = $this->mRevisionId;
426 $oldRevisionObject = $this->mRevisionObject;
427 $oldRevisionTimestamp = $this->mRevisionTimestamp;
428 $oldRevisionUser = $this->mRevisionUser;
429 $oldRevisionSize = $this->mRevisionSize;
430 if ( $revid !==
null ) {
431 $this->mRevisionId = $revid;
432 $this->mRevisionObject =
null;
433 $this->mRevisionTimestamp =
null;
434 $this->mRevisionUser =
null;
435 $this->mRevisionSize =
null;
440 Hooks::run(
'ParserBeforeStrip', [ &
$parser, &$text, &$this->mStripState ] );
442 Hooks::run(
'ParserAfterStrip', [ &
$parser, &$text, &$this->mStripState ] );
444 Hooks::run(
'ParserAfterParse', [ &
$parser, &$text, &$this->mStripState ] );
455 if ( !(
$options->getDisableTitleConversion()
456 || isset( $this->mDoubleUnderscores[
'nocontentconvert'] )
457 || isset( $this->mDoubleUnderscores[
'notitleconvert'] )
458 || $this->mOutput->getDisplayTitle() !==
false )
461 if ( $convruletitle ) {
462 $this->mOutput->setTitleText( $convruletitle );
465 $this->mOutput->setTitleText( $titleText );
469 # Compute runtime adaptive expiry if set
470 $this->mOutput->finalizeAdaptiveCacheExpiry();
472 # Warn if too many heavyweight parser functions were used
473 if ( $this->mExpensiveFunctionCount > $this->mOptions->getExpensiveParserFunctionLimit() ) {
475 $this->mExpensiveFunctionCount,
476 $this->mOptions->getExpensiveParserFunctionLimit()
480 # Information on limits, for the benefit of users who try to skirt them
481 if ( $this->mOptions->getEnableLimitReport() ) {
485 # Wrap non-interface parser output in a <div> so it can be targeted
487 $class = $this->mOptions->getWrapOutputClass();
488 if ( $class !==
false && !$this->mOptions->getInterfaceMessage() ) {
489 $text = Html::rawElement(
'div', [
'class' => $class ], $text );
492 $this->mOutput->setText( $text );
494 $this->mRevisionId = $oldRevisionId;
495 $this->mRevisionObject = $oldRevisionObject;
496 $this->mRevisionTimestamp = $oldRevisionTimestamp;
497 $this->mRevisionUser = $oldRevisionUser;
498 $this->mRevisionSize = $oldRevisionSize;
499 $this->mInputSize =
false;
500 $this->currentRevisionCache =
null;
502 return $this->mOutput;
514 $maxIncludeSize = $this->mOptions->getMaxIncludeSize();
516 $cpuTime = $this->mOutput->getTimeSinceStart(
'cpu' );
517 if ( $cpuTime !==
null ) {
518 $this->mOutput->setLimitReportData(
'limitreport-cputime',
519 sprintf(
"%.3f", $cpuTime )
523 $wallTime = $this->mOutput->getTimeSinceStart(
'wall' );
524 $this->mOutput->setLimitReportData(
'limitreport-walltime',
525 sprintf(
"%.3f", $wallTime )
528 $this->mOutput->setLimitReportData(
'limitreport-ppvisitednodes',
529 [ $this->mPPNodeCount, $this->mOptions->getMaxPPNodeCount() ]
531 $this->mOutput->setLimitReportData(
'limitreport-ppgeneratednodes',
532 [ $this->mGeneratedPPNodeCount, $this->mOptions->getMaxGeneratedPPNodeCount() ]
534 $this->mOutput->setLimitReportData(
'limitreport-postexpandincludesize',
535 [ $this->mIncludeSizes[
'post-expand'], $maxIncludeSize ]
537 $this->mOutput->setLimitReportData(
'limitreport-templateargumentsize',
538 [ $this->mIncludeSizes[
'arg'], $maxIncludeSize ]
540 $this->mOutput->setLimitReportData(
'limitreport-expansiondepth',
541 [ $this->mHighestExpansionDepth, $this->mOptions->getMaxPPExpandDepth() ]
543 $this->mOutput->setLimitReportData(
'limitreport-expensivefunctioncount',
544 [ $this->mExpensiveFunctionCount, $this->mOptions->getExpensiveParserFunctionLimit() ]
547 foreach ( $this->mStripState->getLimitReport()
as list( $key,
$value ) ) {
548 $this->mOutput->setLimitReportData( $key,
$value );
551 Hooks::run(
'ParserLimitReportPrepare', [ $this, $this->mOutput ] );
553 $limitReport =
"NewPP limit report\n";
555 $limitReport .=
'Parsed by ' .
wfHostname() .
"\n";
557 $limitReport .=
'Cached time: ' . $this->mOutput->getCacheTime() .
"\n";
558 $limitReport .=
'Cache expiry: ' . $this->mOutput->getCacheExpiry() .
"\n";
559 $limitReport .=
'Dynamic content: ' .
560 ( $this->mOutput->hasDynamicContent() ?
'true' :
'false' ) .
563 foreach ( $this->mOutput->getLimitReportData()
as $key =>
$value ) {
564 if ( Hooks::run(
'ParserLimitReportFormat',
565 [ $key, &
$value, &$limitReport,
false,
false ]
567 $keyMsg =
wfMessage( $key )->inLanguage(
'en' )->useDatabase(
false );
568 $valueMsg =
wfMessage( [
"$key-value-text",
"$key-value" ] )
569 ->inLanguage(
'en' )->useDatabase(
false );
570 if ( !$valueMsg->exists() ) {
573 if ( !$keyMsg->isDisabled() && !$valueMsg->isDisabled() ) {
574 $valueMsg->params(
$value );
575 $limitReport .=
"{$keyMsg->text()}: {$valueMsg->text()}\n";
581 $limitReport = htmlspecialchars_decode( $limitReport );
583 Hooks::run(
'ParserLimitReport', [ $this, &$limitReport ],
'1.22' );
587 $limitReport = str_replace( [
'-',
'&' ], [
'‐',
'&' ], $limitReport );
588 $text =
"\n<!-- \n$limitReport-->\n";
591 $dataByFunc = $this->mProfiler->getFunctionStats();
592 uasort( $dataByFunc,
function ( $a, $b ) {
593 return $a[
'real'] < $b[
'real'];
596 foreach ( array_slice( $dataByFunc, 0, 10 )
as $item ) {
597 $profileReport[] = sprintf(
"%6.2f%% %8.3f %6d %s",
598 $item[
'%real'], $item[
'real'], $item[
'calls'],
599 htmlspecialchars( $item[
'name'] ) );
601 $text .=
"<!--\nTransclusion expansion time report (%,ms,calls,template)\n";
602 $text .= implode(
"\n", $profileReport ) .
"\n-->\n";
604 $this->mOutput->setLimitReportData(
'limitreport-timingprofile', $profileReport );
608 $this->mOutput->setLimitReportData(
'cachereport-origin',
wfHostname() );
610 $this->mOutput->setLimitReportData(
'cachereport-timestamp',
611 $this->mOutput->getCacheTime() );
612 $this->mOutput->setLimitReportData(
'cachereport-ttl',
613 $this->mOutput->getCacheExpiry() );
614 $this->mOutput->setLimitReportData(
'cachereport-transientcontent',
615 $this->mOutput->hasDynamicContent() );
617 if ( $this->mGeneratedPPNodeCount > $this->mOptions->getMaxGeneratedPPNodeCount() / 10 ) {
618 wfDebugLog(
'generated-pp-node-count', $this->mGeneratedPPNodeCount .
' ' .
619 $this->mTitle->getPrefixedDBkey() );
649 Hooks::run(
'ParserBeforeStrip', [ &
$parser, &$text, &$this->mStripState ] );
650 Hooks::run(
'ParserAfterStrip', [ &
$parser, &$text, &$this->mStripState ] );
692 $magicScopeVariable = $this->
lock();
694 if ( $revid !==
null ) {
695 $this->mRevisionId = $revid;
699 Hooks::run(
'ParserBeforeStrip', [ &
$parser, &$text, &$this->mStripState ] );
700 Hooks::run(
'ParserAfterStrip', [ &
$parser, &$text, &$this->mStripState ] );
702 $text = $this->mStripState->unstripBoth( $text );
717 $text = $this->mStripState->unstripBoth( $text );
736 $text = $msg->params(
$params )->plain();
738 # Parser (re)initialisation
739 $magicScopeVariable = $this->
lock();
745 $text = $this->mStripState->unstripBoth( $text );
756 $this->mUser =
$user;
766 $t = Title::newFromText(
'NO TITLE' );
769 if (
$t->hasFragment() ) {
770 # Strip the fragment to avoid various odd effects
771 $this->mTitle =
$t->createFragmentTarget(
'' );
783 return $this->mTitle;
792 public function Title( $x =
null ) {
793 return wfSetVar( $this->mTitle, $x );
802 $this->mOutputType =
$ot;
819 return wfSetVar( $this->mOutputType, $x );
828 return $this->mOutput;
837 return $this->mOptions;
847 return wfSetVar( $this->mOptions, $x );
854 return $this->mLinkID++;
861 $this->mLinkID = $id;
882 $target = $this->mOptions->getTargetLanguage();
884 if ( $target !==
null ) {
886 } elseif ( $this->mOptions->getInterfaceMessage() ) {
887 return $this->mOptions->getUserLangObj();
888 } elseif ( is_null( $this->mTitle ) ) {
889 throw new MWException( __METHOD__ .
': $this->mTitle is null' );
892 return $this->mTitle->getPageLanguage();
910 if ( !is_null( $this->mUser ) ) {
913 return $this->mOptions->getUser();
922 if ( !isset( $this->mPreprocessor ) ) {
923 $class = $this->mPreprocessorClass;
924 $this->mPreprocessor =
new $class( $this );
926 return $this->mPreprocessor;
936 if ( !$this->mLinkRenderer ) {
937 $this->mLinkRenderer = MediaWikiServices::getInstance()
938 ->getLinkRendererFactory()->create();
939 $this->mLinkRenderer->setStubThreshold(
944 return $this->mLinkRenderer;
971 $taglist = implode(
'|', $elements );
972 $start =
"/<($taglist)(\\s+[^>]*?|\\s*?)(\/?" .
">)|<(!--)/i";
974 while ( $text !=
'' ) {
975 $p = preg_split( $start, $text, 2, PREG_SPLIT_DELIM_CAPTURE );
977 if ( count( $p ) < 5 ) {
980 if ( count( $p ) > 5 ) {
994 $marker = self::MARKER_PREFIX .
"-$element-" . sprintf(
'%08X', $n++ ) . self::MARKER_SUFFIX;
995 $stripped .= $marker;
997 if ( $close ===
'/>' ) {
998 # Empty element tag, <tag />
1003 if ( $element ===
'!--' ) {
1006 $end =
"/(<\\/$element\\s*>)/i";
1008 $q = preg_split( $end, $inside, 2, PREG_SPLIT_DELIM_CAPTURE );
1010 if ( count( $q ) < 3 ) {
1011 # No end tag -- let it run out to the end of the text.
1022 Sanitizer::decodeTagAttributes( $attributes ),
1023 "<$element$attributes$close$content$tail" ];
1034 return $this->mStripList;
1047 $marker = self::MARKER_PREFIX .
"-item-{$this->mMarkerIndex}-" . self::MARKER_SUFFIX;
1048 $this->mMarkerIndex++;
1049 $this->mStripState->addGeneral( $marker, $text );
1063 $td_history = []; # Is currently a td tag open?
1064 $last_tag_history = []; # Save
history of last lag activated (td, th
or caption)
1065 $tr_history = []; # Is currently a tr tag open?
1066 $tr_attributes = []; #
history of tr attributes
1067 $has_opened_tr = []; # Did
this table open a <tr> element?
1068 $indent_level = 0; # indent level
of the
table
1071 $line = trim( $outLine );
1074 $out .= $outLine .
"\n";
1078 $first_character =
$line[0];
1079 $first_two = substr(
$line, 0, 2 );
1082 if ( preg_match(
'/^(:*)\s*\{\|(.*)$/',
$line,
$matches ) ) {
1083 # First check if we are starting a new table
1084 $indent_level = strlen(
$matches[1] );
1086 $attributes = $this->mStripState->unstripBoth(
$matches[2] );
1087 $attributes = Sanitizer::fixTagAttributes( $attributes,
'table' );
1089 $outLine = str_repeat(
'<dl><dd>', $indent_level ) .
"<table{$attributes}>";
1090 array_push( $td_history,
false );
1091 array_push( $last_tag_history,
'' );
1092 array_push( $tr_history,
false );
1093 array_push( $tr_attributes,
'' );
1094 array_push( $has_opened_tr,
false );
1095 } elseif ( count( $td_history ) == 0 ) {
1096 # Don't do any of the following
1097 $out .= $outLine .
"\n";
1099 } elseif ( $first_two ===
'|}' ) {
1100 # We are ending a table
1102 $last_tag = array_pop( $last_tag_history );
1104 if ( !array_pop( $has_opened_tr ) ) {
1105 $line =
"<tr><td></td></tr>{$line}";
1108 if ( array_pop( $tr_history ) ) {
1109 $line =
"</tr>{$line}";
1112 if ( array_pop( $td_history ) ) {
1113 $line =
"</{$last_tag}>{$line}";
1115 array_pop( $tr_attributes );
1116 if ( $indent_level > 0 ) {
1117 $outLine = rtrim(
$line ) . str_repeat(
'</dd></dl>', $indent_level );
1121 } elseif ( $first_two ===
'|-' ) {
1122 # Now we have a table row
1123 $line = preg_replace(
'#^\|-+#',
'',
$line );
1125 # Whats after the tag is now only attributes
1126 $attributes = $this->mStripState->unstripBoth(
$line );
1127 $attributes = Sanitizer::fixTagAttributes( $attributes,
'tr' );
1128 array_pop( $tr_attributes );
1129 array_push( $tr_attributes, $attributes );
1132 $last_tag = array_pop( $last_tag_history );
1133 array_pop( $has_opened_tr );
1134 array_push( $has_opened_tr,
true );
1136 if ( array_pop( $tr_history ) ) {
1140 if ( array_pop( $td_history ) ) {
1141 $line =
"</{$last_tag}>{$line}";
1145 array_push( $tr_history,
false );
1146 array_push( $td_history,
false );
1147 array_push( $last_tag_history,
'' );
1148 } elseif ( $first_character ===
'|'
1149 || $first_character ===
'!'
1150 || $first_two ===
'|+'
1152 # This might be cell elements, td, th or captions
1153 if ( $first_two ===
'|+' ) {
1154 $first_character =
'+';
1161 if ( $first_character ===
'!' ) {
1165 # Split up multiple cells on the same line.
1166 # FIXME : This can result in improper nesting of tags processed
1167 # by earlier parser steps.
1168 $cells = explode(
'||',
$line );
1172 # Loop through each table cell
1173 foreach ( $cells
as $cell ) {
1175 if ( $first_character !==
'+' ) {
1176 $tr_after = array_pop( $tr_attributes );
1177 if ( !array_pop( $tr_history ) ) {
1178 $previous =
"<tr{$tr_after}>\n";
1180 array_push( $tr_history,
true );
1181 array_push( $tr_attributes,
'' );
1182 array_pop( $has_opened_tr );
1183 array_push( $has_opened_tr,
true );
1186 $last_tag = array_pop( $last_tag_history );
1188 if ( array_pop( $td_history ) ) {
1189 $previous =
"</{$last_tag}>\n{$previous}";
1192 if ( $first_character ===
'|' ) {
1194 } elseif ( $first_character ===
'!' ) {
1196 } elseif ( $first_character ===
'+' ) {
1197 $last_tag =
'caption';
1202 array_push( $last_tag_history, $last_tag );
1204 # A cell could contain both parameters and data
1205 $cell_data = explode(
'|', $cell, 2 );
1207 # T2553: Note that a '|' inside an invalid link should not
1208 # be mistaken as delimiting cell parameters
1209 # Bug T153140: Neither should language converter markup.
1210 if ( preg_match(
'/\[\[|-\{/', $cell_data[0] ) === 1 ) {
1211 $cell =
"{$previous}<{$last_tag}>" . trim( $cell );
1212 } elseif ( count( $cell_data ) == 1 ) {
1214 $cell =
"{$previous}<{$last_tag}>" . trim( $cell_data[0] );
1216 $attributes = $this->mStripState->unstripBoth( $cell_data[0] );
1217 $attributes = Sanitizer::fixTagAttributes( $attributes, $last_tag );
1219 $cell =
"{$previous}<{$last_tag}{$attributes}>" . trim( $cell_data[1] );
1223 array_push( $td_history,
true );
1226 $out .= $outLine .
"\n";
1229 # Closing open td, tr && table
1230 while ( count( $td_history ) > 0 ) {
1231 if ( array_pop( $td_history ) ) {
1234 if ( array_pop( $tr_history ) ) {
1237 if ( !array_pop( $has_opened_tr ) ) {
1238 $out .=
"<tr><td></td></tr>\n";
1241 $out .=
"</table>\n";
1244 # Remove trailing line-ending (b/c)
1245 if ( substr(
$out, -1 ) ===
"\n" ) {
1249 # special case: don't return empty table
1250 if (
$out ===
"<table>\n<tr><td></td></tr>\n</table>" ) {
1275 # Hook to suspend the parser in this state
1276 if ( !Hooks::run(
'ParserBeforeInternalParse', [ &
$parser, &$text, &$this->mStripState ] ) ) {
1280 # if $frame is provided, then use $frame for replacing any variables
1282 # use frame depth to infer how include/noinclude tags should be handled
1283 # depth=0 means this is the top-level document; otherwise it's an included document
1284 if ( !$frame->depth ) {
1287 $flag = self::PTD_FOR_INCLUSION;
1290 $text = $frame->expand( $dom );
1292 # if $frame is not provided, then use old-style replaceVariables
1296 Hooks::run(
'InternalParseBeforeSanitize', [ &
$parser, &$text, &$this->mStripState ] );
1297 $text = Sanitizer::removeHTMLtags(
1299 [ $this,
'attributeStripCallback' ],
1301 array_keys( $this->mTransparentTagHooks ),
1303 [ $this,
'addTrackingCategory' ]
1305 Hooks::run(
'InternalParseBeforeLinks', [ &
$parser, &$text, &$this->mStripState ] );
1307 # Tables need to come after variable replacement for things to work
1308 # properly; putting them before other transformations should keep
1309 # exciting things like link expansions from showing up in surprising
1313 $text = preg_replace(
'/(^|\n)-----*/',
'\\1<hr />', $text );
1322 # replaceInternalLinks may sometimes leave behind
1323 # absolute URLs, which have to be masked to hide them from replaceExternalLinks
1324 $text = str_replace( self::MARKER_PREFIX .
'NOPARSE',
'', $text );
1342 $text = $this->mStripState->unstripGeneral( $text );
1348 Hooks::run(
'ParserAfterUnstrip', [ &
$parser, &$text ] );
1351 # Clean up special characters, only run once, next-to-last before doBlockLevels
1353 # French spaces, last one Guillemet-left
1354 # only if there is something before the space
1355 '/(.) (?=\\?|:|;|!|%|\\302\\273)/' =>
'\\1 ',
1356 # french spaces, Guillemet-right
1357 '/(\\302\\253) /' =>
'\\1 ',
1358 '/ (!\s*important)/' =>
' \\1', # Beware
of CSS magic word !important, T13874.
1360 $text = preg_replace( array_keys( $fixtags ), array_values( $fixtags ), $text );
1373 if ( !( $this->mOptions->getDisableContentConversion()
1374 || isset( $this->mDoubleUnderscores[
'nocontentconvert'] ) )
1376 if ( !$this->mOptions->getInterfaceMessage() ) {
1377 # The position of the convert() call should not be changed. it
1378 # assumes that the links are all replaced and the only thing left
1379 # is the <nowiki> mark.
1384 $text = $this->mStripState->unstripNoWiki( $text );
1387 Hooks::run(
'ParserBeforeTidy', [ &
$parser, &$text ] );
1391 $text = $this->mStripState->unstripGeneral( $text );
1393 $text = Sanitizer::normalizeCharReferences( $text );
1396 if ( $this->mOptions->getTidy() ) {
1400 # attempt to sanitize at least some nesting problems
1401 # (T4702 and quite a few others)
1403 # ''Something [http:
1404 # <i>Something</i><a href="http://www.cool.com"..><i>cool></i></a>
1405 '/(<([bi])>)(<([bi])>)?([^<]*)(<\/?a[^<]*>)([^<]*)(<\/\\4>)?(<\/\\2>)/' =>
1406 '\\1\\3\\5\\8\\9\\6\\1\\3\\7\\8\\9',
1407 # fix up an anchor inside another anchor, only
1408 # at least for a single single nested link (T5695)
1409 '/(<a[^>]+>)([^<]*)(<a[^>]+>[^<]*)<\/a>(.*)<\/a>/' =>
1410 '\\1\\2</a>\\3</a>\\1\\4</a>',
1411 # fix div inside inline elements- doBlockLevels won't wrap a line which
1412 # contains a div, so fix it up here; replace
1413 # div with escaped text
1414 '/(<([aib]) [^>]+>)([^<]*)(<div([^>]*)>)(.*)(<\/div>)([^<]*)(<\/\\2>)/' =>
1415 '\\1\\3<div\\5>\\6</div>\\8\\9',
1416 # remove empty italic or bold tag pairs, some
1417 # introduced by rules above
1418 '/<([bi])><\/\\1>/' =>
'',
1421 $text = preg_replace(
1422 array_keys( $tidyregs ),
1423 array_values( $tidyregs ),
1428 Hooks::run(
'ParserAfterTidy', [ &
$parser, &$text ] );
1447 $urlChar = self::EXT_LINK_URL_CLASS;
1448 $addr = self::EXT_LINK_ADDR;
1449 $space = self::SPACE_NOT_NL; # non-newline space
1450 $spdash =
"(?:-|$space)"; # a dash
or a non-newline space
1451 $spaces =
"$space++"; # possessive match
of 1
or more spaces
1452 $text = preg_replace_callback(
1454 (<a[ \t\r\n>].*?</a>) | # m[1]: Skip link text
1455 (<.*?>) | # m[2]: Skip stuff inside HTML elements' .
"
1456 (\b # m[3]: Free external links
1458 ($addr$urlChar*) # m[4]: Post-protocol path
1460 \b(?:RFC|PMID) $spaces # m[5]: RFC or PMID, capture number
1462 \bISBN $spaces ( # m[6]: ISBN, capture number
1463 (?: 97[89] $spdash? )? # optional 13-digit ISBN prefix
1464 (?: [0-9] $spdash? ){9} # 9 digits with opt. delimiters
1465 [0-9Xx] # check digit
1467 )!xu", [ $this,
'magicLinkCallback' ], $text );
1477 if ( isset( $m[1] ) && $m[1] !==
'' ) {
1480 } elseif ( isset( $m[2] ) && $m[2] !==
'' ) {
1483 } elseif ( isset( $m[3] ) && $m[3] !==
'' ) {
1484 # Free external link
1486 } elseif ( isset( $m[5] ) && $m[5] !==
'' ) {
1488 if ( substr( $m[0], 0, 3 ) ===
'RFC' ) {
1489 if ( !$this->mOptions->getMagicRFCLinks() ) {
1494 $cssClass =
'mw-magiclink-rfc';
1495 $trackingCat =
'magiclink-tracking-rfc';
1497 } elseif ( substr( $m[0], 0, 4 ) ===
'PMID' ) {
1498 if ( !$this->mOptions->getMagicPMIDLinks() ) {
1502 $urlmsg =
'pubmedurl';
1503 $cssClass =
'mw-magiclink-pmid';
1504 $trackingCat =
'magiclink-tracking-pmid';
1507 throw new MWException( __METHOD__ .
': unrecognised match type "' .
1508 substr( $m[0], 0, 20 ) .
'"' );
1510 $url =
wfMessage( $urlmsg, $id )->inContentLanguage()->text();
1513 } elseif ( isset( $m[6] ) && $m[6] !==
''
1514 && $this->mOptions->getMagicISBNLinks()
1518 $space = self::SPACE_NOT_NL; # non-newline space
1519 $isbn = preg_replace(
"/$space/",
' ', $isbn );
1520 $num = strtr( $isbn, [
1527 SpecialPage::getTitleFor(
'Booksources', $num ),
1530 'class' =>
'internal mw-magiclink-isbn',
1551 # The characters '<' and '>' (which were escaped by
1552 # removeHTMLtags()) should not be included in
1553 # URLs, per RFC 2396.
1554 # Make terminate a URL as well (bug T84937)
1557 '/&(lt|gt|nbsp|#x0*(3[CcEe]|[Aa]0)|#0*(60|62|160));/',
1562 $trail = substr( $url, $m2[0][1] ) . $trail;
1563 $url = substr( $url, 0, $m2[0][1] );
1566 # Move trailing punctuation to $trail
1568 # If there is no left bracket, then consider right brackets fair game too
1569 if ( strpos( $url,
'(' ) ===
false ) {
1573 $urlRev = strrev( $url );
1574 $numSepChars = strspn( $urlRev, $sep );
1575 # Don't break a trailing HTML entity by moving the ; into $trail
1576 # This is in hot code, so use substr_compare to avoid having to
1577 # create a new string object for the comparison
1578 if ( $numSepChars && substr_compare( $url,
";", -$numSepChars, 1 ) === 0 ) {
1579 # more optimization: instead of running preg_match with a $
1580 # anchor, which can be slow, do the match on the reversed
1581 # string starting at the desired offset.
1582 # un-reversed regexp is: /&([a-z]+|#x[\da-f]+|#\d+)$/i
1583 if ( preg_match(
'/\G([a-z]+|[\da-f]+x#|\d+#)&/i', $urlRev, $m2, 0, $numSepChars ) ) {
1587 if ( $numSepChars ) {
1588 $trail = substr( $url, -$numSepChars ) . $trail;
1589 $url = substr( $url, 0, -$numSepChars );
1592 # Verify that we still have a real URL after trail removal, and
1593 # not just lone protocol
1594 if ( strlen( $trail ) >= $numPostProto ) {
1595 return $url . $trail;
1598 $url = Sanitizer::cleanUrl( $url );
1600 # Is this an external image?
1602 if ( $text ===
false ) {
1603 # Not an image, make a link
1608 # Register it in the output object...
1609 $this->mOutput->addExternalLink( $url );
1611 return $text . $trail;
1624 for ( $i = 6; $i >= 1; --$i ) {
1625 $h = str_repeat(
'=', $i );
1628 $text = preg_replace(
"/^(?:$h)[ \\t]*(.+?)[ \\t]*(?:$h)\\s*$/m",
"<h$i>\\1</h$i>", $text );
1647 $outtext = substr( $outtext, 0, -1 );
1659 $arr = preg_split(
"/(''+)/", $text, -1, PREG_SPLIT_DELIM_CAPTURE );
1660 $countarr = count( $arr );
1661 if ( $countarr == 1 ) {
1670 for ( $i = 1; $i < $countarr; $i += 2 ) {
1671 $thislen = strlen( $arr[$i] );
1675 if ( $thislen == 4 ) {
1676 $arr[$i - 1] .=
"'";
1679 } elseif ( $thislen > 5 ) {
1683 $arr[$i - 1] .= str_repeat(
"'", $thislen - 5 );
1688 if ( $thislen == 2 ) {
1690 } elseif ( $thislen == 3 ) {
1692 } elseif ( $thislen == 5 ) {
1702 if ( ( $numbold % 2 == 1 ) && ( $numitalics % 2 == 1 ) ) {
1703 $firstsingleletterword = -1;
1704 $firstmultiletterword = -1;
1706 for ( $i = 1; $i < $countarr; $i += 2 ) {
1707 if ( strlen( $arr[$i] ) == 3 ) {
1708 $x1 = substr( $arr[$i - 1], -1 );
1709 $x2 = substr( $arr[$i - 1], -2, 1 );
1710 if ( $x1 ===
' ' ) {
1711 if ( $firstspace == -1 ) {
1714 } elseif ( $x2 ===
' ' ) {
1715 $firstsingleletterword = $i;
1720 if ( $firstmultiletterword == -1 ) {
1721 $firstmultiletterword = $i;
1728 if ( $firstsingleletterword > -1 ) {
1729 $arr[$firstsingleletterword] =
"''";
1730 $arr[$firstsingleletterword - 1] .=
"'";
1731 } elseif ( $firstmultiletterword > -1 ) {
1733 $arr[$firstmultiletterword] =
"''";
1734 $arr[$firstmultiletterword - 1] .=
"'";
1735 } elseif ( $firstspace > -1 ) {
1739 $arr[$firstspace] =
"''";
1740 $arr[$firstspace - 1] .=
"'";
1749 foreach ( $arr
as $r ) {
1750 if ( ( $i % 2 ) == 0 ) {
1751 if ( $state ===
'both' ) {
1757 $thislen = strlen( $r );
1758 if ( $thislen == 2 ) {
1759 if ( $state ===
'i' ) {
1762 } elseif ( $state ===
'bi' ) {
1765 } elseif ( $state ===
'ib' ) {
1768 } elseif ( $state ===
'both' ) {
1775 } elseif ( $thislen == 3 ) {
1776 if ( $state ===
'b' ) {
1779 } elseif ( $state ===
'bi' ) {
1782 } elseif ( $state ===
'ib' ) {
1785 } elseif ( $state ===
'both' ) {
1792 } elseif ( $thislen == 5 ) {
1793 if ( $state ===
'b' ) {
1796 } elseif ( $state ===
'i' ) {
1799 } elseif ( $state ===
'bi' ) {
1802 } elseif ( $state ===
'ib' ) {
1805 } elseif ( $state ===
'both' ) {
1817 if ( $state ===
'b' || $state ===
'ib' ) {
1820 if ( $state ===
'i' || $state ===
'bi' || $state ===
'ib' ) {
1823 if ( $state ===
'bi' ) {
1827 if ( $state ===
'both' &&
$buffer ) {
1847 $bits = preg_split( $this->mExtLinkBracketedRegex, $text, -1, PREG_SPLIT_DELIM_CAPTURE );
1848 if ( $bits ===
false ) {
1849 throw new MWException(
"PCRE needs to be compiled with "
1850 .
"--enable-unicode-properties in order for MediaWiki to function" );
1852 $s = array_shift( $bits );
1855 while ( $i < count( $bits ) ) {
1858 $text = $bits[$i++];
1859 $trail = $bits[$i++];
1861 # The characters '<' and '>' (which were escaped by
1862 # removeHTMLtags()) should not be included in
1863 # URLs, per RFC 2396.
1865 if ( preg_match(
'/&(lt|gt);/', $url, $m2, PREG_OFFSET_CAPTURE ) ) {
1866 $text = substr( $url, $m2[0][1] ) .
' ' . $text;
1867 $url = substr( $url, 0, $m2[0][1] );
1870 # If the link text is an image URL, replace it with an <img> tag
1871 # This happened by accident in the original parser, but some people used it extensively
1873 if ( $img !==
false ) {
1879 # Set linktype for CSS
1882 # No link text, e.g. [http:
1883 if ( $text ==
'' ) {
1886 $text =
'[' . $langObj->formatNum( ++$this->mAutonumber ) .
']';
1887 $linktype =
'autonumber';
1889 # Have link text, e.g. [http:
1896 $url = Sanitizer::cleanUrl( $url );
1898 # Use the encoded URL
1899 # This means that users can paste URLs directly into the text
1900 # Funny characters like ö aren't valid in URLs anyway
1901 # This was changed in August 2004
1905 # Register link in the output object.
1906 $this->mOutput->addExternalLink( $url );
1944 $rel = self::getExternalLinkRel( $url, $this->mTitle );
1946 $target = $this->mOptions->getExternalLinkTarget();
1949 if ( !in_array( $target, [
'_self',
'_parent',
'_top' ] ) ) {
1953 if ( $rel !==
'' ) {
1956 $rel .=
'noreferrer noopener';
1973 # First, make sure unsafe characters are encoded
1974 $url = preg_replace_callback(
'/[\x00-\x20"<>\[\\\\\]^`{|}\x7F-\xFF]/',
1976 return rawurlencode( $m[0] );
1982 $end = strlen( $url );
1984 # Fragment part - 'fragment'
1985 $start = strpos( $url,
'#' );
1986 if ( $start !==
false && $start < $end ) {
1987 $ret = self::normalizeUrlComponent(
1988 substr( $url, $start, $end - $start ),
'"#%<>[\]^`{|}' ) .
$ret;
1992 # Query part - 'query' minus &=+;
1993 $start = strpos( $url,
'?' );
1994 if ( $start !==
false && $start < $end ) {
1995 $ret = self::normalizeUrlComponent(
1996 substr( $url, $start, $end - $start ),
'"#%<>[\]^`{|}&=+;' ) .
$ret;
2000 # Scheme and path part - 'pchar'
2001 # (we assume no userinfo or encoded colons in the host)
2002 $ret = self::normalizeUrlComponent(
2003 substr( $url, 0, $end ),
'"#%<>[\]^`{|}/?' ) .
$ret;
2009 $callback =
function (
$matches )
use ( $unsafe ) {
2011 $ord = ord( $char );
2012 if ( $ord > 32 && $ord < 127 && strpos( $unsafe, $char ) ===
false ) {
2016 # Leave it escaped, but use uppercase for a-f
2020 return preg_replace_callback(
'/%[0-9A-Fa-f]{2}/', $callback, $component );
2032 $imagesfrom = $this->mOptions->getAllowExternalImagesFrom();
2033 $imagesexception = !empty( $imagesfrom );
2035 # $imagesfrom could be either a single string or an array of strings, parse out the latter
2036 if ( $imagesexception && is_array( $imagesfrom ) ) {
2037 $imagematch =
false;
2038 foreach ( $imagesfrom
as $match ) {
2039 if ( strpos( $url, $match ) === 0 ) {
2044 } elseif ( $imagesexception ) {
2045 $imagematch = ( strpos( $url, $imagesfrom ) === 0 );
2047 $imagematch =
false;
2050 if ( $this->mOptions->getAllowExternalImages()
2051 || ( $imagesexception && $imagematch )
2053 if ( preg_match( self::EXT_IMAGE_REGEX, $url ) ) {
2058 if ( !$text && $this->mOptions->getEnableImageWhitelist()
2059 && preg_match( self::EXT_IMAGE_REGEX, $url )
2061 $whitelist = explode(
2063 wfMessage(
'external_image_whitelist' )->inContentLanguage()->
text()
2066 foreach ( $whitelist
as $entry ) {
2067 # Sanitize the regex fragment, make it case-insensitive, ignore blank entries/comments
2068 if ( strpos( $entry,
'#' ) === 0 || $entry ===
'' ) {
2071 if ( preg_match(
'/' . str_replace(
'/',
'\\/', $entry ) .
'/i', $url ) ) {
2072 # Image matches a whitelist entry
2106 static $tc =
false, $e1, $e1_img;
2107 # the % is needed to support urlencoded titles as well
2109 $tc = Title::legalChars() .
'#%';
2110 # Match a link having the form [[namespace:link|alternate]]trail
2111 $e1 =
"/^([{$tc}]+)(?:\\|(.+?))?]](.*)\$/sD";
2112 # Match cases where there is no "]]", which might still be images
2113 $e1_img =
"/^([{$tc}]+)\\|(.*)\$/sD";
2118 # split the entire text string on occurrences of [[
2120 # get the first element (all text up to first [[), and remove the space we added
2123 $line = $a->current(); # Workaround
for broken ArrayIterator::next()
that returns
"void"
2124 $s = substr(
$s, 1 );
2128 if ( $useLinkPrefixExtension ) {
2129 # Match the end of a line for a word that's not followed by whitespace,
2130 # e.g. in the case of 'The Arab al[[Razi]]', 'al' will be matched
2133 $e2 =
"/^((?>.*[^$charset]|))(.+)$/sDu";
2136 if ( is_null( $this->mTitle ) ) {
2137 throw new MWException( __METHOD__ .
": \$this->mTitle is null\n" );
2139 $nottalk = !$this->mTitle->isTalkPage();
2141 if ( $useLinkPrefixExtension ) {
2143 if ( preg_match( $e2,
$s, $m ) ) {
2144 $first_prefix = $m[2];
2146 $first_prefix =
false;
2154 # Loop for each link
2155 for ( ;
$line !==
false &&
$line !==
null; $a->next(),
$line = $a->current() ) {
2156 # Check for excessive memory usage
2157 if ( $holders->isBig() ) {
2159 # Do the existence check, replace the link holders and clear the array
2160 $holders->replace(
$s );
2164 if ( $useLinkPrefixExtension ) {
2165 if ( preg_match( $e2,
$s, $m ) ) {
2172 if ( $first_prefix ) {
2173 $prefix = $first_prefix;
2174 $first_prefix =
false;
2178 $might_be_img =
false;
2182 # If we get a ] at the beginning of $m[3] that means we have a link that's something like:
2183 # [[Image:Foo.jpg|[http:
2184 # the real problem is with the $e1 regex
2186 # Still some problems for cases where the ] is meant to be outside punctuation,
2187 # and no image is in sight. See T4095.
2189 && substr( $m[3], 0, 1 ) ===
']'
2190 && strpos( $text,
'[' ) !==
false
2193 $m[3] = substr( $m[3], 1 );
2195 # fix up urlencoded title texts
2196 if ( strpos( $m[1],
'%' ) !==
false ) {
2197 # Should anchors '#' also be rejected?
2198 $m[1] = str_replace( [
'<',
'>' ], [
'<',
'>' ], rawurldecode( $m[1] ) );
2201 } elseif ( preg_match( $e1_img,
$line, $m ) ) {
2202 # Invalid, but might be an image with a link in its caption
2203 $might_be_img =
true;
2205 if ( strpos( $m[1],
'%' ) !==
false ) {
2206 $m[1] = str_replace( [
'<',
'>' ], [
'<',
'>' ], rawurldecode( $m[1] ) );
2214 $origLink = ltrim( $m[1],
' ' );
2216 # Don't allow internal links to pages containing
2217 # PROTO: where PROTO is a valid URL protocol; these
2218 # should be external links.
2219 if ( preg_match(
'/^(?i:' . $this->mUrlProtocols .
')/', $origLink ) ) {
2224 # Make subpage if necessary
2225 if ( $useSubpages ) {
2235 $unstrip = $this->mStripState->killMarkers(
$link );
2236 $noMarkers = ( $unstrip ===
$link );
2238 $nt = $noMarkers ? Title::newFromText(
$link ) :
null;
2239 if ( $nt ===
null ) {
2244 $ns = $nt->getNamespace();
2245 $iw = $nt->getInterwiki();
2247 $noforce = ( substr( $origLink, 0, 1 ) !==
':' );
2249 if ( $might_be_img ) { #
if this is actually an invalid
link
2250 if ( $ns ==
NS_FILE && $noforce ) { # but might be an image
2253 # look at the next 'line' to see if we can close it there
2255 $next_line = $a->current();
2256 if ( $next_line ===
false || $next_line ===
null ) {
2259 $m = explode(
']]', $next_line, 3 );
2260 if ( count( $m ) == 3 ) {
2261 # the first ]] closes the inner link, the second the image
2263 $text .=
"[[{$m[0]}]]{$m[1]}";
2266 } elseif ( count( $m ) == 2 ) {
2267 # if there's exactly one ]] that's fine, we'll keep looking
2268 $text .=
"[[{$m[0]}]]{$m[1]}";
2270 # if $next_line is invalid too, we need look no further
2271 $text .=
'[[' . $next_line;
2276 # we couldn't find the end of this imageLink, so output it raw
2277 # but don't ignore what might be perfectly normal links in the text we've examined
2279 $s .=
"{$prefix}[[$link|$text";
2280 # note: no $trail, because without an end, there *is* no trail
2283 }
else { #
it's not an image, so output it raw
2284 $s .= "{$prefix}[[$link|$text";
2285 # note: no $trail, because without an end, there *is* no trail
2290 $wasblank = ( $text == '' );
2294 # Strip off leading ':
'
2295 $text = substr( $text, 1 );
2298 # T6598 madness. Handle the quotes only if they come from the alternate part
2299 # [[Lista d''e paise d''o munno]] -> <a href="...">Lista d''e paise d''o munno</a>
2300 # [[Criticism of Harry Potter|Criticism of ''Harry Potter'']]
2301 # -> <a href="Criticism of Harry Potter">Criticism of <i>Harry Potter</i></a>
2302 $text = $this->doQuotes( $text );
2305 # Link not escaped by : , create the various objects
2306 if ( $noforce && !$nt->wasLocalInterwiki() ) {
2309 $iw && $this->mOptions->getInterwikiMagic() && $nottalk && (
2310 Language::fetchLanguageName( $iw, null, 'mw
' ) ||
2311 in_array( $iw, $wgExtraInterlanguageLinkPrefixes )
2314 # T26502: filter duplicates
2315 if ( !isset( $this->mLangLinkLanguages[$iw] ) ) {
2316 $this->mLangLinkLanguages[$iw] = true;
2317 $this->mOutput->addLanguageLink( $nt->getFullText() );
2323 $s = rtrim( $s . $prefix ) . $trail; # T175416
2327 if ( $ns == NS_FILE ) {
2328 if ( !wfIsBadImage( $nt->getDBkey(), $this->mTitle ) ) {
2330 # if no parameters were passed, $text
2331 # becomes something like "File:Foo.png",
2332 # which we don't want to pass
on to the
2336 # recursively parse links inside the image caption
2337 # actually, this will parse them in any other parameters, too,
2338 # but it might be hard to fix that, and it doesn't matter ATM
2342 # cloak any absolute URLs inside the image markup, so replaceExternalLinks() won't touch them
2344 $this->
makeImage( $nt, $text, $holders ) ) . $trail;
2351 $s = rtrim(
$s . $prefix ) . $trail; # T2087, T87753
2358 $sortkey = Sanitizer::decodeCharReferences( $sortkey );
2359 $sortkey = str_replace(
"\n",
'', $sortkey );
2361 $this->mOutput->addCategory( $nt->getDBkey(), $sortkey );
2367 # Self-link checking. For some languages, variants of the title are checked in
2368 # LinkHolderArray::doVariants() to allow batching the existence checks necessary
2369 # for linking to a different variant.
2370 if ( $ns !=
NS_SPECIAL && $nt->equals( $this->mTitle ) && !$nt->hasFragment() ) {
2375 # NS_MEDIA is a pseudo-namespace for linking directly to a file
2376 # @todo FIXME: Should do batch file existence checks, see comment below
2378 # Give extensions a chance to select the file revision for us
2382 [ $this, $nt, &
$options, &$descQuery ] );
2383 # Fetch and register the file (file title may be different via hooks)
2385 # Cloak with NOPARSE to avoid replacement in replaceExternalLinks
2391 # Some titles, such as valid special pages or files in foreign repos, should
2392 # be shown as bluelinks even though they're not included in the page table
2393 # @todo FIXME: isAlwaysKnown() can be expensive for file links; we should really do
2394 # batch file existence checks for NS_FILE and NS_MEDIA
2395 if ( $iw ==
'' && $nt->isAlwaysKnown() ) {
2396 $this->mOutput->addLink( $nt );
2399 # Links will be added to the output link list after checking
2400 $s .= $holders->makeHolder( $nt, $text, [], $trail, $prefix );
2422 if ( $text ==
'' ) {
2423 $text = htmlspecialchars( $nt->getPrefixedText() );
2426 $link = $this->getLinkRenderer()->makeKnownLink(
2427 $nt,
new HtmlArmor(
"$prefix$text$inside" )
2430 return $this->armorLinks(
$link ) . $trail;
2444 return preg_replace(
'/\b((?i)' . $this->mUrlProtocols .
')/',
2445 self::MARKER_PREFIX .
"NOPARSE$1", $text );
2453 # Some namespaces don't allow subpages
2454 return MWNamespace::hasSubpages( $this->mTitle->getNamespace() );
2496 if ( is_null( $this->mTitle ) ) {
2501 throw new MWException( __METHOD__ .
' Should only be '
2502 .
' called while parsing (no title set)' );
2512 if ( Hooks::run(
'ParserGetVariableValueVarCache', [ &
$parser, &$this->mVarCache ] ) ) {
2513 if ( isset( $this->mVarCache[$index] ) ) {
2514 return $this->mVarCache[$index];
2518 $ts =
wfTimestamp( TS_UNIX, $this->mOptions->getTimestamp() );
2519 Hooks::run(
'ParserGetVariableValueTs', [ &
$parser, &$ts ] );
2521 $pageLang = $this->getFunctionLang();
2527 case 'currentmonth':
2528 $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->
format(
'm' ),
true );
2530 case 'currentmonth1':
2531 $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->
format(
'n' ),
true );
2533 case 'currentmonthname':
2534 $value = $pageLang->getMonthName( MWTimestamp::getInstance( $ts )->
format(
'n' ) );
2536 case 'currentmonthnamegen':
2537 $value = $pageLang->getMonthNameGen( MWTimestamp::getInstance( $ts )->
format(
'n' ) );
2539 case 'currentmonthabbrev':
2540 $value = $pageLang->getMonthAbbreviation( MWTimestamp::getInstance( $ts )->
format(
'n' ) );
2543 $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->
format(
'j' ),
true );
2546 $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->
format(
'd' ),
true );
2549 $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->
format(
'm' ),
true );
2552 $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->
format(
'n' ),
true );
2554 case 'localmonthname':
2555 $value = $pageLang->getMonthName( MWTimestamp::getLocalInstance( $ts )->
format(
'n' ) );
2557 case 'localmonthnamegen':
2558 $value = $pageLang->getMonthNameGen( MWTimestamp::getLocalInstance( $ts )->
format(
'n' ) );
2560 case 'localmonthabbrev':
2561 $value = $pageLang->getMonthAbbreviation( MWTimestamp::getLocalInstance( $ts )->
format(
'n' ) );
2564 $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->
format(
'j' ),
true );
2567 $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->
format(
'd' ),
true );
2575 case 'fullpagename':
2578 case 'fullpagenamee':
2584 case 'subpagenamee':
2587 case 'rootpagename':
2590 case 'rootpagenamee':
2594 $this->mTitle->getRootText()
2597 case 'basepagename':
2600 case 'basepagenamee':
2604 $this->mTitle->getBaseText()
2607 case 'talkpagename':
2608 if ( $this->mTitle->canHaveTalkPage() ) {
2609 $talkPage = $this->mTitle->getTalkPage();
2615 case 'talkpagenamee':
2616 if ( $this->mTitle->canHaveTalkPage() ) {
2617 $talkPage = $this->mTitle->getTalkPage();
2623 case 'subjectpagename':
2624 $subjPage = $this->mTitle->getSubjectPage();
2627 case 'subjectpagenamee':
2628 $subjPage = $this->mTitle->getSubjectPage();
2632 $pageid = $this->getTitle()->getArticleID();
2633 if ( $pageid == 0 ) {
2634 # 0 means the page doesn't exist in the database,
2635 # which means the user is previewing a new page.
2636 # The vary-revision flag must be set, because the magic word
2637 # will have a different value once the page is saved.
2638 $this->mOutput->setFlag(
'vary-revision' );
2639 wfDebug( __METHOD__ .
": {{PAGEID}} used in a new page, setting vary-revision...\n" );
2641 $value = $pageid ? $pageid :
null;
2644 # Let the edit saving system know we should parse the page
2645 # *after* a revision ID has been assigned.
2646 $this->mOutput->setFlag(
'vary-revision-id' );
2647 wfDebug( __METHOD__ .
": {{REVISIONID}} used, setting vary-revision-id...\n" );
2648 $value = $this->mRevisionId;
2649 if ( !
$value && $this->mOptions->getSpeculativeRevIdCallback() ) {
2650 $value = call_user_func( $this->mOptions->getSpeculativeRevIdCallback() );
2651 $this->mOutput->setSpeculativeRevIdUsed(
$value );
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__ .
": {{REVISIONDAY}} used, setting vary-revision...\n" );
2659 $value = intval( substr( $this->getRevisionTimestamp(), 6, 2 ) );
2661 case 'revisionday2':
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__ .
": {{REVISIONDAY2}} used, setting vary-revision...\n" );
2666 $value = substr( $this->getRevisionTimestamp(), 6, 2 );
2668 case 'revisionmonth':
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__ .
": {{REVISIONMONTH}} used, setting vary-revision...\n" );
2673 $value = substr( $this->getRevisionTimestamp(), 4, 2 );
2675 case 'revisionmonth1':
2676 # Let the edit saving system know we should parse the page
2677 # *after* a revision ID has been assigned. This is for null edits.
2678 $this->mOutput->setFlag(
'vary-revision' );
2679 wfDebug( __METHOD__ .
": {{REVISIONMONTH1}} used, setting vary-revision...\n" );
2680 $value = intval( substr( $this->getRevisionTimestamp(), 4, 2 ) );
2682 case 'revisionyear':
2683 # Let the edit saving system know we should parse the page
2684 # *after* a revision ID has been assigned. This is for null edits.
2685 $this->mOutput->setFlag(
'vary-revision' );
2686 wfDebug( __METHOD__ .
": {{REVISIONYEAR}} used, setting vary-revision...\n" );
2687 $value = substr( $this->getRevisionTimestamp(), 0, 4 );
2689 case 'revisiontimestamp':
2690 # Let the edit saving system know we should parse the page
2691 # *after* a revision ID has been assigned. This is for null edits.
2692 $this->mOutput->setFlag(
'vary-revision' );
2693 wfDebug( __METHOD__ .
": {{REVISIONTIMESTAMP}} used, setting vary-revision...\n" );
2694 $value = $this->getRevisionTimestamp();
2696 case 'revisionuser':
2697 # Let the edit saving system know we should parse the page
2698 # *after* a revision ID has been assigned for null edits.
2699 $this->mOutput->setFlag(
'vary-user' );
2700 wfDebug( __METHOD__ .
": {{REVISIONUSER}} used, setting vary-user...\n" );
2701 $value = $this->getRevisionUser();
2703 case 'revisionsize':
2704 $value = $this->getRevisionSize();
2707 $value = str_replace(
'_',
' ',
$wgContLang->getNsText( $this->mTitle->getNamespace() ) );
2712 case 'namespacenumber':
2713 $value = $this->mTitle->getNamespace();
2716 $value = $this->mTitle->canHaveTalkPage()
2717 ? str_replace(
'_',
' ', $this->mTitle->getTalkNsText() )
2721 $value = $this->mTitle->canHaveTalkPage() ?
wfUrlencode( $this->mTitle->getTalkNsText() ) :
'';
2723 case 'subjectspace':
2724 $value = str_replace(
'_',
' ', $this->mTitle->getSubjectNsText() );
2726 case 'subjectspacee':
2729 case 'currentdayname':
2730 $value = $pageLang->getWeekdayName( (
int)MWTimestamp::getInstance( $ts )->format(
'w' ) + 1 );
2733 $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format(
'Y' ),
true );
2739 $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format(
'H' ),
true );
2742 # @bug T6594 PHP5 has it zero padded, PHP4 does not, cast to
2743 # int to remove the padding
2744 $value = $pageLang->formatNum( (
int)MWTimestamp::getInstance( $ts )->format(
'W' ) );
2747 $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format(
'w' ) );
2749 case 'localdayname':
2750 $value = $pageLang->getWeekdayName(
2751 (
int)MWTimestamp::getLocalInstance( $ts )->format(
'w' ) + 1
2755 $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format(
'Y' ),
true );
2758 $value = $pageLang->time(
2759 MWTimestamp::getLocalInstance( $ts )->format(
'YmdHis' ),
2765 $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format(
'H' ),
true );
2768 # @bug T6594 PHP5 has it zero padded, PHP4 does not, cast to
2769 # int to remove the padding
2770 $value = $pageLang->formatNum( (
int)MWTimestamp::getLocalInstance( $ts )->format(
'W' ) );
2773 $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format(
'w' ) );
2775 case 'numberofarticles':
2778 case 'numberoffiles':
2781 case 'numberofusers':
2784 case 'numberofactiveusers':
2787 case 'numberofpages':
2790 case 'numberofadmins':
2793 case 'numberofedits':
2796 case 'currenttimestamp':
2799 case 'localtimestamp':
2800 $value = MWTimestamp::getLocalInstance( $ts )->format(
'YmdHis' );
2802 case 'currentversion':
2817 case 'directionmark':
2818 return $pageLang->getDirMark();
2819 case 'contentlanguage':
2822 case 'pagelanguage':
2823 $value = $pageLang->getCode();
2825 case 'cascadingsources':
2831 'ParserGetVariableValueSwitch',
2832 [ &
$parser, &$this->mVarCache, &$index, &
$ret, &$frame ]
2839 $this->mVarCache[$index] =
$value;
2881 $dom = $this->getPreprocessor()->preprocessToObj( $text, $flags );
2893 $ltrimmed = ltrim(
$s );
2894 $w1 = substr(
$s, 0, strlen(
$s ) - strlen( $ltrimmed ) );
2895 $trimmed = rtrim( $ltrimmed );
2896 $diff = strlen( $ltrimmed ) - strlen( $trimmed );
2898 $w2 = substr( $ltrimmed, -$diff );
2902 return [ $w1, $trimmed, $w2 ];
2926 # Is there any text? Also, Prevent too big inclusions!
2927 $textSize = strlen( $text );
2928 if ( $textSize < 1 || $textSize > $this->mOptions->getMaxIncludeSize() ) {
2932 if ( $frame ===
false ) {
2933 $frame = $this->getPreprocessor()->newFrame();
2934 } elseif ( !( $frame instanceof
PPFrame ) ) {
2935 wfDebug( __METHOD__ .
" called using plain parameters instead of "
2936 .
"a PPFrame instance. Creating custom frame.\n" );
2937 $frame = $this->getPreprocessor()->newCustomFrame( $frame );
2940 $dom = $this->preprocessToDom( $text );
2942 $text = $frame->expand( $dom, $flags );
2958 $eqpos = strpos( $arg,
'=' );
2959 if ( $eqpos ===
false ) {
2960 $assocArgs[$index++] = $arg;
2962 $name = trim( substr( $arg, 0, $eqpos ) );
2963 $value = trim( substr( $arg, $eqpos + 1 ) );
2964 if (
$value ===
false ) {
2967 if (
$name !==
false ) {
3003 # does no harm if $current and $max are present but are unnecessary for the message
3004 # Not doing ->inLanguage( $this->mOptions->getUserLangObj() ), since this is shown
3005 # only during preview, and that would split the parser cache unnecessarily.
3006 $warning =
wfMessage(
"$limitationType-warning" )->numParams( $current, $max )
3008 $this->mOutput->addWarning( $warning );
3009 $this->addTrackingCategory(
"$limitationType-category" );
3034 $forceRawInterwiki =
false;
3036 $isChildObj =
false;
3038 $isLocalObj =
false;
3040 # Title object, where $text came from
3043 # $part1 is the bit before the first |, and must contain only title characters.
3044 # Various prefixes will be stripped from it later.
3045 $titleWithSpaces = $frame->expand( $piece[
'title'] );
3046 $part1 = trim( $titleWithSpaces );
3049 # Original title text preserved for various purposes
3050 $originalTitle = $part1;
3052 # $args is a list of argument nodes, starting from index 0, not including $part1
3053 # @todo FIXME: If piece['parts'] is null then the call to getLength()
3054 # below won't work b/c this $args isn't an object
3055 $args = (
null == $piece[
'parts'] ) ? [] : $piece[
'parts'];
3057 $profileSection =
null;
3061 $substMatch = $this->mSubstWords->matchStartAndRemove( $part1 );
3063 # Possibilities for substMatch: "subst", "safesubst" or FALSE
3064 # Decide whether to expand template or keep wikitext as-is.
3065 if ( $this->ot[
'wiki'] ) {
3066 if ( $substMatch ===
false ) {
3067 $literal =
true; # literal when
in PST with no prefix
3069 $literal =
false; # expand when
in PST with subst:
or safesubst:
3072 if ( $substMatch ==
'subst' ) {
3073 $literal =
true; # literal when not
in PST with
plain subst:
3075 $literal =
false; # expand when not
in PST with safesubst:
or no prefix
3079 $text = $frame->virtualBracketedImplode(
'{{',
'|',
'}}', $titleWithSpaces,
$args );
3086 if ( !$found &&
$args->getLength() == 0 ) {
3087 $id = $this->mVariables->matchStartToEnd( $part1 );
3088 if ( $id !==
false ) {
3089 $text = $this->getVariableValue( $id, $frame );
3097 # MSG, MSGNW and RAW
3101 if ( $mwMsgnw->matchStartAndRemove( $part1 ) ) {
3104 # Remove obsolete MSG:
3106 $mwMsg->matchStartAndRemove( $part1 );
3111 if ( $mwRaw->matchStartAndRemove( $part1 ) ) {
3112 $forceRawInterwiki =
true;
3118 $colonPos = strpos( $part1,
':' );
3119 if ( $colonPos !==
false ) {
3120 $func = substr( $part1, 0, $colonPos );
3121 $funcArgs = [ trim( substr( $part1, $colonPos + 1 ) ) ];
3122 $argsLength =
$args->getLength();
3123 for ( $i = 0; $i < $argsLength; $i++ ) {
3124 $funcArgs[] =
$args->item( $i );
3127 $result = $this->callParserFunction( $frame, $func, $funcArgs );
3128 }
catch ( Exception $ex ) {
3133 if ( isset(
$result[
'title'] ) ) {
3136 if ( isset(
$result[
'found'] ) ) {
3139 if ( array_key_exists(
'text',
$result ) ) {
3143 if ( isset(
$result[
'nowiki'] ) ) {
3146 if ( isset(
$result[
'isHTML'] ) ) {
3149 if ( isset(
$result[
'forceRawInterwiki'] ) ) {
3150 $forceRawInterwiki =
$result[
'forceRawInterwiki'];
3152 if ( isset(
$result[
'isChildObj'] ) ) {
3153 $isChildObj =
$result[
'isChildObj'];
3155 if ( isset(
$result[
'isLocalObj'] ) ) {
3156 $isLocalObj =
$result[
'isLocalObj'];
3161 # Finish mangling title and then check for loops.
3162 # Set $title to a Title object and $titleText to the PDBK
3165 # Split the title into page and subpage
3167 $relative = $this->maybeDoSubpageLink( $part1, $subpage );
3168 if ( $part1 !== $relative ) {
3170 $ns = $this->mTitle->getNamespace();
3172 $title = Title::newFromText( $part1, $ns );
3174 $titleText =
$title->getPrefixedText();
3175 # Check for language variants if the template is not found
3176 if ( $this->getConverterLanguage()->hasVariants() &&
$title->getArticleID() == 0 ) {
3177 $this->getConverterLanguage()->findVariantLink( $part1,
$title,
true );
3179 # Do recursion depth check
3180 $limit = $this->mOptions->getMaxTemplateDepth();
3181 if ( $frame->depth >= $limit ) {
3183 $text =
'<span class="error">'
3184 .
wfMessage(
'parser-template-recursion-depth-warning' )
3185 ->numParams( $limit )->inContentLanguage()->text()
3191 # Load from database
3192 if ( !$found &&
$title ) {
3193 $profileSection = $this->mProfiler->scopedProfileIn(
$title->getPrefixedDBkey() );
3194 if ( !
$title->isExternal() ) {
3195 if (
$title->isSpecialPage()
3196 && $this->mOptions->getAllowSpecialInclusion()
3197 && $this->ot[
'html']
3204 $argsLength =
$args->getLength();
3205 for ( $i = 0; $i < $argsLength; $i++ ) {
3206 $bits =
$args->item( $i )->splitArg();
3207 if ( strval( $bits[
'index'] ) ===
'' ) {
3209 $value = trim( $frame->expand( $bits[
'value'] ) );
3218 if ( $specialPage && $specialPage->maxIncludeCacheTime() === 0 ) {
3219 $context->setUser( $this->getUser() );
3224 $context->setLanguage( $this->mOptions->getUserLangObj() );
3228 $text =
$context->getOutput()->getHTML();
3229 $this->mOutput->addOutputPageMetadata(
$context->getOutput() );
3232 if ( $specialPage && $specialPage->maxIncludeCacheTime() !==
false ) {
3233 $this->mOutput->updateRuntimeAdaptiveExpiry(
3234 $specialPage->maxIncludeCacheTime()
3238 } elseif ( MWNamespace::isNonincludable(
$title->getNamespace() ) ) {
3239 $found =
false; # access denied
3240 wfDebug( __METHOD__ .
": template inclusion denied for " .
3241 $title->getPrefixedDBkey() .
"\n" );
3244 if ( $text !==
false ) {
3250 # If the title is valid but undisplayable, make a link to it
3251 if ( !$found && ( $this->ot[
'html'] || $this->ot[
'pre'] ) ) {
3252 $text =
"[[:$titleText]]";
3255 } elseif (
$title->isTrans() ) {
3256 # Interwiki transclusion
3257 if ( $this->ot[
'html'] && !$forceRawInterwiki ) {
3258 $text = $this->interwikiTransclude(
$title,
'render' );
3261 $text = $this->interwikiTransclude(
$title,
'raw' );
3262 # Preprocess it like a template
3263 $text = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
3269 # Do infinite loop check
3270 # This has to be done after redirect resolution to avoid infinite loops via redirects
3271 if ( !$frame->loopCheck(
$title ) ) {
3273 $text =
'<span class="error">'
3274 .
wfMessage(
'parser-template-loop-warning', $titleText )->inContentLanguage()->text()
3276 $this->addTrackingCategory(
'template-loop-category' );
3277 $this->mOutput->addWarning(
wfMessage(
'template-loop-warning',
3279 wfDebug( __METHOD__ .
": template loop broken at '$titleText'\n" );
3283 # If we haven't found text to substitute by now, we're done
3284 # Recover the source wikitext and return it
3286 $text = $frame->virtualBracketedImplode(
'{{',
'|',
'}}', $titleWithSpaces,
$args );
3287 if ( $profileSection ) {
3288 $this->mProfiler->scopedProfileOut( $profileSection );
3290 return [
'object' => $text ];
3293 # Expand DOM-style return values in a child frame
3294 if ( $isChildObj ) {
3295 # Clean up argument array
3300 } elseif ( $titleText !==
false && $newFrame->isEmpty() ) {
3301 # Expansion is eligible for the empty-frame cache
3302 $text = $newFrame->cachedExpand( $titleText, $text );
3304 # Uncached expansion
3305 $text = $newFrame->expand( $text );
3308 if ( $isLocalObj && $nowiki ) {
3310 $isLocalObj =
false;
3313 if ( $profileSection ) {
3314 $this->mProfiler->scopedProfileOut( $profileSection );
3317 # Replace raw HTML by a placeholder
3319 $text = $this->insertStripItem( $text );
3320 } elseif ( $nowiki && ( $this->ot[
'html'] || $this->ot[
'pre'] ) ) {
3321 # Escape nowiki-style return values
3323 } elseif ( is_string( $text )
3324 && !$piece[
'lineStart']
3325 && preg_match(
'/^(?:{\\||:|;|#|\*)/', $text )
3327 # T2529: if the template begins with a table or block-level
3328 # element, it should be treated as beginning a new line.
3329 # This behavior is somewhat controversial.
3330 $text =
"\n" . $text;
3333 if ( is_string( $text ) && !$this->incrementIncludeSize(
'post-expand', strlen( $text ) ) ) {
3334 # Error, oversize inclusion
3335 if ( $titleText !==
false ) {
3336 # Make a working, properly escaped link if possible (T25588)
3337 $text =
"[[:$titleText]]";
3339 # This will probably not be a working link, but at least it may
3340 # provide some hint of where the problem is
3341 preg_replace(
'/^:/',
'', $originalTitle );
3342 $text =
"[[:$originalTitle]]";
3344 $text .= $this->insertStripItem(
'<!-- WARNING: template omitted, '
3345 .
'post-expand include size too large -->' );
3346 $this->limitationWarn(
'post-expand-template-inclusion' );
3349 if ( $isLocalObj ) {
3350 $ret = [
'object' => $text ];
3352 $ret = [
'text' => $text ];
3380 # Case sensitive functions
3381 if ( isset( $this->mFunctionSynonyms[1][$function] ) ) {
3382 $function = $this->mFunctionSynonyms[1][$function];
3384 # Case insensitive functions
3386 if ( isset( $this->mFunctionSynonyms[0][$function] ) ) {
3387 $function = $this->mFunctionSynonyms[0][$function];
3389 return [
'found' =>
false ];
3393 list( $callback, $flags ) = $this->mFunctionHooks[$function];
3399 if ( $flags & self::SFH_OBJECT_ARGS ) {
3400 # Convert arguments to PPNodes and collect for appending to $allArgs
3402 foreach (
$args as $k => $v ) {
3403 if ( $v instanceof
PPNode || $k === 0 ) {
3406 $funcArgs[] = $this->mPreprocessor->newPartNodeArray( [ $k => $v ] )->item( 0 );
3410 # Add a frame parameter, and pass the arguments as an array
3411 $allArgs[] = $frame;
3412 $allArgs[] = $funcArgs;
3414 # Convert arguments to plain text and append to $allArgs
3415 foreach (
$args as $k => $v ) {
3416 if ( $v instanceof
PPNode ) {
3417 $allArgs[] = trim( $frame->expand( $v ) );
3418 } elseif ( is_int( $k ) && $k >= 0 ) {
3419 $allArgs[] = trim( $v );
3421 $allArgs[] = trim(
"$k=$v" );
3426 $result = call_user_func_array( $callback, $allArgs );
3428 # The interface for function hooks allows them to return a wikitext
3429 # string or an array containing the string and any flags. This mungs
3430 # things around to match what this method should return.
3447 $preprocessFlags = 0;
3448 if ( isset(
$result[
'noparse'] ) ) {
3449 $noparse =
$result[
'noparse'];
3451 if ( isset(
$result[
'preprocessFlags'] ) ) {
3452 $preprocessFlags =
$result[
'preprocessFlags'];
3456 $result[
'text'] = $this->preprocessToDom(
$result[
'text'], $preprocessFlags );
3473 $titleText =
$title->getPrefixedDBkey();
3475 if ( isset( $this->mTplRedirCache[$titleText] ) ) {
3476 list( $ns, $dbk ) = $this->mTplRedirCache[$titleText];
3477 $title = Title::makeTitle( $ns, $dbk );
3478 $titleText =
$title->getPrefixedDBkey();
3480 if ( isset( $this->mTplDomCache[$titleText] ) ) {
3481 return [ $this->mTplDomCache[$titleText],
$title ];
3484 # Cache miss, go to the database
3487 if ( $text ===
false ) {
3488 $this->mTplDomCache[$titleText] =
false;
3489 return [
false,
$title ];
3492 $dom = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
3493 $this->mTplDomCache[$titleText] = $dom;
3495 if ( !
$title->equals( $cacheTitle ) ) {
3496 $this->mTplRedirCache[$cacheTitle->getPrefixedDBkey()] =
3515 $cacheKey =
$title->getPrefixedDBkey();
3516 if ( !$this->currentRevisionCache ) {
3517 $this->currentRevisionCache =
new MapCacheLRU( 100 );
3519 if ( !$this->currentRevisionCache->has( $cacheKey ) ) {
3520 $this->currentRevisionCache->set( $cacheKey,
3522 call_user_func( $this->mOptions->getCurrentRevisionCallback(),
$title, $this )
3525 return $this->currentRevisionCache->get( $cacheKey );
3550 $templateCb = $this->mOptions->getTemplateCallback();
3551 $stuff = call_user_func( $templateCb,
$title, $this );
3553 $text = $stuff[
'text'];
3554 if ( is_string( $stuff[
'text'] ) ) {
3555 $text = strtr( $text,
"\x7f",
"?" );
3557 $finalTitle = isset( $stuff[
'finalTitle'] ) ? $stuff[
'finalTitle'] :
$title;
3558 if ( isset( $stuff[
'deps'] ) ) {
3559 foreach ( $stuff[
'deps']
as $dep ) {
3560 $this->mOutput->addTemplate( $dep[
'title'], $dep[
'page_id'], $dep[
'rev_id'] );
3561 if ( $dep[
'title']->equals( $this->getTitle() ) ) {
3564 $this->mOutput->setFlag(
'vary-revision' );
3568 return [ $text, $finalTitle ];
3577 return $this->fetchTemplateAndTitle(
$title )[0];
3590 $text = $skip =
false;
3594 # Loop to fetch the article, with up to 1 redirect
3596 for ( $i = 0; $i < 2 && is_object(
$title ); $i++ ) {
3597 # Give extensions a chance to select the revision instead
3598 $id =
false; # Assume current
3599 Hooks::run(
'BeforeParserFetchTemplateAndtitle',
3606 'page_id' =>
$title->getArticleID(),
3613 $rev = Revision::newFromId( $id );
3619 $rev_id =
$rev ?
$rev->getId() : 0;
3620 # If there is no current revision, there is no page
3621 if ( $id ===
false && !
$rev ) {
3622 $linkCache = LinkCache::singleton();
3623 $linkCache->addBadLinkObj(
$title );
3628 'page_id' =>
$title->getArticleID(),
3629 'rev_id' => $rev_id ];
3631 # We fetched a rev from a different title; register it too...
3633 'title' =>
$rev->getTitle(),
3634 'page_id' =>
$rev->getPage(),
3635 'rev_id' => $rev_id ];
3639 $content =
$rev->getContent();
3640 $text = $content ? $content->getWikitextForTransclusion() :
null;
3642 Hooks::run(
'ParserFetchTemplate',
3645 if ( $text ===
false || $text ===
null ) {
3652 if ( !$message->exists() ) {
3656 $content = $message->content();
3657 $text = $message->plain();
3666 $title = $content->getRedirectTarget();
3670 'finalTitle' => $finalTitle,
3695 $time = $file ? $file->getTimestamp() :
false;
3696 $sha1 = $file ? $file->getSha1() :
false;
3697 # Register the file as a dependency...
3698 $this->mOutput->addImage(
$title->getDBkey(),
$time, $sha1 );
3699 if ( $file && !
$title->equals( $file->getTitle() ) ) {
3700 # Update fetched file title
3701 $title = $file->getTitle();
3702 $this->mOutput->addImage(
$title->getDBkey(),
$time, $sha1 );
3704 return [ $file,
$title ];
3718 if ( isset(
$options[
'broken'] ) ) {
3720 } elseif ( isset(
$options[
'sha1'] ) ) {
3740 return wfMessage(
'scarytranscludedisabled' )->inContentLanguage()->text();
3743 $url =
$title->getFullURL( [
'action' => $action ] );
3745 if ( strlen( $url ) > 255 ) {
3746 return wfMessage(
'scarytranscludetoolong' )->inContentLanguage()->text();
3748 return $this->fetchScaryTemplateMaybeFromCache( $url );
3759 $obj =
$dbr->selectRow(
'transcache', [
'tc_time',
'tc_contents' ],
3760 [
'tc_url' => $url,
"tc_time >= " .
$dbr->addQuotes( $tsCond ) ] );
3762 return $obj->tc_contents;
3765 $req = MWHttpRequest::factory( $url, [], __METHOD__ );
3768 $text =
$req->getContent();
3769 } elseif (
$req->getStatus() != 200 ) {
3771 return wfMessage(
'scarytranscludefailed-httpstatus' )
3772 ->params( $url,
$req->getStatus() )->inContentLanguage()->text();
3774 return wfMessage(
'scarytranscludefailed', $url )->inContentLanguage()->text();
3778 $dbw->replace(
'transcache', [
'tc_url' ], [
3780 'tc_time' => $dbw->timestamp( time() ),
3781 'tc_contents' => $text
3797 $parts = $piece[
'parts'];
3798 $nameWithSpaces = $frame->expand( $piece[
'title'] );
3799 $argName = trim( $nameWithSpaces );
3801 $text = $frame->getArgument( $argName );
3802 if ( $text ===
false && $parts->getLength() > 0
3803 && ( $this->ot[
'html']
3805 || ( $this->ot[
'wiki'] && $frame->isTemplate() )
3808 # No match in frame, use the supplied default
3809 $object = $parts->item( 0 )->getChildren();
3811 if ( !$this->incrementIncludeSize(
'arg', strlen( $text ) ) ) {
3812 $error =
'<!-- WARNING: argument omitted, expansion size too large -->';
3813 $this->limitationWarn(
'post-expand-template-argument' );
3816 if ( $text ===
false && $object ===
false ) {
3818 $object = $frame->virtualBracketedImplode(
'{{{',
'|',
'}}}', $nameWithSpaces, $parts );
3820 if ( $error !==
false ) {
3823 if ( $object !==
false ) {
3824 $ret = [
'object' => $object ];
3826 $ret = [
'text' => $text ];
3848 static $errorStr =
'<span class="error">';
3849 static $errorLen = 20;
3852 if ( substr(
$name, 0, $errorLen ) === $errorStr ) {
3858 $attrText = !isset(
$params[
'attr'] ) ? null : $frame->expand(
$params[
'attr'] );
3859 if ( substr( $attrText, 0, $errorLen ) === $errorStr ) {
3867 $content = !isset(
$params[
'inner'] ) ? null : $frame->expand(
$params[
'inner'] );
3869 $marker = self::MARKER_PREFIX .
"-$name-"
3870 . sprintf(
'%08X', $this->mMarkerIndex++ ) . self::MARKER_SUFFIX;
3872 $isFunctionTag = isset( $this->mFunctionTagHooks[strtolower(
$name )] ) &&
3873 ( $this->ot[
'html'] || $this->ot[
'pre'] );
3874 if ( $isFunctionTag ) {
3875 $markerType =
'none';
3877 $markerType =
'general';
3879 if ( $this->ot[
'html'] || $isFunctionTag ) {
3881 $attributes = Sanitizer::decodeTagAttributes( $attrText );
3882 if ( isset(
$params[
'attributes'] ) ) {
3883 $attributes = $attributes +
$params[
'attributes'];
3886 if ( isset( $this->mTagHooks[
$name] ) ) {
3887 $output = call_user_func_array( $this->mTagHooks[
$name],
3888 [ $content, $attributes, $this, $frame ] );
3889 } elseif ( isset( $this->mFunctionTagHooks[
$name] ) ) {
3890 list( $callback, ) = $this->mFunctionTagHooks[
$name];
3894 $output = call_user_func_array( $callback, [ &
$parser, $frame, $content, $attributes ] );
3896 $output =
'<span class="error">Invalid tag extension name: ' .
3897 htmlspecialchars(
$name ) .
'</span>';
3904 if ( isset( $flags[
'markerType'] ) ) {
3905 $markerType = $flags[
'markerType'];
3909 if ( is_null( $attrText ) ) {
3912 if ( isset(
$params[
'attributes'] ) ) {
3913 foreach (
$params[
'attributes']
as $attrName => $attrValue ) {
3914 $attrText .=
' ' . htmlspecialchars( $attrName ) .
'="' .
3915 htmlspecialchars( $attrValue ) .
'"';
3918 if ( $content ===
null ) {
3919 $output =
"<$name$attrText/>";
3921 $close = is_null(
$params[
'close'] ) ?
'' : $frame->expand(
$params[
'close'] );
3922 if ( substr( $close, 0, $errorLen ) === $errorStr ) {
3926 $output =
"<$name$attrText>$content$close";
3930 if ( $markerType ===
'none' ) {
3932 } elseif ( $markerType ===
'nowiki' ) {
3933 $this->mStripState->addNoWiki( $marker,
$output );
3934 } elseif ( $markerType ===
'general' ) {
3935 $this->mStripState->addGeneral( $marker,
$output );
3937 throw new MWException( __METHOD__ .
': invalid marker type' );
3950 if ( $this->mIncludeSizes[
$type] + $size > $this->mOptions->getMaxIncludeSize() ) {
3953 $this->mIncludeSizes[
$type] += $size;
3964 $this->mExpensiveFunctionCount++;
3965 return $this->mExpensiveFunctionCount <= $this->mOptions->getExpensiveParserFunctionLimit();
3977 # The position of __TOC__ needs to be recorded
3979 if ( $mw->match( $text ) ) {
3980 $this->mShowToc =
true;
3981 $this->mForceTocPosition =
true;
3983 # Set a placeholder. At the end we'll fill it in with the TOC.
3984 $text = $mw->replace(
'<!--MWTOC\'"-->', $text, 1 );
3986 # Only keep the first one.
3987 $text = $mw->replace(
'', $text );
3990 # Now match and remove the rest of them
3992 $this->mDoubleUnderscores = $mwa->matchAndRemove( $text );
3994 if ( isset( $this->mDoubleUnderscores[
'nogallery'] ) ) {
3995 $this->mOutput->mNoGallery =
true;
3997 if ( isset( $this->mDoubleUnderscores[
'notoc'] ) && !$this->mForceTocPosition ) {
3998 $this->mShowToc =
false;
4000 if ( isset( $this->mDoubleUnderscores[
'hiddencat'] )
4003 $this->addTrackingCategory(
'hidden-category-category' );
4005 # (T10068) Allow control over whether robots index a page.
4006 # __INDEX__ always overrides __NOINDEX__, see T16899
4007 if ( isset( $this->mDoubleUnderscores[
'noindex'] ) && $this->mTitle->canUseNoindex() ) {
4008 $this->mOutput->setIndexPolicy(
'noindex' );
4009 $this->addTrackingCategory(
'noindex-category' );
4011 if ( isset( $this->mDoubleUnderscores[
'index'] ) && $this->mTitle->canUseNoindex() ) {
4012 $this->mOutput->setIndexPolicy(
'index' );
4013 $this->addTrackingCategory(
'index-category' );
4016 # Cache all double underscores in the database
4017 foreach ( $this->mDoubleUnderscores
as $key => $val ) {
4018 $this->mOutput->setProperty( $key,
'' );
4030 return $this->mOutput->addTrackingCategory( $msg, $this->mTitle );
4052 # Inhibit editsection links if requested in the page
4053 if ( isset( $this->mDoubleUnderscores[
'noeditsection'] ) ) {
4054 $maybeShowEditLink =
false;
4056 $maybeShowEditLink =
true;
4059 # Get all headlines for numbering them and adding funky stuff like [edit]
4060 # links - this is for later, but we need the number of headlines right now
4061 # NOTE: white space in headings have been trimmed in doHeadings. They shouldn't
4062 # be trimmed here since whitespace in HTML headings is significant.
4064 $numMatches = preg_match_all(
4065 '/<H(?P<level>[1-6])(?P<attrib>.*?>)(?P<header>[\s\S]*?)<\/H[1-6] *>/i',
4070 # if there are fewer than 4 headlines in the article, do not show TOC
4071 # unless it's been explicitly enabled.
4072 $enoughToc = $this->mShowToc &&
4073 ( ( $numMatches >= 4 ) || $this->mForceTocPosition );
4075 # Allow user to stipulate that a page should have a "new section"
4076 # link added via __NEWSECTIONLINK__
4077 if ( isset( $this->mDoubleUnderscores[
'newsectionlink'] ) ) {
4078 $this->mOutput->setNewSection(
true );
4081 # Allow user to remove the "new section"
4082 # link via __NONEWSECTIONLINK__
4083 if ( isset( $this->mDoubleUnderscores[
'nonewsectionlink'] ) ) {
4084 $this->mOutput->hideNewSection(
true );
4087 # if the string __FORCETOC__ (not case-sensitive) occurs in the HTML,
4088 # override above conditions and always show TOC above first header
4089 if ( isset( $this->mDoubleUnderscores[
'forcetoc'] ) ) {
4090 $this->mShowToc =
true;
4098 # Ugh .. the TOC should have neat indentation levels which can be
4099 # passed to the skin functions. These are determined here
4103 $sublevelCount = [];
4109 $markerRegex = self::MARKER_PREFIX .
"-h-(\d+)-" . self::MARKER_SUFFIX;
4110 $baseTitleText = $this->mTitle->getPrefixedDBkey();
4111 $oldType = $this->mOutputType;
4112 $this->setOutputType( self::OT_WIKI );
4113 $frame = $this->getPreprocessor()->newFrame();
4114 $root = $this->preprocessToDom( $origText );
4115 $node = $root->getFirstChild();
4120 $headlines = $numMatches !==
false ?
$matches[3] : [];
4122 foreach ( $headlines
as $headline ) {
4123 $isTemplate =
false;
4125 $sectionIndex =
false;
4127 $markerMatches = [];
4128 if ( preg_match(
"/^$markerRegex/", $headline, $markerMatches ) ) {
4129 $serial = $markerMatches[1];
4130 list( $titleText, $sectionIndex ) = $this->mHeadings[$serial];
4131 $isTemplate = ( $titleText != $baseTitleText );
4132 $headline = preg_replace(
"/^$markerRegex\\s*/",
"", $headline );
4136 $prevlevel = $level;
4138 $level =
$matches[1][$headlineCount];
4140 if ( $level > $prevlevel ) {
4141 # Increase TOC level
4143 $sublevelCount[$toclevel] = 0;
4145 $prevtoclevel = $toclevel;
4149 } elseif ( $level < $prevlevel && $toclevel > 1 ) {
4150 # Decrease TOC level, find level to jump to
4152 for ( $i = $toclevel; $i > 0; $i-- ) {
4153 if ( $levelCount[$i] == $level ) {
4154 # Found last matching level
4157 } elseif ( $levelCount[$i] < $level ) {
4158 # Found first matching level below current level
4168 # Unindent only if the previous toc level was shown :p
4170 $prevtoclevel = $toclevel;
4176 # No change in level, end TOC line
4182 $levelCount[$toclevel] = $level;
4184 # count number of headlines for each level
4185 $sublevelCount[$toclevel]++;
4187 for ( $i = 1; $i <= $toclevel; $i++ ) {
4188 if ( !empty( $sublevelCount[$i] ) ) {
4192 $numbering .= $this->getTargetLanguage()->formatNum( $sublevelCount[$i] );
4197 # The safe header is a version of the header text safe to use for links
4199 # Remove link placeholders by the link text.
4200 # <!--LINK number-->
4202 # link text with suffix
4203 # Do this before unstrip since link text can contain strip markers
4204 $safeHeadline = $this->replaceLinkHoldersText( $headline );
4206 # Avoid insertion of weird stuff like <math> by expanding the relevant sections
4207 $safeHeadline = $this->mStripState->unstripBoth( $safeHeadline );
4209 # Strip out HTML (first regex removes any tag not allowed)
4211 # * <sup> and <sub> (T10393)
4215 # * <span dir="rtl"> and <span dir="ltr"> (T37167)
4216 # * <s> and <strike> (T35715)
4217 # We strip any parameter from accepted tags (second regex), except dir="rtl|ltr" from <span>,
4218 # to allow setting directionality in toc items.
4219 $tocline = preg_replace(
4221 '#<(?!/?(span|sup|sub|bdi|i|b|s|strike)(?: [^>]*)?>).*?>#',
4222 '#<(/?(?:span(?: dir="(?:rtl|ltr)")?|sup|sub|bdi|i|b|s|strike))(?: .*?)?>#'
4228 # Strip '<span></span>', which is the result from the above if
4229 # <span id="foo"></span> is used to produce an additional anchor
4231 $tocline = str_replace(
'<span></span>',
'', $tocline );
4233 $tocline = trim( $tocline );
4235 # For the anchor, strip out HTML-y stuff period
4236 $safeHeadline = preg_replace(
'/<.*?>/',
'', $safeHeadline );
4237 $safeHeadline = Sanitizer::normalizeSectionNameWhitespace( $safeHeadline );
4239 # Save headline for section edit hint before it's escaped
4240 $headlineHint = $safeHeadline;
4242 # Decode HTML entities
4243 $safeHeadline = Sanitizer::decodeCharReferences( $safeHeadline );
4245 $safeHeadline = self::normalizeSectionName( $safeHeadline );
4247 $fallbackHeadline = Sanitizer::escapeIdForAttribute( $safeHeadline, Sanitizer::ID_FALLBACK );
4248 $linkAnchor = Sanitizer::escapeIdForLink( $safeHeadline );
4249 $safeHeadline = Sanitizer::escapeIdForAttribute( $safeHeadline, Sanitizer::ID_PRIMARY );
4250 if ( $fallbackHeadline === $safeHeadline ) {
4251 # No reason to have both (in fact, we can't)
4252 $fallbackHeadline =
false;
4255 # HTML IDs must be case-insensitively unique for IE compatibility (T12721).
4256 # @todo FIXME: We may be changing them depending on the current locale.
4257 $arrayKey = strtolower( $safeHeadline );
4258 if ( $fallbackHeadline ===
false ) {
4259 $fallbackArrayKey =
false;
4261 $fallbackArrayKey = strtolower( $fallbackHeadline );
4264 # Create the anchor for linking from the TOC to the section
4265 $anchor = $safeHeadline;
4266 $fallbackAnchor = $fallbackHeadline;
4267 if ( isset( $refers[$arrayKey] ) ) {
4269 for ( $i = 2; isset( $refers[
"${arrayKey}_$i"] ); ++$i );
4271 $linkAnchor .=
"_$i";
4272 $refers[
"${arrayKey}_$i"] =
true;
4274 $refers[$arrayKey] =
true;
4276 if ( $fallbackHeadline !==
false && isset( $refers[$fallbackArrayKey] ) ) {
4278 for ( $i = 2; isset( $refers[
"${fallbackArrayKey}_$i"] ); ++$i );
4279 $fallbackAnchor .=
"_$i";
4280 $refers[
"${fallbackArrayKey}_$i"] =
true;
4282 $refers[$fallbackArrayKey] =
true;
4285 # Don't number the heading if it is the only one (looks silly)
4286 if ( count(
$matches[3] ) > 1 && $this->mOptions->getNumberHeadings() ) {
4287 # the two are different if the line contains a link
4288 $headline = Html::element(
4290 [
'class' =>
'mw-headline-number' ],
4292 ) .
' ' . $headline;
4297 $numbering, $toclevel, ( $isTemplate ?
false : $sectionIndex ) );
4300 # Add the section to the section tree
4301 # Find the DOM node for this header
4302 $noOffset = ( $isTemplate || $sectionIndex ===
false );
4303 while ( $node && !$noOffset ) {
4304 if ( $node->getName() ===
'h' ) {
4305 $bits = $node->splitHeading();
4306 if ( $bits[
'i'] == $sectionIndex ) {
4310 $byteOffset += mb_strlen( $this->mStripState->unstripBoth(
4312 $node = $node->getNextSibling();
4315 'toclevel' => $toclevel,
4318 'number' => $numbering,
4319 'index' => ( $isTemplate ?
'T-' :
'' ) . $sectionIndex,
4320 'fromtitle' => $titleText,
4321 'byteoffset' => ( $noOffset ?
null : $byteOffset ),
4322 'anchor' => $anchor,
4325 # give headline the correct <h#> tag
4326 if ( $maybeShowEditLink && $sectionIndex !==
false ) {
4328 if ( $isTemplate ) {
4329 # Put a T flag in the section identifier, to indicate to extractSections()
4330 # that sections inside <includeonly> should be counted.
4331 $editsectionPage = $titleText;
4332 $editsectionSection =
"T-$sectionIndex";
4333 $editsectionContent =
null;
4335 $editsectionPage = $this->mTitle->getPrefixedText();
4336 $editsectionSection = $sectionIndex;
4337 $editsectionContent = $headlineHint;
4351 $editlink =
'<mw:editsection page="' . htmlspecialchars( $editsectionPage );
4352 $editlink .=
'" section="' . htmlspecialchars( $editsectionSection ) .
'"';
4353 if ( $editsectionContent !==
null ) {
4354 $editlink .=
'>' . $editsectionContent .
'</mw:editsection>';
4362 $matches[
'attrib'][$headlineCount], $anchor, $headline,
4363 $editlink, $fallbackAnchor );
4368 $this->setOutputType( $oldType );
4370 # Never ever show TOC if no headers
4371 if ( $numVisible < 1 ) {
4380 $this->mOutput->setTOCHTML( $toc );
4381 $toc = self::TOC_START . $toc . self::TOC_END;
4385 $this->mOutput->setSections( $tocraw );
4388 # split up and insert constructed headlines
4389 $blocks = preg_split(
'/<H[1-6].*?>[\s\S]*?<\/H[1-6]>/i', $text );
4394 foreach ( $blocks
as $block ) {
4396 if ( empty( $head[$i - 1] ) ) {
4397 $sections[$i] = $block;
4399 $sections[$i] = $head[$i - 1] . $block;
4412 Hooks::run(
'ParserSectionCreate', [ $this, $i, &$sections[$i], $maybeShowEditLink ] );
4417 if ( $enoughToc && $isMain && !$this->mForceTocPosition ) {
4420 $sections[0] = $sections[0] . $toc .
"\n";
4423 $full .= implode(
'', $sections );
4425 if ( $this->mForceTocPosition ) {
4426 return str_replace(
'<!--MWTOC\'"-->', $toc, $full );
4446 if ( $clearState ) {
4447 $magicScopeVariable = $this->lock();
4449 $this->startParse(
$title,
$options, self::OT_WIKI, $clearState );
4450 $this->setUser(
$user );
4453 $text = str_replace(
"\000",
'', $text );
4458 $text = TextContent::normalizeLineEndings( $text );
4460 if (
$options->getPreSaveTransform() ) {
4461 $text = $this->pstPass2( $text,
$user );
4463 $text = $this->mStripState->unstripBoth( $text );
4465 $this->setUser(
null ); # Reset
4481 # Note: This is the timestamp saved as hardcoded wikitext to
4482 # the database, we use $wgContLang here in order to give
4483 # everyone the same signature and use the default one rather
4484 # than the one selected in each user's preferences.
4486 $ts = $this->mOptions->getTimestamp();
4487 $timestamp = MWTimestamp::getLocalInstance( $ts );
4488 $ts = $timestamp->format(
'YmdHis' );
4489 $tzMsg = $timestamp->getTimezoneMessage()->inContentLanguage()->text();
4491 $d =
$wgContLang->timeanddate( $ts,
false,
false ) .
" ($tzMsg)";
4493 # Variable replacement
4494 # Because mOutputType is OT_WIKI, this will only process {{subst:xxx}} type tags
4495 $text = $this->replaceVariables( $text );
4497 # This works almost by chance, as the replaceVariables are done before the getUserSig(),
4498 # which may corrupt this parser instance via its wfMessage()->text() call-
4501 if ( strpos( $text,
'~~~' ) !==
false ) {
4502 $sigText = $this->getUserSig(
$user );
4503 $text = strtr( $text, [
4505 '~~~~' =>
"$sigText $d",
4508 # The main two signature forms used above are time-sensitive
4509 $this->mOutput->setFlag(
'user-signature' );
4512 # Context links ("pipe tricks"): [[|name]] and [[name (context)|]]
4513 $tc =
'[' . Title::legalChars() .
']';
4514 $nc =
'[ _0-9A-Za-z\x80-\xff-]'; # Namespaces can
use non-ascii!
4517 $p1 =
"/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\))\\|]]/";
4519 $p4 =
"/\[\[(:?$nc+:|:|)($tc+?)( ?($tc+))\\|]]/";
4521 $p3 =
"/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\)|)((?:, |,)$tc+|)\\|]]/";
4523 $p2 =
"/\[\[\\|($tc+)]]/";
4525 # try $p1 first, to turn "[[A, B (C)|]]" into "[[A, B (C)|A, B]]"
4526 $text = preg_replace( $p1,
'[[\\1\\2\\3|\\2]]', $text );
4527 $text = preg_replace( $p4,
'[[\\1\\2\\3|\\2]]', $text );
4528 $text = preg_replace( $p3,
'[[\\1\\2\\3\\4|\\2]]', $text );
4530 $t = $this->mTitle->getText();
4532 if ( preg_match(
"/^($nc+:|)$tc+?( \\($tc+\\))$/",
$t, $m ) ) {
4533 $text = preg_replace( $p2,
"[[$m[1]\\1$m[2]|\\1]]", $text );
4534 } elseif ( preg_match(
"/^($nc+:|)$tc+?(, $tc+|)$/",
$t, $m ) &&
"$m[1]$m[2]" !=
'' ) {
4535 $text = preg_replace( $p2,
"[[$m[1]\\1$m[2]|\\1]]", $text );
4537 # if there's no context, don't bother duplicating the title
4538 $text = preg_replace( $p2,
'[[\\1]]', $text );
4558 public function getUserSig( &$user, $nickname =
false, $fancySig =
null ) {
4563 # If not given, retrieve from the user object.
4564 if ( $nickname ===
false ) {
4565 $nickname =
$user->getOption(
'nickname' );
4568 if ( is_null( $fancySig ) ) {
4569 $fancySig =
$user->getBoolOption(
'fancysig' );
4572 $nickname = $nickname ==
null ?
$username : $nickname;
4576 wfDebug( __METHOD__ .
": $username has overlong signature.\n" );
4577 } elseif ( $fancySig !==
false ) {
4578 # Sig. might contain markup; validate this
4579 if ( $this->validateSig( $nickname ) !==
false ) {
4580 # Validated; clean up (if needed) and return it
4581 return $this->cleanSig( $nickname,
true );
4583 # Failed to validate; fall back to the default
4585 wfDebug( __METHOD__ .
": $username has bad XML tags in signature.\n" );
4589 # Make sure nickname doesnt get a sig in a sig
4590 $nickname = self::cleanSigInSig( $nickname );
4592 # If we're still here, make it a link to the user page
4595 $msgName =
$user->isAnon() ?
'signature-anon' :
'signature';
4597 return wfMessage( $msgName, $userText, $nickText )->inContentLanguage()
4598 ->title( $this->getTitle() )->text();
4608 return Xml::isWellFormedXmlFragment( $text ) ? $text :
false;
4624 $magicScopeVariable = $this->lock();
4628 # Option to disable this feature
4629 if ( !$this->mOptions->getCleanSignatures() ) {
4633 # @todo FIXME: Regex doesn't respect extension tags or nowiki
4634 # => Move this logic to braceSubstitution()
4636 $substRegex =
'/\{\{(?!(?:' . $substWord->getBaseRegex() .
'))/x' . $substWord->getRegexCase();
4637 $substText =
'{{' . $substWord->getSynonym( 0 );
4639 $text = preg_replace( $substRegex, $substText, $text );
4640 $text = self::cleanSigInSig( $text );
4641 $dom = $this->preprocessToDom( $text );
4642 $frame = $this->getPreprocessor()->newFrame();
4643 $text = $frame->expand( $dom );
4646 $text = $this->mStripState->unstripBoth( $text );
4659 $text = preg_replace(
'/~{3,5}/',
'', $text );
4673 $outputType, $clearState =
true
4685 $outputType, $clearState =
true
4687 $this->setTitle(
$title );
4689 $this->setOutputType( $outputType );
4690 if ( $clearState ) {
4691 $this->clearState();
4704 static $executing =
false;
4706 # Guard against infinite recursion
4747 public function setHook( $tag, callable $callback ) {
4748 $tag = strtolower( $tag );
4749 if ( preg_match(
'/[<>\r\n]/', $tag, $m ) ) {
4750 throw new MWException(
"Invalid character {$m[0]} in setHook('$tag', ...) call" );
4752 $oldVal = isset( $this->mTagHooks[$tag] ) ? $this->mTagHooks[$tag] :
null;
4753 $this->mTagHooks[$tag] = $callback;
4754 if ( !in_array( $tag, $this->mStripList ) ) {
4755 $this->mStripList[] = $tag;
4779 $tag = strtolower( $tag );
4780 if ( preg_match(
'/[<>\r\n]/', $tag, $m ) ) {
4781 throw new MWException(
"Invalid character {$m[0]} in setTransparentHook('$tag', ...) call" );
4783 $oldVal = isset( $this->mTransparentTagHooks[$tag] ) ? $this->mTransparentTagHooks[$tag] :
null;
4784 $this->mTransparentTagHooks[$tag] = $callback;
4793 $this->mTagHooks = [];
4794 $this->mFunctionTagHooks = [];
4795 $this->mStripList = $this->mDefaultStripList;
4844 $oldVal = isset( $this->mFunctionHooks[$id] ) ? $this->mFunctionHooks[$id][0] :
null;
4845 $this->mFunctionHooks[$id] = [ $callback, $flags ];
4847 # Add to function cache
4850 throw new MWException( __METHOD__ .
'() expecting a magic word identifier.' );
4853 $synonyms = $mw->getSynonyms();
4854 $sensitive = intval( $mw->isCaseSensitive() );
4856 foreach ( $synonyms
as $syn ) {
4858 if ( !$sensitive ) {
4862 if ( !( $flags & self::SFH_NO_HASH ) ) {
4865 # Remove trailing colon
4866 if ( substr( $syn, -1, 1 ) ===
':' ) {
4867 $syn = substr( $syn, 0, -1 );
4869 $this->mFunctionSynonyms[$sensitive][$syn] = $id;
4880 return array_keys( $this->mFunctionHooks );
4894 $tag = strtolower( $tag );
4895 if ( preg_match(
'/[<>\r\n]/', $tag, $m ) ) {
4896 throw new MWException(
"Invalid character {$m[0]} in setFunctionTagHook('$tag', ...) call" );
4898 $old = isset( $this->mFunctionTagHooks[$tag] ) ?
4899 $this->mFunctionTagHooks[$tag] :
null;
4900 $this->mFunctionTagHooks[$tag] = [ $callback, $flags ];
4902 if ( !in_array( $tag, $this->mStripList ) ) {
4903 $this->mStripList[] = $tag;
4917 $this->mLinkHolders->replace( $text );
4928 return $this->mLinkHolders->replaceText( $text );
4946 if ( isset(
$params[
'mode'] ) ) {
4952 }
catch ( Exception
$e ) {
4957 $ig->setContextTitle( $this->mTitle );
4958 $ig->setShowBytes(
false );
4959 $ig->setShowDimensions(
false );
4960 $ig->setShowFilename(
false );
4961 $ig->setParser( $this );
4962 $ig->setHideBadImages();
4963 $ig->setAttributes( Sanitizer::validateTagAttributes(
$params,
'ul' ) );
4965 if ( isset(
$params[
'showfilename'] ) ) {
4966 $ig->setShowFilename(
true );
4968 $ig->setShowFilename(
false );
4970 if ( isset(
$params[
'caption'] ) ) {
4971 $caption =
$params[
'caption'];
4972 $caption = htmlspecialchars( $caption );
4973 $caption = $this->replaceInternalLinks( $caption );
4974 $ig->setCaptionHtml( $caption );
4976 if ( isset(
$params[
'perrow'] ) ) {
4977 $ig->setPerRow(
$params[
'perrow'] );
4979 if ( isset(
$params[
'widths'] ) ) {
4980 $ig->setWidths(
$params[
'widths'] );
4982 if ( isset(
$params[
'heights'] ) ) {
4983 $ig->setHeights(
$params[
'heights'] );
4985 $ig->setAdditionalOptions(
$params );
4989 Hooks::run(
'BeforeParserrenderImageGallery', [ &
$parser, &$ig ] );
4993 # match lines like these:
4994 # Image:someimage.jpg|This is some image
5002 if ( strpos(
$matches[0],
'%' ) !==
false ) {
5006 if ( is_null(
$title ) ) {
5007 # Bogus title. Ignore these so we don't bomb out later.
5011 # We need to get what handler the file uses, to figure out parameters.
5012 # Note, a hook can overide the file name, and chose an entirely different
5013 # file (which potentially could be of a different type and have different handler).
5016 Hooks::run(
'BeforeParserFetchFileAndTitle',
5018 # Don't register it now, as TraditionalImageGallery does that later.
5020 $handler = $file ? $file->getHandler() :
false;
5023 'img_alt' =>
'gallery-internal-alt',
5024 'img_link' =>
'gallery-internal-link',
5027 $paramMap = $paramMap +
$handler->getParamMap();
5030 unset( $paramMap[
'img_width'] );
5038 $handlerOptions = [];
5052 foreach ( $parameterMatches
as $parameterMatch ) {
5053 list( $magicName, $match ) = $mwArray->matchVariableStartToEnd( $parameterMatch );
5055 $paramName = $paramMap[$magicName];
5057 switch ( $paramName ) {
5058 case 'gallery-internal-alt':
5059 $alt = $this->stripAltText( $match,
false );
5061 case 'gallery-internal-link':
5062 $linkValue = strip_tags( $this->replaceLinkHoldersText( $match ) );
5063 $chars = self::EXT_LINK_URL_CLASS;
5064 $addr = self::EXT_LINK_ADDR;
5065 $prots = $this->mUrlProtocols;
5067 if ( preg_match(
'/^-{R|(.*)}-$/', $linkValue ) ) {
5070 $linkValue = substr( $linkValue, 4, -2 );
5072 if ( preg_match(
"/^($prots)$addr$chars*$/u", $linkValue ) ) {
5074 $this->mOutput->addExternalLink(
$link );
5076 $localLinkTitle = Title::newFromText( $linkValue );
5077 if ( $localLinkTitle !==
null ) {
5078 $this->mOutput->addLink( $localLinkTitle );
5079 $link = $localLinkTitle->getLinkURL();
5085 if (
$handler->validateParam( $paramName, $match ) ) {
5086 $handlerOptions[$paramName] = $match;
5089 wfDebug(
"$parameterMatch failed parameter validation\n" );
5090 $label =
'|' . $parameterMatch;
5096 $label =
'|' . $parameterMatch;
5100 $label = substr( $label, 1 );
5103 $ig->add(
$title, $label, $alt,
$link, $handlerOptions );
5105 $html = $ig->toHTML();
5106 Hooks::run(
'AfterParserFetchFileAndTitle', [ $this, $ig, &
$html ] );
5116 $handlerClass = get_class(
$handler );
5120 if ( !isset( $this->mImageParams[$handlerClass] ) ) {
5121 # Initialise static lists
5122 static $internalParamNames = [
5123 'horizAlign' => [
'left',
'right',
'center',
'none' ],
5124 'vertAlign' => [
'baseline',
'sub',
'super',
'top',
'text-top',
'middle',
5125 'bottom',
'text-bottom' ],
5126 'frame' => [
'thumbnail',
'manualthumb',
'framed',
'frameless',
5127 'upright',
'border',
'link',
'alt',
'class' ],
5129 static $internalParamMap;
5130 if ( !$internalParamMap ) {
5131 $internalParamMap = [];
5132 foreach ( $internalParamNames
as $type => $names ) {
5139 $magicName = str_replace(
'-',
'_',
"img_$name" );
5140 $internalParamMap[$magicName] = [
$type,
$name ];
5145 # Add handler params
5146 $paramMap = $internalParamMap;
5148 $handlerParamMap =
$handler->getParamMap();
5149 foreach ( $handlerParamMap
as $magic => $paramName ) {
5150 $paramMap[$magic] = [
'handler', $paramName ];
5153 $this->mImageParams[$handlerClass] = $paramMap;
5154 $this->mImageParamsMagicArray[$handlerClass] =
new MagicWordArray( array_keys( $paramMap ) );
5156 return [ $this->mImageParams[$handlerClass], $this->mImageParamsMagicArray[$handlerClass] ];
5168 # Check if the options text is of the form "options|alt text"
5170 # * thumbnail make a thumbnail with enlarge-icon and caption, alignment depends on lang
5171 # * left no resizing, just left align. label is used for alt= only
5172 # * right same, but right aligned
5173 # * none same, but not aligned
5174 # * ___px scale to ___ pixels width, no aligning. e.g. use in taxobox
5175 # * center center the image
5176 # * frame Keep original image size, no magnify-button.
5177 # * framed Same as "frame"
5178 # * frameless like 'thumb' but without a frame. Keeps user preferences for width
5179 # * upright reduce width for upright images, rounded to full __0 px
5180 # * border draw a 1px border around the image
5181 # * alt Text for HTML alt attribute (defaults to empty)
5182 # * class Set a class for img node
5183 # * link Set the target of the image link. Can be external, interwiki, or local
5184 # vertical-align values (no % or length right now):
5194 # Protect LanguageConverter markup when splitting into parts
5199 # Give extensions a chance to select the file revision for us
5202 Hooks::run(
'BeforeParserFetchFileAndTitle',
5204 # Fetch and register the file (file title may be different via hooks)
5208 $handler = $file ? $file->getHandler() :
false;
5210 list( $paramMap, $mwArray ) = $this->getImageParams(
$handler );
5213 $this->addTrackingCategory(
'broken-file-category' );
5216 # Process the input parameters
5218 $params = [
'frame' => [],
'handler' => [],
5219 'horizAlign' => [],
'vertAlign' => [] ];
5220 $seenformat =
false;
5221 foreach ( $parts
as $part ) {
5222 $part = trim( $part );
5223 list( $magicName,
$value ) = $mwArray->matchVariableStartToEnd( $part );
5225 if ( isset( $paramMap[$magicName] ) ) {
5226 list(
$type, $paramName ) = $paramMap[$magicName];
5228 # Special case; width and height come in one variable together
5229 if (
$type ===
'handler' && $paramName ===
'width' ) {
5230 $parsedWidthParam = self::parseWidthParam(
$value );
5231 if ( isset( $parsedWidthParam[
'width'] ) ) {
5232 $width = $parsedWidthParam[
'width'];
5233 if (
$handler->validateParam(
'width', $width ) ) {
5238 if ( isset( $parsedWidthParam[
'height'] ) ) {
5239 $height = $parsedWidthParam[
'height'];
5240 if (
$handler->validateParam(
'height', $height ) ) {
5245 # else no validation -- T15436
5247 if (
$type ===
'handler' ) {
5248 # Validate handler parameter
5251 # Validate internal parameters
5252 switch ( $paramName ) {
5256 # @todo FIXME: Possibly check validity here for
5257 # manualthumb? downstream behavior seems odd with
5258 # missing manual thumbs.
5263 $chars = self::EXT_LINK_URL_CLASS;
5264 $addr = self::EXT_LINK_ADDR;
5265 $prots = $this->mUrlProtocols;
5267 $paramName =
'no-link';
5270 } elseif ( preg_match(
"/^((?i)$prots)/",
$value ) ) {
5271 if ( preg_match(
"/^((?i)$prots)$addr$chars*$/u",
$value, $m ) ) {
5272 $paramName =
'link-url';
5273 $this->mOutput->addExternalLink(
$value );
5274 if ( $this->mOptions->getExternalLinkTarget() ) {
5275 $params[
$type][
'link-target'] = $this->mOptions->getExternalLinkTarget();
5280 $linkTitle = Title::newFromText(
$value );
5282 $paramName =
'link-title';
5284 $this->mOutput->addLink( $linkTitle );
5293 $validated = !$seenformat;
5297 # Most other things appear to be empty or numeric...
5298 $validated = (
$value ===
false || is_numeric( trim(
$value ) ) );
5307 if ( !$validated ) {
5312 # Process alignment parameters
5313 if (
$params[
'horizAlign'] ) {
5320 $params[
'frame'][
'caption'] = $caption;
5322 # Will the image be presented in a frame, with the caption below?
5323 $imageIsFramed = isset(
$params[
'frame'][
'frame'] )
5324 || isset(
$params[
'frame'][
'framed'] )
5325 || isset(
$params[
'frame'][
'thumbnail'] )
5326 || isset(
$params[
'frame'][
'manualthumb'] );
5328 # In the old days, [[Image:Foo|text...]] would set alt text. Later it
5329 # came to also set the caption, ordinary text after the image -- which
5330 # makes no sense, because that just repeats the text multiple times in
5331 # screen readers. It *also* came to set the title attribute.
5332 # Now that we have an alt attribute, we should not set the alt text to
5333 # equal the caption: that's worse than useless, it just repeats the
5334 # text. This is the framed/thumbnail case. If there's no caption, we
5335 # use the unnamed parameter for alt text as well, just for the time be-
5336 # ing, if the unnamed param is set and the alt param is not.
5337 # For the future, we need to figure out if we want to tweak this more,
5338 # e.g., introducing a title= parameter for the title; ignoring the un-
5339 # named parameter entirely for images without a caption; adding an ex-
5340 # plicit caption= parameter and preserving the old magic unnamed para-
5342 if ( $imageIsFramed ) { # Framed image
5343 if ( $caption ===
'' && !isset(
$params[
'frame'][
'alt'] ) ) {
5344 # No caption or alt text, add the filename as the alt text so
5345 # that screen readers at least get some description of the image
5348 # Do not set $params['frame']['title'] because tooltips don't make sense
5350 }
else { # Inline image
5351 if ( !isset(
$params[
'frame'][
'alt'] ) ) {
5352 # No alt text, use the "caption" for the alt text
5353 if ( $caption !==
'' ) {
5354 $params[
'frame'][
'alt'] = $this->stripAltText( $caption, $holders );
5356 # No caption, fall back to using the filename for the
5361 # Use the "caption" for the tooltip text
5362 $params[
'frame'][
'title'] = $this->stripAltText( $caption, $holders );
5365 Hooks::run(
'ParserMakeImageParams', [
$title, $file, &
$params, $this ] );
5367 # Linker does the rest
5370 $time, $descQuery, $this->mOptions->getThumbSize() );
5372 # Give the handler a chance to modify the parser object
5374 $handler->parserTransformHook( $this, $file );
5386 # Strip bad stuff out of the title (tooltip). We can't just use
5387 # replaceLinkHoldersText() here, because if this function is called
5388 # from replaceInternalLinks2(), mLinkHolders won't be up-to-date.
5390 $tooltip = $holders->replaceText( $caption );
5392 $tooltip = $this->replaceLinkHoldersText( $caption );
5395 # make sure there are no placeholders in thumbnail attributes
5396 # that are later expanded to html- so expand them now and
5398 $tooltip = $this->mStripState->unstripBoth( $tooltip );
5399 $tooltip = Sanitizer::stripAllTags( $tooltip );
5410 wfDebug(
"Parser output marked as uncacheable.\n" );
5411 if ( !$this->mOutput ) {
5413 " can only be called when actually parsing something" );
5415 $this->mOutput->updateCacheExpiry( 0 );
5427 $text = $this->replaceVariables( $text, $frame );
5428 $text = $this->mStripState->unstripBoth( $text );
5439 array_keys( $this->mTransparentTagHooks ),
5440 array_keys( $this->mTagHooks ),
5441 array_keys( $this->mFunctionTagHooks )
5457 $elements = array_keys( $this->mTransparentTagHooks );
5458 $text = self::extractTagsAndParams( $elements, $text,
$matches );
5462 list( $element, $content,
$params, $tag ) = $data;
5463 $tagName = strtolower( $element );
5464 if ( isset( $this->mTransparentTagHooks[$tagName] ) ) {
5465 $output = call_user_func_array(
5466 $this->mTransparentTagHooks[$tagName],
5472 $replacements[$marker] =
$output;
5474 return strtr( $text, $replacements );
5509 $magicScopeVariable = $this->lock();
5512 $frame = $this->getPreprocessor()->newFrame();
5514 # Process section extraction flags
5516 $sectionParts = explode(
'-', $sectionId );
5517 $sectionIndex = array_pop( $sectionParts );
5518 foreach ( $sectionParts
as $part ) {
5519 if ( $part ===
'T' ) {
5520 $flags |= self::PTD_FOR_INCLUSION;
5524 # Check for empty input
5525 if ( strval( $text ) ===
'' ) {
5526 # Only sections 0 and T-0 exist in an empty document
5527 if ( $sectionIndex == 0 ) {
5528 if ( $mode ===
'get' ) {
5534 if ( $mode ===
'get' ) {
5542 # Preprocess the text
5543 $root = $this->preprocessToDom( $text, $flags );
5545 # <h> nodes indicate section breaks
5546 # They can only occur at the top level, so we can find them by iterating the root's children
5547 $node = $root->getFirstChild();
5549 # Find the target section
5550 if ( $sectionIndex == 0 ) {
5551 # Section zero doesn't nest, level=big
5552 $targetLevel = 1000;
5555 if ( $node->getName() ===
'h' ) {
5556 $bits = $node->splitHeading();
5557 if ( $bits[
'i'] == $sectionIndex ) {
5558 $targetLevel = $bits[
'level'];
5562 if ( $mode ===
'replace' ) {
5565 $node = $node->getNextSibling();
5571 if ( $mode ===
'get' ) {
5578 # Find the end of the section, including nested sections
5580 if ( $node->getName() ===
'h' ) {
5581 $bits = $node->splitHeading();
5582 $curLevel = $bits[
'level'];
5583 if ( $bits[
'i'] != $sectionIndex && $curLevel <= $targetLevel ) {
5587 if ( $mode ===
'get' ) {
5590 $node = $node->getNextSibling();
5593 # Write out the remainder (in replace mode only)
5594 if ( $mode ===
'replace' ) {
5595 # Output the replacement text
5596 # Add two newlines on -- trailing whitespace in $newText is conventionally
5597 # stripped by the editor, so we need both newlines to restore the paragraph gap
5598 # Only add trailing whitespace if there is newText
5599 if ( $newText !=
"" ) {
5600 $outText .= $newText .
"\n\n";
5605 $node = $node->getNextSibling();
5609 if ( is_string( $outText ) ) {
5610 # Re-insert stripped tags
5611 $outText = rtrim( $this->mStripState->unstripBoth( $outText ) );
5631 public function getSection( $text, $sectionId, $defaultText =
'' ) {
5632 return $this->extractSections( $text, $sectionId,
'get', $defaultText );
5648 return $this->extractSections( $oldText, $sectionId,
'replace', $newText );
5657 return $this->mRevisionId;
5667 if ( !is_null( $this->mRevisionObject ) ) {
5668 return $this->mRevisionObject;
5670 if ( is_null( $this->mRevisionId ) ) {
5674 $rev = call_user_func(
5675 $this->mOptions->getCurrentRevisionCallback(), $this->getTitle(), $this
5678 # If the parse is for a new revision, then the callback should have
5679 # already been set to force the object and should match mRevisionId.
5680 # If not, try to fetch by mRevisionId for sanity.
5681 if (
$rev &&
$rev->getId() != $this->mRevisionId ) {
5682 $rev = Revision::newFromId( $this->mRevisionId );
5685 $this->mRevisionObject =
$rev;
5687 return $this->mRevisionObject;
5696 if ( is_null( $this->mRevisionTimestamp ) ) {
5699 $revObject = $this->getRevisionObject();
5700 $timestamp = $revObject ? $revObject->getTimestamp() :
wfTimestampNow();
5702 # The cryptic '' timezone parameter tells to use the site-default
5703 # timezone offset instead of the user settings.
5704 # Since this value will be saved into the parser cache, served
5705 # to other users, and potentially even used inside links and such,
5706 # it needs to be consistent for all visitors.
5707 $this->mRevisionTimestamp =
$wgContLang->userAdjust( $timestamp,
'' );
5710 return $this->mRevisionTimestamp;
5719 if ( is_null( $this->mRevisionUser ) ) {
5720 $revObject = $this->getRevisionObject();
5722 # if this template is subst: the revision id will be blank,
5723 # so just use the current user's name
5725 $this->mRevisionUser = $revObject->getUserText();
5726 } elseif ( $this->ot[
'wiki'] || $this->mOptions->getIsPreview() ) {
5727 $this->mRevisionUser = $this->getUser()->getName();
5730 return $this->mRevisionUser;
5739 if ( is_null( $this->mRevisionSize ) ) {
5740 $revObject = $this->getRevisionObject();
5742 # if this variable is subst: the revision id will be blank,
5743 # so just use the parser input size, because the own substituation
5744 # will change the size.
5746 $this->mRevisionSize = $revObject->getSize();
5748 $this->mRevisionSize = $this->mInputSize;
5751 return $this->mRevisionSize;
5760 $this->mDefaultSort =
$sort;
5761 $this->mOutput->setProperty(
'defaultsort',
$sort );
5775 if ( $this->mDefaultSort !==
false ) {
5776 return $this->mDefaultSort;
5789 return $this->mDefaultSort;
5793 $text = Sanitizer::normalizeSectionNameWhitespace( $text );
5794 $text = Sanitizer::decodeCharReferences( $text );
5795 $text = self::normalizeSectionName( $text );
5800 return '#' . Sanitizer::escapeIdForLink( $sectionName );
5807 $id = Sanitizer::escapeIdForAttribute( $sectionName, Sanitizer::ID_FALLBACK );
5809 $id = Sanitizer::escapeIdForLink( $sectionName );
5824 # Strip out wikitext links(they break the anchor)
5825 $text = $this->stripSectionName( $text );
5826 $sectionName = self::getSectionNameFromStrippedText( $text );
5827 return self::makeAnchor( $sectionName );
5840 # Strip out wikitext links(they break the anchor)
5841 $text = $this->stripSectionName( $text );
5842 $sectionName = self::getSectionNameFromStrippedText( $text );
5843 return self::makeLegacyAnchor( $sectionName );
5852 $sectionName = self::getSectionNameFromStrippedText( $text );
5853 return self::makeAnchor( $sectionName );
5863 # T90902: ensure the same normalization is applied for IDs as to links
5864 $titleParser = MediaWikiServices::getInstance()->getTitleParser();
5867 $parts = $titleParser->splitTitleString(
"#$text" );
5871 return $parts[
'fragment'];
5889 # Strip internal link markup
5890 $text = preg_replace(
'/\[\[:?([^[|]+)\|([^[]+)\]\]/',
'$2', $text );
5891 $text = preg_replace(
'/\[\[:?([^[]+)\|?\]\]/',
'$1', $text );
5893 # Strip external link markup
5894 # @todo FIXME: Not tolerant to blank link text
5896 # on how many empty links there are on the page - need to figure that out.
5897 $text = preg_replace(
'/\[(?i:' . $this->mUrlProtocols .
')([^ ]+?) ([^[]+)\]/',
'$2', $text );
5899 # Parse wikitext quotes (italics & bold)
5900 $text = $this->doQuotes( $text );
5918 $outputType = self::OT_HTML
5920 $magicScopeVariable = $this->lock();
5923 $text = $this->replaceVariables( $text );
5924 $text = $this->mStripState->unstripBoth( $text );
5925 $text = Sanitizer::removeHTMLtags( $text );
5946 return $this->testSrvus( $text,
$title,
$options, self::OT_PREPROCESS );
5968 while ( $i < strlen(
$s ) ) {
5969 $markerStart = strpos(
$s, self::MARKER_PREFIX, $i );
5970 if ( $markerStart ===
false ) {
5971 $out .= call_user_func( $callback, substr(
$s, $i ) );
5974 $out .= call_user_func( $callback, substr(
$s, $i, $markerStart - $i ) );
5975 $markerEnd = strpos(
$s, self::MARKER_SUFFIX, $markerStart );
5976 if ( $markerEnd ===
false ) {
5977 $out .= substr(
$s, $markerStart );
5980 $markerEnd += strlen( self::MARKER_SUFFIX );
5981 $out .= substr(
$s, $markerStart, $markerEnd - $markerStart );
5996 return $this->mStripState->killMarkers( $text );
6020 'version' => self::HALF_PARSED_VERSION,
6021 'stripState' => $this->mStripState->getSubState( $text ),
6022 'linkHolders' => $this->mLinkHolders->getSubArray( $text )
6045 if ( !isset( $data[
'version'] ) || $data[
'version'] != self::HALF_PARSED_VERSION ) {
6046 throw new MWException( __METHOD__ .
': invalid version' );
6049 # First, extract the strip state.
6050 $texts = [ $data[
'text'] ];
6051 $texts = $this->mStripState->merge( $data[
'stripState'], $texts );
6053 # Now renumber links
6054 $texts = $this->mLinkHolders->mergeForeign( $data[
'linkHolders'], $texts );
6056 # Should be good to go.
6072 return isset( $data[
'version'] ) && $data[
'version'] == self::HALF_PARSED_VERSION;
6085 $parsedWidthParam = [];
6087 return $parsedWidthParam;
6090 # (T15500) In both cases (width/height and width only),
6091 # permit trailing "px" for backward compatibility.
6092 if ( $parseHeight && preg_match(
'/^([0-9]*)x([0-9]*)\s*(?:px)?\s*$/',
$value, $m ) ) {
6093 $width = intval( $m[1] );
6094 $height = intval( $m[2] );
6095 $parsedWidthParam[
'width'] = $width;
6096 $parsedWidthParam[
'height'] = $height;
6097 } elseif ( preg_match(
'/^[0-9]*\s*(?:px)?\s*$/',
$value ) ) {
6098 $width = intval(
$value );
6099 $parsedWidthParam[
'width'] = $width;
6101 return $parsedWidthParam;
6114 if ( $this->mInParse ) {
6115 throw new MWException(
"Parser state cleared while parsing. "
6116 .
"Did you call Parser::parse recursively? Lock is held by: " . $this->mInParse );
6122 $this->mInParse =
$e->getTraceAsString();
6124 $recursiveCheck =
new ScopedCallback(
function () {
6125 $this->mInParse =
false;
6128 return $recursiveCheck;
6143 if ( preg_match(
'/^<p>(.*)\n?<\/p>\n?$/sU',
$html, $m ) ) {
6144 if ( strpos( $m[1],
'</p>' ) ===
false ) {
6164 if ( $this->mInParse ) {
6178 OutputPage::setupOOUI();
6179 $this->mOutput->setEnableOOUI(
true );
and(b) You must cause any modified files to carry prominent notices stating that You changed the files
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for and distribution as defined by Sections through of this document Licensor shall mean the copyright owner or entity authorized by the copyright owner that is granting the License Legal Entity shall mean the union of the acting entity and all other entities that control are controlled by or are under common control with that entity For the purposes of this definition control direct or to cause the direction or management of such whether by contract or including but not limited to software source documentation and configuration files Object form shall mean any form resulting from mechanical transformation or translation of a Source including but not limited to compiled object generated and conversions to other media types Work shall mean the work of whether in Source or Object made available under the as indicated by a copyright notice that is included in or attached to the whether in Source or Object that is based or other modifications as a an original work of authorship For the purposes of this Derivative Works shall not include works that remain separable or merely the Work and Derivative Works thereof Contribution shall mean any work of including the original version of the Work and any modifications or additions to that Work or Derivative Works that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner For the purposes of this submitted means any form of or written communication sent to the Licensor or its including but not limited to communication on electronic mailing source code control and issue tracking systems that are managed or on behalf the Licensor for the purpose of discussing and improving the but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as Not a Contribution Contributor shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work Grant of Copyright License Subject to the terms and conditions of this each Contributor hereby grants to You a non no royalty irrevocable copyright license to prepare Derivative Works publicly display
If you want to remove the page from your watchlist later
$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,...
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
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.
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.
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.
static getSectionNameFromStrippedText( $text)
stripAltText( $caption, $holders)
getOptions()
Get the ParserOptions object.
cleanSig( $text, $parsing=false)
Clean up signature text.
preSaveTransform( $text, Title $title, User $user, ParserOptions $options, $clearState=true)
Transform wiki markup when saving a page by doing "\\r\\n" -> "\\n" conversion, substituting signatur...
startParse(Title $title=null, ParserOptions $options, $outputType, $clearState=true)
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.
static makeLegacyAnchor( $sectionName)
static guessSectionNameFromStrippedText( $text)
Like guessSectionNameFromWikiText(), but takes already-stripped text as input.
doMagicLinks( $text)
Replace special strings like "ISBN xxx" and "RFC xxx" with magic external links.
unserializeHalfParsedText( $data)
Load the parser state given in the $data array, which is assumed to have been generated by serializeH...
limitationWarn( $limitationType, $current='', $max='')
Warn the user when a parser limitation is reached Will warn at most once the user per limitation type...
LinkHolderArray $mLinkHolders
makeImage( $title, $options, $holders=false)
Parse image options text and use it to make an image.
disableCache()
Set a flag in the output object indicating that the content is dynamic and shouldn't be cached.
pstPass2( $text, $user)
Pre-save transform helper function.
transformMsg( $text, $options, $title=null)
Wrapper for preprocess()
getRevisionSize()
Get the size of the revision.
extractSections( $text, $sectionId, $mode, $newText='')
Break wikitext input into sections, and either pull or replace some particular section's text.
stripSectionName( $text)
Strips a text string of wikitext for use in a section anchor.
static normalizeSectionName( $text)
Apply the same normalization as code making links to this section would.
makeFreeExternalLink( $url, $numPostProto)
Make a free external link, given a user-supplied URL.
recursivePreprocess( $text, $frame=false)
Recursive parser entry point that can be called from an extension tag hook.
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)
makeLimitReport()
Set the limit report data in the current ParserOutput, and return the limit report HTML comment.
MagicWordArray $mVariables
getStripList()
Get a list of strippable XML-like elements.
setUser( $user)
Set the current user.
markerSkipCallback( $s, $callback)
Call a callback function on all regions of the given text that are not inside strip markers,...
setHook( $tag, callable $callback)
Create an HTML-style tag, e.g.
doDoubleUnderscore( $text)
Strip double-underscore items like NOGALLERY and NOTOC Fills $this->mDoubleUnderscores,...
argSubstitution( $piece, $frame)
Triple brace replacement – used for template arguments.
replaceInternalLinks( $s)
Process [[ ]] wikilinks.
array $mLangLinkLanguages
Array with the language name of each language link (i.e.
recursiveTagParse( $text, $frame=false)
Half-parse wikitext to half-parsed HTML.
string $mUniqPrefix
Deprecated accessor for the strip marker prefix.
fetchFileAndTitle( $title, $options=[])
Fetch a file and its title and register a reference to it.
getSection( $text, $sectionId, $defaultText='')
This function returns the text of a section, specified by a number ($section).
internalParse( $text, $isMain=true, $frame=false)
Helper function for parse() that transforms wiki markup into half-parsed HTML.
static extractTagsAndParams( $elements, $text, &$matches)
Replaces all occurrences of HTML-style comments and the given tags in the text with a random marker a...
replaceSection( $oldText, $sectionId, $newText)
This function returns $oldtext after the content of the section specified by $section has been replac...
interwikiTransclude( $title, $action)
Transclude an interwiki link.
setTitle( $t)
Set the context title.
incrementIncludeSize( $type, $size)
Increment an include size counter.
maybeDoSubpageLink( $target, &$text)
Handle link to subpage if necessary.
getRevisionObject()
Get the revision object for $this->mRevisionId.
doBlockLevels( $text, $linestart)
Make lists from lines starting with ':', '*', '#', etc.
fetchTemplateAndTitle( $title)
Fetch the unparsed text of a template and register a reference to it.
getDefaultSort()
Accessor for $mDefaultSort Will use the empty string if none is set.
replaceLinkHoldersText( $text)
Replace "<!--LINK-->" link placeholders with plain text of links (not HTML-formatted).
getFunctionHooks()
Get all registered function hook identifiers.
doAllQuotes( $text)
Replace single quotes with HTML markup.
Preprocessor $mPreprocessor
fetchCurrentRevisionOfTitle( $title)
Fetch the current revision of a given title.
getOutput()
Get the ParserOutput object.
Options( $x=null)
Accessor/mutator for the ParserOptions object.
Title( $x=null)
Accessor/mutator for the Title object.
recursiveTagParseFully( $text, $frame=false)
Fully parse wikitext to fully parsed HTML.
guessSectionNameFromWikiText( $text)
Try to guess the section anchor name based on a wikitext fragment presumably extracted from a heading...
clearTagHooks()
Remove all tag hooks.
replaceLinkHolders(&$text, $options=0)
Replace "<!--LINK-->" link placeholders with actual links, in the buffer Placeholders created in Link...
getRevisionId()
Get the ID of the revision we are parsing.
static parseWidthParam( $value, $parseHeight=true)
Parsed a width param of imagelink like 300px or 200x300px.
validateSig( $text)
Check that the user's signature contains no bad XML.
getPreprocessor()
Get a preprocessor object.
guessLegacySectionNameFromWikiText( $text)
Same as guessSectionNameFromWikiText(), but produces legacy anchors instead, if possible.
OutputType( $x=null)
Accessor/mutator for the output type.
__destruct()
Reduce memory usage to reduce the impact of circular references.
fetchFile( $title, $options=[])
Fetch a file and its title and register a reference to it.
getUser()
Get a User object either from $this->mUser, if set, or from the ParserOptions object otherwise.
doQuotes( $text)
Helper function for doAllQuotes()
fetchTemplate( $title)
Fetch the unparsed text of a template and register a reference to it.
doHeadings( $text)
Parse headers and return html.
insertStripItem( $text)
Add an item to the strip state Returns the unique tag which must be inserted into the stripped text T...
setTransparentTagHook( $tag, callable $callback)
As setHook(), but letting the contents be parsed.
incrementExpensiveFunctionCount()
Increment the expensive function count.
testPreprocess( $text, Title $title, ParserOptions $options)
static makeAnchor( $sectionName)
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.
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 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...
Represents a title within MediaWiki.
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
=Architecture==Two class hierarchies are used to provide the functionality associated with the different content models:*Content interface(and AbstractContent base class) define functionality that acts on the concrete content of a page, and *ContentHandler base class provides functionality specific to a content model, but not acting on concrete content. The most important function of ContentHandler is to act as a factory for the appropriate implementation of Content. These Content objects are to be used by MediaWiki everywhere, instead of passing page content around as text. All manipulation and analysis of page content must be done via the appropriate methods of the Content object. For each content model, a subclass of ContentHandler has to be registered with $wgContentHandlers. The ContentHandler object for a given content model can be obtained using ContentHandler::getForModelID($id). Also Title, WikiPage and Revision now have getContentHandler() methods for convenience. ContentHandler objects are singletons that provide functionality specific to the content type, but not directly acting on the content of some page. ContentHandler::makeEmptyContent() and ContentHandler::unserializeContent() can be used to create a Content object of the appropriate type. However, it is recommended to instead use WikiPage::getContent() resp. Revision::getContent() to get a page 's content as a Content object. These two methods should be the ONLY way in which page content is accessed. Another important function of ContentHandler objects is to define custom action handlers for a content model, see ContentHandler::getActionOverrides(). This is similar to what WikiPage::getActionOverrides() was already doing.==Serialization==With the ContentHandler facility, page content no longer has to be text based. Objects implementing the Content interface are used to represent and handle the content internally. For storage and data exchange, each content model supports at least one serialization format via ContentHandler::serializeContent($content). The list of supported formats for a given content model can be accessed using ContentHandler::getSupportedFormats(). Content serialization formats are identified using MIME type like strings. The following formats are built in:*text/x-wiki - wikitext *text/javascript - for js pages *text/css - for css pages *text/plain - for future use, e.g. with plain text messages. *text/html - for future use, e.g. with plain html messages. *application/vnd.php.serialized - for future use with the api and for extensions *application/json - for future use with the api, and for use by extensions *application/xml - for future use with the api, and for use by extensions In PHP, use the corresponding CONTENT_FORMAT_XXX constant. Note that when using the API to access page content, especially action=edit, action=parse and action=query &prop=revisions, the model and format of the content should always be handled explicitly. Without that information, interpretation of the provided content is not reliable. The same applies to XML dumps generated via maintenance/dumpBackup.php or Special:Export. Also note that the API will provide encapsulated, serialized content - so if the API was called with format=json, and contentformat is also json(or rather, application/json), the page content is represented as a string containing an escaped json structure. Extensions that use JSON to serialize some types of page content may provide specialized API modules that allow access to that content in a more natural form.==Compatibility==The ContentHandler facility is introduced in a way that should allow all existing code to keep functioning at least for pages that contain wikitext or other text based content. However, a number of functions and hooks have been deprecated in favor of new versions that are aware of the page 's content model, and will now generate warnings when used. Most importantly, the following functions have been deprecated:*Revisions::getText() is deprecated in favor Revisions::getContent() *WikiPage::getText() is deprecated in favor WikiPage::getContent() Also, the old Article::getContent()(which returns text) is superceded by Article::getContentObject(). However, both methods should be avoided since they do not provide clean access to the page 's actual content. For instance, they may return a system message for non-existing pages. Use WikiPage::getContent() instead. Code that relies on a textual representation of the page content should eventually be rewritten. However, ContentHandler::getContentText() provides a stop-gap that can be used to get text for a page. Its behavior is controlled by $wgContentHandlerTextFallback it
Some information about database access in MediaWiki By Tim January Database layout For information about the MediaWiki database such as a description of the tables and their please see
We use the convention $dbr for read and $dbw for write to help you keep track of whether the database object is a the world will explode Or to be a subsequent write query which succeeded on the master may fail when replicated to the slave due to a unique key collision Replication on the slave will stop and it may take hours to repair the database and get it back online Setting read_only in my cnf on the slave will avoid this but given the dire we prefer to have as many checks as possible We provide a but the wrapper functions like please read the documentation for except in special pages derived from QueryPage It s a common pitfall for new developers to submit code containing SQL queries which examine huge numbers of rows Remember that COUNT * is(N), counting rows in atable is like counting beans in a bucket.------------------------------------------------------------------------ Replication------------------------------------------------------------------------The largest installation of MediaWiki, Wikimedia, uses a large set ofslave MySQL servers replicating writes made to a master MySQL server. Itis important to understand the issues associated with this setup if youwant to write code destined for Wikipedia.It 's often the case that the best algorithm to use for a given taskdepends on whether or not replication is in use. Due to our unabashedWikipedia-centrism, we often just use the replication-friendly version, but if you like, you can use wfGetLB() ->getServerCount() > 1 tocheck to see if replication is in use.===Lag===Lag primarily occurs when large write queries are sent to the master.Writes on the master are executed in parallel, but they are executed inserial when they are replicated to the slaves. The master writes thequery to the binlog when the transaction is committed. The slaves pollthe binlog and start executing the query as soon as it appears. They canservice reads while they are performing a write query, but will not readanything more from the binlog and thus will perform no more writes. Thismeans that if the write query runs for a long time, the slaves will lagbehind the master for the time it takes for the write query to complete.Lag can be exacerbated by high read load. MediaWiki 's load balancer willstop sending reads to a slave when it is lagged by more than 30 seconds.If the load ratios are set incorrectly, or if there is too much loadgenerally, this may lead to a slave permanently hovering around 30seconds lag.If all slaves are lagged by more than 30 seconds, MediaWiki will stopwriting to the database. All edits and other write operations will berefused, with an error returned to the user. This gives the slaves achance to catch up. Before we had this mechanism, the slaves wouldregularly lag by several minutes, making review of recent editsdifficult.In addition to this, MediaWiki attempts to ensure that the user seesevents occurring on the wiki in chronological order. A few seconds of lagcan be tolerated, as long as the user sees a consistent picture fromsubsequent requests. This is done by saving the master binlog positionin the session, and then at the start of each request, waiting for theslave to catch up to that position before doing any reads from it. Ifthis wait times out, reads are allowed anyway, but the request isconsidered to be in "lagged slave mode". Lagged slave mode can bechecked by calling wfGetLB() ->getLaggedSlaveMode(). The onlypractical consequence at present is a warning displayed in the pagefooter.===Lag avoidance===To avoid excessive lag, queries which write large numbers of rows shouldbe split up, generally to write one row at a time. Multi-row INSERT ...SELECT queries are the worst offenders should be avoided altogether.Instead do the select first and then the insert.===Working with lag===Despite our best efforts, it 's not practical to guarantee a low-lagenvironment. Lag will usually be less than one second, but mayoccasionally be up to 30 seconds. For scalability, it 's very importantto keep load on the master low, so simply sending all your queries tothe master is not the answer. So when you have a genuine need forup-to-date data, the following approach is advised:1) Do a quick query to the master for a sequence number or timestamp 2) Run the full query on the slave and check if it matches the data you gotfrom the master 3) If it doesn 't, run the full query on the masterTo avoid swamping the master every time the slaves lag, use of thisapproach should be kept to a minimum. In most cases you should just readfrom the slave and let the user deal with the delay.------------------------------------------------------------------------ Lock contention------------------------------------------------------------------------Due to the high write rate on Wikipedia(and some other wikis), MediaWiki developers need to be very careful to structure their writesto avoid long-lasting locks. By default, MediaWiki opens a transactionat the first query, and commits it before the output is sent. Locks willbe held from the time when the query is done until the commit. So youcan reduce lock time by doing as much processing as possible before youdo your write queries.Often this approach is not good enough, and it becomes necessary toenclose small groups of queries in their own transaction. Use thefollowing syntax:$dbw=wfGetDB(DB_MASTER
For a write use something like
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 that
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
when a variable name is used in a it is silently declared as a new local masking the global
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 etc For and for historical it also represents a few features of articles that don t involve their such as access rights See also title txt Article Encapsulates access to the page table of the database The object represents a an and maintains state such as etc Revision Encapsulates individual page revision data and access to the revision text blobs storage system Higher level code should never touch text storage directly
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 as
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
globals txt Globals are evil The original MediaWiki code relied on globals for processing context far too often MediaWiki development since then has been a story of slowly moving context out of global variables and into objects Storing processing context in object member variables allows those objects to be reused in a much more flexible way Consider the elegance of
database rows
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
this hook is for auditing only $req
the array() calling protocol came about after MediaWiki 1.4rc1.
do that in ParserLimitReportFormat instead $parser
see documentation in includes Linker php for Linker::makeImageLink & $time
The index of the header message $result[1]=The index of the body text message $result[2 through n]=Parameters passed to body text message. Please note the header message cannot receive/use parameters. 'ImgAuthModifyHeaders':Executed just before a file is streamed to a user via img_auth.php, allowing headers to be modified beforehand. $title:LinkTarget object & $headers:HTTP headers(name=> value, names are case insensitive). Two headers get special handling:If-Modified-Since(value must be a valid HTTP date) and Range(must be of the form "bytes=(\d*-\d*)") will be honored when streaming the file. 'ImportHandleLogItemXMLTag':When parsing a XML tag in a log item. Return false to stop further processing of the tag $reader:XMLReader object $logInfo:Array of information 'ImportHandlePageXMLTag':When parsing a XML tag in a page. Return false to stop further processing of the tag $reader:XMLReader object & $pageInfo:Array of information 'ImportHandleRevisionXMLTag':When parsing a XML tag in a page revision. Return false to stop further processing of the tag $reader:XMLReader object $pageInfo:Array of page information $revisionInfo:Array of revision information 'ImportHandleToplevelXMLTag':When parsing a top level XML tag. Return false to stop further processing of the tag $reader:XMLReader object 'ImportHandleUnknownUser':When a user doesn 't exist locally, this hook is called to give extensions an opportunity to auto-create it. If the auto-creation is successful, return false. $name:User name 'ImportHandleUploadXMLTag':When parsing a XML tag in a file upload. Return false to stop further processing of the tag $reader:XMLReader object $revisionInfo:Array of information 'ImportLogInterwikiLink':Hook to change the interwiki link used in log entries and edit summaries for transwiki imports. & $fullInterwikiPrefix:Interwiki prefix, may contain colons. & $pageTitle:String that contains page title. 'ImportSources':Called when reading from the $wgImportSources configuration variable. Can be used to lazy-load the import sources list. & $importSources:The value of $wgImportSources. Modify as necessary. See the comment in DefaultSettings.php for the detail of how to structure this array. 'InfoAction':When building information to display on the action=info page. $context:IContextSource object & $pageInfo:Array of information 'InitializeArticleMaybeRedirect':MediaWiki check to see if title is a redirect. & $title:Title object for the current page & $request:WebRequest & $ignoreRedirect:boolean to skip redirect check & $target:Title/string of redirect target & $article:Article object 'InternalParseBeforeLinks':during Parser 's internalParse method before links but after nowiki/noinclude/includeonly/onlyinclude and other processings. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InternalParseBeforeSanitize':during Parser 's internalParse method just before the parser removes unwanted/dangerous HTML tags and after nowiki/noinclude/includeonly/onlyinclude and other processings. Ideal for syntax-extensions after template/parser function execution which respect nowiki and HTML-comments. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InterwikiLoadPrefix':When resolving if a given prefix is an interwiki or not. Return true without providing an interwiki to continue interwiki search. $prefix:interwiki prefix we are looking for. & $iwData:output array describing the interwiki with keys iw_url, iw_local, iw_trans and optionally iw_api and iw_wikiid. 'InvalidateEmailComplete':Called after a user 's email has been invalidated successfully. $user:user(object) whose email is being invalidated 'IRCLineURL':When constructing the URL to use in an IRC notification. Callee may modify $url and $query, URL will be constructed as $url . $query & $url:URL to index.php & $query:Query string $rc:RecentChange object that triggered url generation 'IsFileCacheable':Override the result of Article::isFileCacheable()(if true) & $article:article(object) being checked 'IsTrustedProxy':Override the result of IP::isTrustedProxy() & $ip:IP being check & $result:Change this value to override the result of IP::isTrustedProxy() 'IsUploadAllowedFromUrl':Override the result of UploadFromUrl::isAllowedUrl() $url:URL used to upload from & $allowed:Boolean indicating if uploading is allowed for given URL 'isValidEmailAddr':Override the result of Sanitizer::validateEmail(), for instance to return false if the domain name doesn 't match your organization. $addr:The e-mail address entered by the user & $result:Set this and return false to override the internal checks 'isValidPassword':Override the result of User::isValidPassword() $password:The password entered by the user & $result:Set this and return false to override the internal checks $user:User the password is being validated for 'Language::getMessagesFileName':$code:The language code or the language we 're looking for a messages file for & $file:The messages file path, you can override this to change the location. 'LanguageGetMagic':DEPRECATED! Use $magicWords in a file listed in $wgExtensionMessagesFiles instead. Use this to define synonyms of magic words depending of the language & $magicExtensions:associative array of magic words synonyms $lang:language code(string) 'LanguageGetNamespaces':Provide custom ordering for namespaces or remove namespaces. Do not use this hook to add namespaces. Use CanonicalNamespaces for that. & $namespaces:Array of namespaces indexed by their numbers 'LanguageGetSpecialPageAliases':DEPRECATED! Use $specialPageAliases in a file listed in $wgExtensionMessagesFiles instead. Use to define aliases of special pages names depending of the language & $specialPageAliases:associative array of magic words synonyms $lang:language code(string) 'LanguageGetTranslatedLanguageNames':Provide translated language names. & $names:array of language code=> language name $code:language of the preferred translations 'LanguageLinks':Manipulate a page 's language links. This is called in various places to allow extensions to define the effective language links for a page. $title:The page 's Title. & $links:Array with elements of the form "language:title" in the order that they will be output. & $linkFlags:Associative array mapping prefixed links to arrays of flags. Currently unused, but planned to provide support for marking individual language links in the UI, e.g. for featured articles. 'LanguageSelector':Hook to change the language selector available on a page. $out:The output page. $cssClassName:CSS class name of the language selector. 'LinkBegin':DEPRECATED! Use HtmlPageLinkRendererBegin instead. Used when generating internal and interwiki links in Linker::link(), before processing starts. Return false to skip default processing and return $ret. See documentation for Linker::link() for details on the expected meanings of parameters. $skin:the Skin object $target:the Title that the link is pointing to & $html:the contents that the< a > tag should have(raw HTML) $result
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
usually copyright or history_copyright This message must be in HTML not wikitext if the section is included from a template to be included in the link
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction you ll probably need to make sure the header is varied on and they can depend only on the ResourceLoaderContext $context
namespace and then decline to actually register it file or subcat img or subcat $title
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
null for the local wiki Added in
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
you don t have to do a grep find to see where the $wgReverseTitle variable is used
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
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). 'DeleteUnknownPreferences':Called by the cleanupPreferences.php maintenance script to build a WHERE clause with which to delete preferences that are not known about. This hook is used by extensions that have dynamically-named preferences that should not be deleted in the usual cleanup process. For example, the Gadgets extension creates preferences prefixed with 'gadget-', and so anything with that prefix is excluded from the deletion. &where:An array that will be passed as the $cond parameter to IDatabase::select() to determine what will be deleted from the user_properties table. $db:The IDatabase object, useful for accessing $db->buildLike() etc. 'DifferenceEngineAfterLoadNewText':called in DifferenceEngine::loadNewText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before returning true from this function. $differenceEngine:DifferenceEngine object 'DifferenceEngineLoadTextAfterNewContentIsLoaded':called in DifferenceEngine::loadText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before checking if the variable 's value is null. This hook can be used to inject content into said class member variable. $differenceEngine:DifferenceEngine object 'DifferenceEngineMarkPatrolledLink':Allows extensions to change the "mark as patrolled" link which is shown both on the diff header as well as on the bottom of a page, usually wrapped in a span element which has class="patrollink". $differenceEngine:DifferenceEngine object & $markAsPatrolledLink:The "mark as patrolled" link HTML(string) $rcid:Recent change ID(rc_id) for this change(int) 'DifferenceEngineMarkPatrolledRCID':Allows extensions to possibly change the rcid parameter. For example the rcid might be set to zero due to the user being the same as the performer of the change but an extension might still want to show it under certain conditions. & $rcid:rc_id(int) of the change or 0 $differenceEngine:DifferenceEngine object $change:RecentChange object $user:User object representing the current user 'DifferenceEngineNewHeader':Allows extensions to change the $newHeader variable, which contains information about the new revision, such as the revision 's author, whether the revision was marked as a minor edit or not, etc. $differenceEngine:DifferenceEngine object & $newHeader:The string containing the various #mw-diff-otitle[1-5] divs, which include things like revision author info, revision comment, RevisionDelete link and more $formattedRevisionTools:Array containing revision tools, some of which may have been injected with the DiffRevisionTools hook $nextlink:String containing the link to the next revision(if any) $status
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
We ve cleaned up the code here by removing clumps of infrequently used code and moving them off somewhere else It s much easier for someone working with this code to see what s _really_ going on
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
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
There are three types of nodes:
magicword txt Magic Words are some phrases used in the wikitext They are used for two things
controlled by $wgMainCacheType controlled by $wgParserCacheType controlled by $wgMessageCacheType If you set CACHE_NONE to one of the three control variable
This document describes the state of Postgres support in and is fairly well maintained The main code is very well while extensions are very hit and miss it is probably the most supported database after MySQL Much of the work in making MediaWiki database agnostic came about through the work of creating Postgres but without copying over all the usage comments General notes on the but these can almost always be programmed around *Although Postgres has a true BOOLEAN type