136 use DeprecationHelper;
138 # Flags for Parser::setFunctionHook
142 # Constants needed for external link processing
156 private const EXT_LINK_ADDR =
'(?:[0-9.]+|\\[(?i:[0-9a-f:.]+)\\]|[^][<>"\\x00-\\x20\\x7F\p{Zs}\x{FFFD}])';
159 private const EXT_IMAGE_REGEX =
'/^(http:\/\/|https:\/\/)((?:\\[(?i:[0-9a-f:.]+)\\])?[^][<>"\\x00-\\x20\\x7F\p{Zs}\x{FFFD}]+)
160 \\/([A-Za-z0-9_.,~%\\-+&;#*?!=()@\\x80-\\xFF]+)\\.((?i)avif|gif|jpg|jpeg|png|svg|webp)$/Sxu';
163 private const SPACE_NOT_NL =
'(?:\t| |&\#0*160;|&\#[Xx]0*[Aa]0;|\p{Zs})';
171 # Allowed values for $this->mOutputType
201 public const MARKER_SUFFIX =
"-QINU`\"'\x7f";
203 private const HEADLINE_MARKER_REGEX =
'/^' . self::MARKER_PREFIX .
'-h-(\d+)-' . self::MARKER_SUFFIX .
'/';
219 public const TOC_PLACEHOLDER =
'<meta property="mw:PageProp/toc" />';
223 private array $mTagHooks = [];
225 private array $mFunctionHooks = [];
227 private array $mFunctionSynonyms = [ 0 => [], 1 => [] ];
229 private array $mStripList = [];
231 private array $mVarCache = [];
233 private array $mImageParams = [];
235 private array $mImageParamsMagicArray = [];
246 private string $mExtLinkBracketedRegex;
253 private int $mAutonumber = 0;
256 private int $mLinkID = 0;
257 private array $mIncludeSizes;
268 private array $mTplRedirCache;
272 private array $mDoubleUnderscores;
278 private bool $mShowToc;
279 private bool $mForceTocPosition;
280 private array $mTplDomCache;
284 # These are variables reset at least once per parse regardless of $clearState
292 # Deprecated "dynamic" properties
293 # These used to be dynamic properties added to the parser, but these
294 # have been deprecated since 1.42.
309 private Title $mTitle;
311 private int $mOutputType;
319 private bool $mStripExtTags =
true;
326 private ?
int $mRevisionId =
null;
328 private ?
string $mRevisionTimestamp =
null;
330 private ?
string $mRevisionUser =
null;
332 private ?
int $mRevisionSize =
null;
334 private $mInputSize =
false;
349 private $mInParse =
false;
400 private LoggerInterface $logger,
402 private LanguageConverterFactory $languageConverterFactory,
403 private LanguageNameUtils $languageNameUtils,
415 $this->deprecateDynamicPropertiesAccess(
'1.42', __CLASS__ );
416 $this->deprecatePublicProperty(
'ot',
'1.35', __CLASS__ );
417 $this->deprecatePublicProperty(
'mTitle',
'1.35', __CLASS__ );
418 $this->deprecatePublicProperty(
'mOptions',
'1.35', __CLASS__ );
423 throw new BadMethodCallException(
'Direct construction of Parser not allowed' );
427 $this->mExtLinkBracketedRegex =
'/\[(((?i)' . $this->urlUtils->validProtocols() .
')' .
428 self::EXT_LINK_ADDR .
429 self::EXT_LINK_URL_CLASS .
'*)\p{Zs}*([^\]\\x00-\\x08\\x0a-\\x1F\\x{FFFD}]*)\]/Su';
431 $this->hookRunner =
new HookRunner( $hookContainer );
438 'disableLangConversion' => $languageConverterFactory->isConversionDisabled(),
444 CoreParserFunctions::register(
446 new ServiceOptions( CoreParserFunctions::REGISTER_OPTIONS, $svcOptions )
452 $this->initializeVariables();
454 $this->hookRunner->onParserFirstCallInit( $this );
463 if ( isset( $this->mLinkHolders ) ) {
465 unset( $this->mLinkHolders );
468 foreach ( $this as $name => $value ) {
469 unset( $this->$name );
477 $this->mInParse =
false;
479 $this->mPreprocessor = clone $this->mPreprocessor;
480 $this->mPreprocessor->resetParser( $this );
482 $this->hookRunner->onParserCloned( $this );
506 $this->mAutonumber = 0;
509 $this->getContentLanguageConverter(),
513 $this->mRevisionTimestamp =
null;
514 $this->mRevisionId =
null;
515 $this->mRevisionUser =
null;
516 $this->mRevisionSize =
null;
517 $this->mRevisionRecordObject =
null;
518 $this->mVarCache = [];
520 $this->currentRevisionCache =
null;
524 # Clear these on every parse, T6549
525 $this->mTplRedirCache = [];
526 $this->mTplDomCache = [];
528 $this->mShowToc =
true;
529 $this->mForceTocPosition =
false;
530 $this->mIncludeSizes = [
534 $this->mPPNodeCount = 0;
535 $this->mHighestExpansionDepth = 0;
536 $this->mHeadings = [];
537 $this->mDoubleUnderscores = [];
538 $this->mExpensiveFunctionCount = 0;
542 $this->hookRunner->onParserClearState( $this );
551 $this->mOptions->registerWatcher( $this->mOutput->recordOption( ... ) );
562 $ts = $this->mOptions->getTimestamp();
563 $date = DateTime::createFromFormat(
564 'YmdHis', $ts,
new DateTimeZone(
'UTC' )
566 if ( $this->hookContainer->isRegistered(
'ParserGetVariableValueTs' ) ) {
567 $s = $date->format(
'U' );
568 $this->hookRunner->onParserGetVariableValueTs( $this, $s );
594 $linestart =
true, $clearState =
true, $revid =
null
599 $text = strtr( $text,
"\x7f",
"?" );
600 $magicScopeVariable = $this->lock();
603 $text = str_replace(
"\000",
'', $text );
605 $this->startParse( $page, $options, self::OT_HTML, $clearState );
607 $this->currentRevisionCache =
null;
608 $this->mInputSize = strlen( $text );
609 $this->mOutput->resetParseStartTime();
611 $oldRevisionId = $this->mRevisionId;
612 $oldRevisionRecordObject = $this->mRevisionRecordObject;
613 $oldRevisionTimestamp = $this->mRevisionTimestamp;
614 $oldRevisionUser = $this->mRevisionUser;
615 $oldRevisionSize = $this->mRevisionSize;
616 if ( $revid !==
null ) {
617 $this->mRevisionId = $revid;
618 $this->mRevisionRecordObject =
null;
619 $this->mRevisionTimestamp =
null;
620 $this->mRevisionUser =
null;
621 $this->mRevisionSize =
null;
624 $text = $this->internalParse( $text );
625 $this->hookRunner->onParserAfterParse( $this, $text, $this->mStripState );
627 $text = $this->internalParseHalfParsed( $text,
true, $linestart );
637 && !isset( $this->mDoubleUnderscores[
'nocontentconvert'] )
638 && !isset( $this->mDoubleUnderscores[
'notitleconvert'] )
639 && $this->mOutput->getDisplayTitle() ===
false
641 $titleText = $this->getTargetLanguageConverter()->getConvRuleTitle();
642 if ( $titleText !==
false ) {
645 [ $nsText, $nsSeparator, $mainText ] = $this->getTargetLanguageConverter()->convertSplitTitle( $page );
648 $titleText = self::formatPageTitle( $nsText, $nsSeparator, $mainText );
650 $this->mOutput->setTitleText( $titleText );
653 # Recording timing info. Must be called before finalizeAdaptiveCacheExpiry() and
654 # makeLimitReport(), which make use of the timing info.
655 $this->mOutput->recordTimeProfile();
657 # Compute runtime adaptive expiry if set
658 $this->mOutput->finalizeAdaptiveCacheExpiry();
660 # Warn if too many heavyweight parser functions were used
662 $this->limitationWarn(
'expensive-parserfunction',
663 $this->mExpensiveFunctionCount,
668 # Information on limits, for the benefit of users who try to skirt them
669 $this->makeLimitReport( $this->mOptions, $this->mOutput );
671 $this->mOutput->setFromParserOptions( $options );
673 $this->mOutput->setRawText( $text );
675 $this->mRevisionId = $oldRevisionId;
676 $this->mRevisionRecordObject = $oldRevisionRecordObject;
677 $this->mRevisionTimestamp = $oldRevisionTimestamp;
678 $this->mRevisionUser = $oldRevisionUser;
679 $this->mRevisionSize = $oldRevisionSize;
680 $this->mInputSize =
false;
681 $this->currentRevisionCache =
null;
683 return $this->mOutput;
705 if ( $cpuTime !==
null ) {
707 sprintf(
"%.3f", $cpuTime )
713 sprintf(
"%.3f", $wallTime )
719 $revisionSize = $this->mInputSize !==
false ? $this->mInputSize :
720 $this->getRevisionSize();
725 [ $this->mIncludeSizes[
'post-expand'], $maxIncludeSize ]
728 [ $this->mIncludeSizes[
'arg'], $maxIncludeSize ]
737 foreach ( $this->mStripState->getLimitReport() as [ $key, $value ] ) {
741 $this->hookRunner->onParserLimitReportPrepare( $this, $parserOutput );
744 $dataByFunc = $this->mProfiler->getFunctionStats();
745 uasort( $dataByFunc,
static function ( $a, $b ) {
746 return $b[
'real'] <=> $a[
'real'];
749 foreach ( array_slice( $dataByFunc, 0, 10 ) as $item ) {
750 $profileReport[] = sprintf(
"%6.2f%% %8.3f %6d %s",
751 $item[
'%real'], $item[
'real'], $item[
'calls'],
752 htmlspecialchars( $item[
'name'] ) );
795 $text = $this->internalParse( $text,
false, $frame );
819 $text = $this->recursiveTagParse( $text, $frame );
820 $text = $this->internalParseHalfParsed( $text,
false );
844 $text = $this->recursiveTagParse( $text );
845 $this->hookRunner->onParserAfterParse( $this, $text, $this->mStripState );
846 $text = $this->internalParseHalfParsed( $text,
true );
869 $magicScopeVariable = $this->lock();
870 $this->startParse( $page, $options, self::OT_PREPROCESS,
true );
871 if ( $revid !==
null ) {
872 $this->mRevisionId = $revid;
874 $this->hookRunner->onParserBeforePreprocess( $this, $text, $this->mStripState );
875 $text = $this->replaceVariables( $text, $frame );
876 $text = $this->mStripState->unstripBoth( $text );
890 $text = $this->replaceVariables( $text, $frame );
891 $text = $this->mStripState->unstripBoth( $text );
911 $text = $msg->params( $params )->plain();
913 # Parser (re)initialisation
914 $magicScopeVariable = $this->lock();
915 $this->startParse( $page, $options, self::OT_PLAIN,
true );
917 $flags = PPFrame::NO_ARGS | PPFrame::NO_TEMPLATES;
918 $dom = $this->preprocessToDom( $text, Preprocessor::DOM_FOR_INCLUSION );
919 $text = $this->getPreprocessor()->newFrame()->expand( $dom, $flags );
920 $text = $this->mStripState->unstripBoth( $text );
932 $this->mUser = $user;
943 $this->setPage( $t );
963 $t = Title::makeTitle(
NS_SPECIAL,
'Badtitle/Parser' );
968 $t = Title::newFromPageReference( $t );
971 if ( $t->hasFragment() ) {
972 # Strip the fragment to avoid various odd effects
973 $this->mTitle = $t->createFragmentTarget(
'' );
985 if ( $this->mTitle->isSpecial(
'Badtitle' ) ) {
986 [ , $subPage ] = $this->specialPageFactory->resolveAlias( $this->mTitle->getDBkey() );
988 if ( $subPage ===
'Missing' ) {
989 wfDeprecated( __METHOD__ .
' without a Title set',
'1.34' );
994 return $this->mTitle;
1003 return $this->mOutputType;
1012 $this->mOutputType = $ot;
1028 if ( !isset( $this->mOutput ) ) {
1029 wfDeprecated( __METHOD__ .
' before initialization',
'1.42' );
1033 return $this->mOutput;
1041 return $this->mOptions;
1050 $this->mOptions = $options;
1058 return $this->mLinkID++;
1066 $this->mLinkID = $id;
1078 $target = $this->mOptions->getTargetLanguage();
1080 if ( $target !==
null ) {
1082 } elseif ( $this->mOptions->getInterfaceMessage() ) {
1083 return $this->mOptions->getUserLangObj();
1086 return $this->getTitle()->getPageLanguage();
1097 return $this->mUser ?? $this->getOptions()->getUserIdentity();
1107 return $this->mPreprocessor;
1118 if ( !$this->mLinkRenderer ) {
1119 $this->mLinkRenderer = $this->linkRendererFactory->create();
1122 return $this->mLinkRenderer;
1132 return $this->magicWordFactory;
1142 return $this->contLang;
1152 return $this->badFileLookup;
1179 $taglist = implode(
'|', $elements );
1180 $start =
"/<($taglist)(\\s+[^>]*?|\\s*?)(\/?>)|<(!--)/i";
1182 while ( $text !=
'' ) {
1183 $p = preg_split( $start, $text, 2, PREG_SPLIT_DELIM_CAPTURE );
1185 if ( count( $p ) < 5 ) {
1188 if ( count( $p ) > 5 ) {
1196 [ , $element, $attributes, $close, $inside ] = $p;
1199 $marker = self::MARKER_PREFIX .
"-$element-" . sprintf(
'%08X', $n++ ) . self::MARKER_SUFFIX;
1200 $stripped .= $marker;
1202 if ( $close ===
'/>' ) {
1203 # Empty element tag, <tag />
1208 if ( $element ===
'!--' ) {
1211 $end =
"/(<\\/$element\\s*>)/i";
1213 $q = preg_split( $end, $inside, 2, PREG_SPLIT_DELIM_CAPTURE );
1215 if ( count( $q ) < 3 ) {
1216 # No end tag -- let it run out to the end of the text.
1220 [ , $tail, $text ] = $q;
1226 Sanitizer::decodeTagAttributes( $attributes ),
1227 "<$element$attributes$close$content$tail" ];
1238 return $this->mStripList;
1246 return $this->mStripState;
1259 $marker = self::MARKER_PREFIX .
"-item-{$this->mMarkerIndex}-" . self::MARKER_SUFFIX;
1260 $this->mMarkerIndex++;
1261 $this->mStripState->addGeneral( $marker, $text );
1271 private function handleTables( $text ) {
1272 $lines = StringUtils::explode(
"\n", $text );
1274 $td_history = []; # Is currently a td tag open?
1275 $last_tag_history = []; # Save history of last lag activated (td, th or caption)
1276 $tr_history = []; # Is currently a tr tag open?
1277 $tr_attributes = []; # history of tr attributes
1278 $has_opened_tr = []; # Did
this table open a <tr> element?
1279 $indent_level = 0; # indent level of the table
1281 foreach ( $lines as $outLine ) {
1282 $line = trim( $outLine );
1284 if ( $line ===
'' ) { # empty line, go to next line
1285 $out .= $outLine .
"\n";
1289 $first_character = $line[0];
1290 $first_two = substr( $line, 0, 2 );
1293 if ( preg_match(
'/^(:*)\s*\{\|(.*)$/', $line,
$matches ) ) {
1294 # First check if we are starting a new table
1295 $indent_level = strlen(
$matches[1] );
1297 $attributes = $this->mStripState->unstripBoth(
$matches[2] );
1298 $attributes = Sanitizer::fixTagAttributes( $attributes,
'table' );
1300 $outLine = str_repeat(
'<dl><dd>', $indent_level ) .
"<table{$attributes}>";
1301 $td_history[] =
false;
1302 $last_tag_history[] =
'';
1303 $tr_history[] =
false;
1304 $tr_attributes[] =
'';
1305 $has_opened_tr[] =
false;
1306 } elseif ( count( $td_history ) == 0 ) {
1307 # Don't do any of the following
1308 $out .= $outLine .
"\n";
1310 } elseif ( $first_two ===
'|}' ) {
1311 # We are ending a table
1312 $line =
'</table>' . substr( $line, 2 );
1313 $last_tag = array_pop( $last_tag_history );
1315 if ( !array_pop( $has_opened_tr ) ) {
1316 $line =
"<tr><td></td></tr>{$line}";
1319 if ( array_pop( $tr_history ) ) {
1320 $line =
"</tr>{$line}";
1323 if ( array_pop( $td_history ) ) {
1324 $line =
"</{$last_tag}>{$line}";
1326 array_pop( $tr_attributes );
1327 if ( $indent_level > 0 ) {
1328 $outLine = rtrim( $line ) . str_repeat(
'</dd></dl>', $indent_level );
1332 } elseif ( $first_two ===
'|-' ) {
1333 # Now we have a table row
1334 $line = preg_replace(
'#^\|-+#',
'', $line );
1336 # Whats after the tag is now only attributes
1337 $attributes = $this->mStripState->unstripBoth( $line );
1338 $attributes = Sanitizer::fixTagAttributes( $attributes,
'tr' );
1339 array_pop( $tr_attributes );
1340 $tr_attributes[] = $attributes;
1343 $last_tag = array_pop( $last_tag_history );
1344 array_pop( $has_opened_tr );
1345 $has_opened_tr[] =
true;
1347 if ( array_pop( $tr_history ) ) {
1351 if ( array_pop( $td_history ) ) {
1352 $line =
"</{$last_tag}>{$line}";
1356 $tr_history[] =
false;
1357 $td_history[] =
false;
1358 $last_tag_history[] =
'';
1359 } elseif ( $first_character ===
'|'
1360 || $first_character ===
'!'
1361 || $first_two ===
'|+'
1363 # This might be cell elements, td, th or captions
1364 if ( $first_two ===
'|+' ) {
1365 $first_character =
'+';
1366 $line = substr( $line, 2 );
1368 $line = substr( $line, 1 );
1372 if ( $first_character ===
'!' ) {
1373 $line = StringUtils::replaceMarkup(
'!!',
'||', $line );
1376 # Split up multiple cells on the same line.
1377 # FIXME : This can result in improper nesting of tags processed
1378 # by earlier parser steps.
1379 $cells = explode(
'||', $line );
1383 # Loop through each table cell
1384 foreach ( $cells as $cell ) {
1386 if ( $first_character !==
'+' ) {
1387 $tr_after = array_pop( $tr_attributes );
1388 if ( !array_pop( $tr_history ) ) {
1389 $previous =
"<tr{$tr_after}>\n";
1391 $tr_history[] =
true;
1392 $tr_attributes[] =
'';
1393 array_pop( $has_opened_tr );
1394 $has_opened_tr[] =
true;
1397 $last_tag = array_pop( $last_tag_history );
1399 if ( array_pop( $td_history ) ) {
1400 $previous =
"</{$last_tag}>\n{$previous}";
1403 if ( $first_character ===
'|' ) {
1405 } elseif ( $first_character ===
'!' ) {
1407 } elseif ( $first_character ===
'+' ) {
1408 $last_tag =
'caption';
1413 $last_tag_history[] = $last_tag;
1415 # A cell could contain both parameters and data
1416 $cell_data = explode(
'|', $cell, 2 );
1418 # T2553: Note that a '|' inside an invalid link should not
1419 # be mistaken as delimiting cell parameters
1420 # Bug T153140: Neither should language converter markup.
1421 if ( preg_match(
'/\[\[|-\{/', $cell_data[0] ) === 1 ) {
1422 $cell =
"{$previous}<{$last_tag}>" . trim( $cell );
1423 } elseif ( count( $cell_data ) == 1 ) {
1425 $cell =
"{$previous}<{$last_tag}>" . trim( $cell_data[0] );
1427 $attributes = $this->mStripState->unstripBoth( $cell_data[0] );
1428 $attributes = Sanitizer::fixTagAttributes( $attributes, $last_tag );
1430 $cell =
"{$previous}<{$last_tag}{$attributes}>" . trim( $cell_data[1] );
1434 $td_history[] =
true;
1437 $out .= $outLine .
"\n";
1440 # Closing open td, tr && table
1441 while ( count( $td_history ) > 0 ) {
1442 if ( array_pop( $td_history ) ) {
1445 if ( array_pop( $tr_history ) ) {
1448 if ( !array_pop( $has_opened_tr ) ) {
1449 $out .=
"<tr><td></td></tr>\n";
1452 $out .=
"</table>\n";
1455 # Remove trailing line-ending (b/c)
1456 if ( substr( $out, -1 ) ===
"\n" ) {
1457 $out = substr( $out, 0, -1 );
1460 # special case: don't return empty table
1461 if ( $out ===
"<table>\n<tr><td></td></tr>\n</table>" ) {
1484 # Hook to suspend the parser in this state
1485 if ( !$this->hookRunner->onParserBeforeInternalParse( $this, $text, $this->mStripState ) ) {
1489 # if $frame is provided, then use $frame for replacing any variables
1491 # use frame depth to infer how include/noinclude tags should be handled
1492 # depth=0 means this is the top-level document; otherwise it's an included document
1493 if ( !$frame->depth ) {
1496 $flag = Preprocessor::DOM_FOR_INCLUSION;
1498 $dom = $this->preprocessToDom( $text, $flag );
1499 $text = $frame->expand( $dom );
1501 # if $frame is not provided, then use old-style replaceVariables
1502 $text = $this->replaceVariables( $text );
1505 $text = Sanitizer::internalRemoveHtmlTags(
1509 function ( &$text, $frame =
false ) {
1510 $text = $this->replaceVariables( $text, $frame );
1511 $text = $this->mStripState->unstripBoth( $text );
1517 $this->hookRunner->onInternalParseBeforeLinks( $this, $text, $this->mStripState );
1519 # Tables need to come after variable replacement for things to work
1520 # properly; putting them before other transformations should keep
1521 # exciting things like link expansions from showing up in surprising
1523 $text = $this->handleTables( $text );
1525 $text = preg_replace(
'/(^|\n)-----*/',
'\\1<hr />', $text );
1527 $text = $this->handleDoubleUnderscore( $text );
1529 $text = $this->handleHeadings( $text );
1530 $text = $this->handleInternalLinks( $text );
1531 $text = $this->handleAllQuotes( $text );
1532 $text = $this->handleExternalLinks( $text );
1534 # handleInternalLinks may sometimes leave behind
1535 # absolute URLs, which have to be masked to hide them from handleExternalLinks
1536 $text = str_replace( self::MARKER_PREFIX .
'NOPARSE',
'', $text );
1538 $text = $this->handleMagicLinks( $text );
1539 $text = $this->finalizeHeadings( $text, $origText, $isMain );
1551 return $this->languageConverterFactory->getLanguageConverter(
1552 $this->getTargetLanguage()
1560 return $this->languageConverterFactory->getLanguageConverter(
1561 $this->getContentLanguage()
1573 return $this->hookContainer;
1585 return $this->hookRunner;
1597 private function internalParseHalfParsed( $text, $isMain =
true, $linestart =
true ) {
1598 $text = $this->mStripState->unstripGeneral( $text );
1600 $text = BlockLevelPass::doBlockLevels( $text, $linestart );
1602 $this->replaceLinkHoldersPrivate( $text );
1612 if ( !( $this->mOptions->getDisableContentConversion()
1613 || isset( $this->mDoubleUnderscores[
'nocontentconvert'] )
1614 || $this->mOptions->getInterfaceMessage() )
1616 # The position of the convert() call should not be changed. it
1617 # assumes that the links are all replaced and the only thing left
1618 # is the <nowiki> mark.
1619 $converter = $this->getTargetLanguageConverter();
1620 $text = $converter->convert( $text );
1627 $this->mOutput->getTOCData(),
1628 $this->getTargetLanguage(),
1632 $this->mOutput->setLanguage(
new Bcp47CodeValue(
1633 LanguageCode::bcp47( $converter->getPreferredVariant() )
1636 $this->mOutput->setLanguage( $this->getTargetLanguage() );
1639 $text = $this->mStripState->unstripNoWiki( $text );
1641 $text = $this->mStripState->unstripGeneral( $text );
1643 $text = $this->tidy->tidy( $text, Sanitizer::armorFrenchSpaces( ... ) );
1646 $title = $this->getPage();
1648 $this->mOutput->setTitle( $title );
1650 $this->hookRunner->onParserAfterTidy( $this, $text );
1666 private function handleMagicLinks( $text ) {
1667 $prots = $this->urlUtils->validAbsoluteProtocols();
1668 $urlChar = self::EXT_LINK_URL_CLASS;
1669 $addr = self::EXT_LINK_ADDR;
1670 $space = self::SPACE_NOT_NL; # non-newline space
1671 $spdash =
"(?:-|$space)"; # a dash or a non-newline space
1672 $spaces =
"$space++"; # possessive match of 1 or more spaces
1673 $text = preg_replace_callback(
1675 (<a[ \t\r\n>].*?</a>) | # m[1]: Skip link text
1676 (<.*?>) | # m[2]: Skip stuff inside HTML elements' .
"
1677 (\b # m[3]: Free external links
1679 ($addr$urlChar*) # m[4]: Post-protocol path
1681 \b(?:RFC|PMID) $spaces # m[5]: RFC or PMID, capture number
1683 \bISBN $spaces ( # m[6]: ISBN, capture number
1684 (?: 97[89] $spdash? )? # optional 13-digit ISBN prefix
1685 (?: [0-9] $spdash? ){9} # 9 digits with opt. delimiters
1686 [0-9Xx] # check digit
1689 $this->magicLinkCallback( ... ),
1699 private function magicLinkCallback( array $m ) {
1700 if ( isset( $m[1] ) && $m[1] !==
'' ) {
1703 } elseif ( isset( $m[2] ) && $m[2] !==
'' ) {
1706 } elseif ( isset( $m[3] ) && $m[3] !==
'' ) {
1707 # Free external link
1708 return $this->makeFreeExternalLink( $m[0], strlen( $m[4] ) );
1709 } elseif ( isset( $m[5] ) && $m[5] !==
'' ) {
1711 if ( str_starts_with( $m[0],
'RFC' ) ) {
1712 if ( !$this->mOptions->getMagicRFCLinks() ) {
1717 $cssClass =
'mw-magiclink-rfc';
1718 $trackingCat =
'magiclink-tracking-rfc';
1720 } elseif ( str_starts_with( $m[0],
'PMID' ) ) {
1721 if ( !$this->mOptions->getMagicPMIDLinks() ) {
1725 $urlmsg =
'pubmedurl';
1726 $cssClass =
'mw-magiclink-pmid';
1727 $trackingCat =
'magiclink-tracking-pmid';
1731 throw new UnexpectedValueException( __METHOD__ .
': unrecognised match type "' .
1732 substr( $m[0], 0, 20 ) .
'"' );
1734 $url =
wfMessage( $urlmsg, $id )->inContentLanguage()->text();
1735 $this->addTrackingCategory( $trackingCat );
1743 } elseif ( isset( $m[6] ) && $m[6] !==
''
1744 && $this->mOptions->getMagicISBNLinks()
1748 $space = self::SPACE_NOT_NL; # non-newline space
1749 $isbn = preg_replace(
"/$space/",
' ', $isbn );
1750 $num = strtr( $isbn, [
1755 $this->addTrackingCategory(
'magiclink-tracking-isbn' );
1757 SpecialPage::getTitleFor(
'Booksources', $num ),
1760 'class' =>
'internal mw-magiclink-isbn',
1778 private function makeFreeExternalLink(
$url, $numPostProto ) {
1781 # The characters '<' and '>' (which were escaped by
1782 # internalRemoveHtmlTags()) should not be included in
1783 # URLs, per RFC 2396.
1784 # Make terminate a URL as well (bug T84937)
1787 '/&(lt|gt|nbsp|#x0*(3[CcEe]|[Aa]0)|#0*(60|62|160));/',
1792 $trail = substr(
$url, $m2[0][1] ) . $trail;
1793 $url = substr(
$url, 0, $m2[0][1] );
1796 # Move trailing punctuation to $trail
1798 # If there is no left bracket, then consider right brackets fair game too
1799 if ( !str_contains(
$url,
'(' ) ) {
1803 $urlRev = strrev(
$url );
1804 $numSepChars = strspn( $urlRev, $sep );
1805 # Don't break a trailing HTML entity by moving the ; into $trail
1806 # This is in hot code, so use substr_compare to avoid having to
1807 # create a new string object for the comparison
1808 if ( $numSepChars && substr_compare(
$url,
";", -$numSepChars, 1 ) === 0 ) {
1809 # more optimization: instead of running preg_match with a $
1810 # anchor, which can be slow, do the match on the reversed
1811 # string starting at the desired offset.
1812 # un-reversed regexp is: /&([a-z]+|#x[\da-f]+|#\d+)$/i
1813 if ( preg_match(
'/\G([a-z]+|[\da-f]+x#|\d+#)&/i', $urlRev, $m2, 0, $numSepChars ) ) {
1817 if ( $numSepChars ) {
1818 $trail = substr(
$url, -$numSepChars ) . $trail;
1819 $url = substr(
$url, 0, -$numSepChars );
1822 # Verify that we still have a real URL after trail removal, and
1823 # not just lone protocol
1824 if ( strlen( $trail ) >= $numPostProto ) {
1825 return $url . $trail;
1828 $url = Sanitizer::cleanUrl(
$url );
1830 # Is this an external image?
1831 $text = $this->maybeMakeExternalImage(
$url );
1832 if ( $text ===
false ) {
1833 # Not an image, make a link
1836 $this->getTargetLanguageConverter()->markNoConversion(
$url ),
1839 $this->getExternalLinkAttribs(
$url )
1841 # Register it in the output object...
1842 $this->mOutput->addExternalLink(
$url );
1844 return $text . $trail;
1853 private function handleHeadings( $text ) {
1854 for ( $i = 6; $i >= 1; --$i ) {
1855 $h = str_repeat(
'=', $i );
1858 $text = preg_replace(
"/^(?:$h)[ \\t]*(.+?)[ \\t]*(?:$h)\\s*$/m",
"<h$i>\\1</h$i>", $text );
1870 private function handleAllQuotes( $text ) {
1872 $lines = StringUtils::explode(
"\n", $text );
1873 foreach ( $lines as $line ) {
1874 $outtext .= $this->doQuotes( $line ) .
"\n";
1876 $outtext = substr( $outtext, 0, -1 );
1889 $arr = preg_split(
"/(''+)/", $text, -1, PREG_SPLIT_DELIM_CAPTURE );
1890 $countarr = count( $arr );
1891 if ( $countarr == 1 ) {
1900 for ( $i = 1; $i < $countarr; $i += 2 ) {
1901 $thislen = strlen( $arr[$i] );
1905 if ( $thislen == 4 ) {
1906 $arr[$i - 1] .=
"'";
1909 } elseif ( $thislen > 5 ) {
1913 $arr[$i - 1] .= str_repeat(
"'", $thislen - 5 );
1918 if ( $thislen == 2 ) {
1920 } elseif ( $thislen == 3 ) {
1922 } elseif ( $thislen == 5 ) {
1932 if ( ( $numbold % 2 == 1 ) && ( $numitalics % 2 == 1 ) ) {
1933 $firstsingleletterword = -1;
1934 $firstmultiletterword = -1;
1936 for ( $i = 1; $i < $countarr; $i += 2 ) {
1937 if ( strlen( $arr[$i] ) == 3 ) {
1938 $x1 = substr( $arr[$i - 1], -1 );
1939 $x2 = substr( $arr[$i - 1], -2, 1 );
1940 if ( $x1 ===
' ' ) {
1941 if ( $firstspace == -1 ) {
1944 } elseif ( $x2 ===
' ' ) {
1945 $firstsingleletterword = $i;
1949 } elseif ( $firstmultiletterword == -1 ) {
1950 $firstmultiletterword = $i;
1956 if ( $firstsingleletterword > -1 ) {
1957 $arr[$firstsingleletterword] =
"''";
1958 $arr[$firstsingleletterword - 1] .=
"'";
1959 } elseif ( $firstmultiletterword > -1 ) {
1961 $arr[$firstmultiletterword] =
"''";
1962 $arr[$firstmultiletterword - 1] .=
"'";
1963 } elseif ( $firstspace > -1 ) {
1967 $arr[$firstspace] =
"''";
1968 $arr[$firstspace - 1] .=
"'";
1977 foreach ( $arr as $r ) {
1978 if ( ( $i % 2 ) == 0 ) {
1979 if ( $state ===
'both' ) {
1985 $thislen = strlen( $r );
1986 if ( $thislen == 2 ) {
1988 if ( $state ===
'i' ) {
1991 } elseif ( $state ===
'bi' ) {
1994 } elseif ( $state ===
'ib' ) {
1995 $output .=
'</b></i><b>';
1997 } elseif ( $state ===
'both' ) {
1998 $output .=
'<b><i>' . $buffer .
'</i>';
2004 } elseif ( $thislen == 3 ) {
2006 if ( $state ===
'b' ) {
2009 } elseif ( $state ===
'bi' ) {
2010 $output .=
'</i></b><i>';
2012 } elseif ( $state ===
'ib' ) {
2015 } elseif ( $state ===
'both' ) {
2016 $output .=
'<i><b>' . $buffer .
'</b>';
2022 } elseif ( $thislen == 5 ) {
2024 if ( $state ===
'b' ) {
2025 $output .=
'</b><i>';
2027 } elseif ( $state ===
'i' ) {
2028 $output .=
'</i><b>';
2030 } elseif ( $state ===
'bi' ) {
2031 $output .=
'</i></b>';
2033 } elseif ( $state ===
'ib' ) {
2034 $output .=
'</b></i>';
2036 } elseif ( $state ===
'both' ) {
2037 $output .=
'<i><b>' . $buffer .
'</b></i>';
2048 if ( $state ===
'b' || $state ===
'ib' ) {
2051 if ( $state ===
'i' || $state ===
'bi' || $state ===
'ib' ) {
2054 if ( $state ===
'bi' ) {
2058 if ( $state ===
'both' && $buffer ) {
2059 $output .=
'<b><i>' . $buffer .
'</i></b>';
2073 private function handleExternalLinks( $text ) {
2074 $bits = preg_split( $this->mExtLinkBracketedRegex, $text, -1, PREG_SPLIT_DELIM_CAPTURE );
2075 if ( $bits ===
false ) {
2079 $s = array_shift( $bits );
2082 while ( $i < count( $bits ) ) {
2085 $text = $bits[$i++];
2086 $trail = $bits[$i++];
2088 # The characters '<' and '>' (which were escaped by
2089 # internalRemoveHtmlTags()) should not be included in
2090 # URLs, per RFC 2396.
2092 if ( preg_match(
'/&(lt|gt);/',
$url, $m2, PREG_OFFSET_CAPTURE ) ) {
2093 $text = substr(
$url, $m2[0][1] ) .
' ' . $text;
2094 $url = substr(
$url, 0, $m2[0][1] );
2097 # If the link text is an image URL, replace it with an <img> tag
2098 # This happened by accident in the original parser, but some people used it extensively
2099 $img = $this->maybeMakeExternalImage( $text );
2100 if ( $img !==
false ) {
2106 # Set linktype for CSS
2109 # No link text, e.g. [http:
2110 if ( $text ==
'' ) {
2112 $langObj = $this->getTargetLanguage();
2113 $text =
'[' . $langObj->formatNum( ++$this->mAutonumber ) .
']';
2114 $linktype =
'autonumber';
2116 # Have link text, e.g. [http:
2118 [ $dtrail, $trail ] = Linker::splitTrail( $trail );
2122 if ( preg_match(
'/^(?:' . $this->urlUtils->validAbsoluteProtocols() .
')/', $text ) ) {
2123 $text = $this->getTargetLanguageConverter()->markNoConversion( $text );
2126 $url = Sanitizer::cleanUrl(
$url );
2128 # Use the encoded URL
2129 # This means that users can paste URLs directly into the text
2130 # Funny characters like ö aren't valid in URLs anyway
2131 # This was changed in August 2004
2134 new HtmlArmor( $text ),
2137 $this->getExternalLinkAttribs(
$url )
2138 ) . $dtrail . $trail;
2140 # Register link in the output object.
2141 $this->mOutput->addExternalLink(
$url );
2159 $mainConfig = MediaWikiServices::getInstance()->getMainConfig();
2160 $noFollowLinks = $mainConfig->get( MainConfigNames::NoFollowLinks );
2161 $noFollowNsExceptions = $mainConfig->get( MainConfigNames::NoFollowNsExceptions );
2162 $noFollowDomainExceptions = $mainConfig->get( MainConfigNames::NoFollowDomainExceptions );
2163 $ns = $title ? $title->getNamespace() :
false;
2165 $noFollowLinks && !in_array( $ns, $noFollowNsExceptions )
2166 && !
wfGetUrlUtils()->matchesDomainList( (
string)
$url, $noFollowDomainExceptions )
2186 $rel = self::getExternalLinkRel(
$url, $this->getTitle() ) ??
'';
2188 $target = $this->mOptions->getExternalLinkTarget();
2190 $attribs[
'target'] = $target;
2191 if ( !in_array( $target, [
'_self',
'_parent',
'_top' ] ) ) {
2195 if ( $rel !==
'' ) {
2198 $rel .=
'noreferrer noopener';
2201 if ( $rel !==
'' ) {
2202 $attribs[
'rel'] = $rel;
2218 # Test for RFC 3986 IPv6 syntax
2219 $scheme =
'[a-z][a-z0-9+.-]*:';
2220 $userinfo =
'(?:[a-z0-9\-._~!$&\'()*+,;=:]|%[0-9a-f]{2})*';
2221 $ipv6Host =
'\\[((?:[0-9a-f:]|%3[0-A]|%[46][1-6])+)\\]';
2222 if ( preg_match(
"<^(?:{$scheme})?//(?:{$userinfo}@)?{$ipv6Host}(?:[:/?#].*|)$>i",
$url, $m ) &&
2223 IPUtils::isValid( rawurldecode( $m[1] ) )
2225 $isIPv6 = rawurldecode( $m[1] );
2230 # Make sure unsafe characters are encoded
2231 $url = preg_replace_callback(
2232 '/[\x00-\x20"<>\[\\\\\]^`{|}\x7F-\xFF]+/',
2233 static fn ( $m ) => rawurlencode( $m[0] ),
2238 $end = strlen(
$url );
2240 # Fragment part - 'fragment'
2241 $start = strpos(
$url,
'#' );
2242 if ( $start !==
false && $start < $end ) {
2243 $ret = self::normalizeUrlComponent(
2244 substr(
$url, $start, $end - $start ),
'"#%<>[\]^`{|}' ) . $ret;
2248 # Query part - 'query' minus &=+;
2249 $start = strpos(
$url,
'?' );
2250 if ( $start !==
false && $start < $end ) {
2251 $ret = self::normalizeUrlComponent(
2252 substr(
$url, $start, $end - $start ),
'"#%<>[\]^`{|}&=+;' ) . $ret;
2256 # Path part - 'pchar', remove dot segments
2257 # (find first '/' after the optional '
2258 $start = strpos(
$url,
'//' );
2259 $start = strpos(
$url,
'/', $start ===
false ? 0 : $start + 2 );
2260 if ( $start !==
false && $start < $end ) {
2261 $ret = UrlUtils::removeDotSegments( self::normalizeUrlComponent(
2262 substr(
$url, $start, $end - $start ),
'"#%<>[\]^`{|}/?' ) ) . $ret;
2266 # Scheme and host part - 'pchar'
2267 # (we assume no userinfo or encoded colons in the host)
2268 $ret = self::normalizeUrlComponent(
2269 substr(
$url, 0, $end ),
'"#%<>[\]^`{|}/?' ) . $ret;
2272 if ( $isIPv6 !==
false ) {
2273 $ipv6Host =
"%5B({$isIPv6})%5D";
2274 $ret = preg_replace(
2275 "<^((?:{$scheme})?//(?:{$userinfo}@)?){$ipv6Host}(?=[:/?#]|$)>i",
2284 private static function normalizeUrlComponent(
string $component,
string $unsafe ): string {
2285 $callback = static function (
$matches ) use ( $unsafe ) {
2287 $ord = ord( $char );
2288 if ( $ord > 32 && $ord < 127 && !str_contains( $unsafe, $char ) ) {
2292 # Leave it escaped, but use uppercase for a-f
2296 return preg_replace_callback(
'/%[0-9A-Fa-f]{2}/', $callback, $component );
2307 private function maybeMakeExternalImage(
$url ) {
2308 $imagesfrom = $this->mOptions->getAllowExternalImagesFrom();
2309 $imagesexception = (bool)$imagesfrom;
2311 # $imagesfrom could be either a single string or an array of strings, parse out the latter
2312 if ( $imagesexception && is_array( $imagesfrom ) ) {
2313 $imagematch =
false;
2314 foreach ( $imagesfrom as $match ) {
2315 if ( str_starts_with(
$url, $match ) ) {
2320 } elseif ( $imagesexception ) {
2321 $imagematch = str_starts_with(
$url, $imagesfrom );
2323 $imagematch =
false;
2326 if ( $this->mOptions->getAllowExternalImages()
2327 || ( $imagesexception && $imagematch )
2329 if ( preg_match( self::EXT_IMAGE_REGEX,
$url ) ) {
2331 $text = Linker::makeExternalImage(
$url );
2334 if ( !$text && $this->mOptions->getEnableImageWhitelist()
2335 && preg_match( self::EXT_IMAGE_REGEX,
$url )
2337 $whitelist = explode(
2339 wfMessage(
'external_image_whitelist' )->inContentLanguage()->text()
2342 foreach ( $whitelist as $entry ) {
2343 # Sanitize the regex fragment, make it case-insensitive, ignore blank entries/comments
2344 if ( $entry ===
'' || str_starts_with( $entry,
'#' ) ) {
2348 if ( preg_match(
'/' . str_replace(
'/',
'\\/', $entry ) .
'/i',
$url ) ) {
2349 # Image matches a whitelist entry
2350 $text = Linker::makeExternalImage(
$url );
2365 private function handleInternalLinks( $text ) {
2366 $this->mLinkHolders->merge( $this->handleInternalLinks2( $text ) );
2375 private function handleInternalLinks2( &$s ) {
2376 static $tc =
false, $e1, $e1_img;
2377 # the % is needed to support urlencoded titles as well
2379 $tc = Title::legalChars() .
'#%';
2380 # Match a link having the form [[namespace:link|alternate]]trail
2381 $e1 =
"/^([{$tc}]+)(?:\\|(.+?))?]](.*)\$/sD";
2382 # Match cases where there is no "]]", which might still be images
2383 $e1_img =
"/^([{$tc}]+)\\|(.*)\$/sD";
2386 $holders =
new LinkHolderArray(
2388 $this->getContentLanguageConverter(),
2389 $this->getHookContainer() );
2391 # split the entire text string on occurrences of [[
2392 $a = StringUtils::explode(
'[[',
' ' . $s );
2393 # get the first element (all text up to first [[), and remove the space we added
2396 $line = $a->current(); # Workaround
for broken ArrayIterator::next() that returns "
void"
2397 $s = substr( $s, 1 );
2399 $nottalk = !$this->getTitle()->isTalkPage();
2401 $useLinkPrefixExtension = $this->getTargetLanguage()->linkPrefixExtension();
2403 if ( $useLinkPrefixExtension ) {
2404 # Match the end of a line for a word that's not followed by whitespace,
2405 # e.g. in the case of 'The Arab al[[Razi]]', 'al' will be matched
2406 $charset = $this->contLang->linkPrefixCharset();
2407 $e2 =
"/^((?>.*[^$charset]|))(.+)$/sDu";
2409 if ( preg_match( $e2, $s, $m ) ) {
2410 $first_prefix = $m[2];
2412 $first_prefix =
false;
2416 $first_prefix =
false;
2420 # Some namespaces don't allow subpages
2421 $useSubpages = $this->nsInfo->hasSubpages(
2422 $this->getTitle()->getNamespace()
2425 # Loop for each link
2426 for ( ; $line !==
false && $line !==
null; $a->next(), $line = $a->current() ) {
2427 # Check for excessive memory usage
2428 if ( $holders->isBig() ) {
2430 # Do the existence check, replace the link holders and clear the array
2431 $holders->replace( $s );
2435 if ( $useLinkPrefixExtension ) {
2437 if ( preg_match( $e2, $s, $m ) ) {
2438 [ , $s, $prefix ] = $m;
2443 if ( $first_prefix ) {
2444 $prefix = $first_prefix;
2445 $first_prefix =
false;
2449 $might_be_img =
false;
2451 if ( preg_match( $e1, $line, $m ) ) { # page with normal text or alt
2453 # If we get a ] at the beginning of $m[3] that means we have a link that's something like:
2454 # [[Image:Foo.jpg|[http:
2455 # the real problem is with the $e1 regex
2457 # Still some problems for cases where the ] is meant to be outside punctuation,
2458 # and no image is in sight. See T4095.
2460 && substr( $m[3], 0, 1 ) ===
']'
2461 && strpos( $text,
'[' ) !==
false
2463 $text .=
']'; # so that handleExternalLinks($text) works later
2464 $m[3] = substr( $m[3], 1 );
2466 # fix up urlencoded title texts
2467 if ( str_contains( $m[1],
'%' ) ) {
2468 # Should anchors '#' also be rejected?
2469 $m[1] = str_replace( [
'<',
'>' ], [
'<',
'>' ], rawurldecode( $m[1] ) );
2472 } elseif ( preg_match( $e1_img, $line, $m ) ) {
2473 # Invalid, but might be an image with a link in its caption
2474 $might_be_img =
true;
2476 if ( str_contains( $m[1],
'%' ) ) {
2477 $m[1] = str_replace( [
'<',
'>' ], [
'<',
'>' ], rawurldecode( $m[1] ) );
2480 }
else { # Invalid form; output directly
2481 $s .= $prefix .
'[[' . $line;
2485 $origLink = ltrim( $m[1],
' ' );
2487 # Don't allow internal links to pages containing
2488 # PROTO: where PROTO is a valid URL protocol; these
2489 # should be external links.
2490 if ( preg_match(
'/^(?i:' . $this->urlUtils->validProtocols() .
')/', $origLink ) ) {
2491 $s .= $prefix .
'[[' . $line;
2495 # Make subpage if necessary
2496 if ( $useSubpages ) {
2497 $link = Linker::normalizeSubpageLink(
2498 $this->getTitle(), $origLink, $text
2508 $unstrip = $this->mStripState->killMarkers( $link );
2509 $noMarkers = ( $unstrip === $link );
2511 $nt = $noMarkers ? Title::newFromText( $link ) : null;
2512 if ( $nt ===
null ) {
2513 $s .= $prefix .
'[[' . $line;
2517 $ns = $nt->getNamespace();
2518 $iw = $nt->getInterwiki();
2520 $noforce = !str_starts_with( $origLink,
':' );
2522 if ( $might_be_img ) { #
if this is actually an invalid link
2523 if ( $ns ===
NS_FILE && $noforce ) { # but might be an image
2526 # look at the next 'line' to see if we can close it there
2528 $next_line = $a->current();
2529 if ( $next_line ===
false || $next_line ===
null ) {
2532 $m = explode(
']]', $next_line, 3 );
2533 if ( count( $m ) == 3 ) {
2534 # the first ]] closes the inner link, the second the image
2536 $text .=
"[[{$m[0]}]]{$m[1]}";
2539 } elseif ( count( $m ) == 2 ) {
2540 # if there's exactly one ]] that's fine, we'll keep looking
2541 $text .=
"[[{$m[0]}]]{$m[1]}";
2543 # if $next_line is invalid too, we need look no further
2544 $text .=
'[[' . $next_line;
2549 # we couldn't find the end of this imageLink, so output it raw
2550 # but don't ignore what might be perfectly normal links in the text we've examined
2551 $holders->merge( $this->handleInternalLinks2( $text ) );
2552 $s .=
"{$prefix}[[$link|$text";
2553 # note: no $trail, because without an end, there *is* no trail
2556 }
else { # it
's not an image, so output it raw
2557 $s .= "{$prefix}[[$link|$text";
2558 # note: no $trail, because without an end, there *is* no trail
2563 $wasblank = ( $text == '' );
2567 # Strip off leading ':
'
2568 $text = substr( $text, 1 );
2571 # T6598 madness. Handle the quotes only if they come from the alternate part
2572 # [[Lista d''e paise d''o munno]] -> <a href="...">Lista d''e paise d''o munno</a>
2573 # [[Criticism of Harry Potter|Criticism of ''Harry Potter'']]
2574 # -> <a href="Criticism of Harry Potter">Criticism of <i>Harry Potter</i></a>
2575 $text = $this->doQuotes( $text );
2578 # Link not escaped by : , create the various objects
2579 if ( $noforce && !$nt->wasLocalInterwiki() ) {
2582 $iw && $this->mOptions->getInterwikiMagic() && $nottalk && (
2583 $this->languageNameUtils->getLanguageName(
2585 LanguageNameUtils::AUTONYMS,
2586 LanguageNameUtils::DEFINED
2588 || in_array( $iw, $this->svcOptions->get( MainConfigNames::ExtraInterlanguageLinkPrefixes ) )
2591 # T26502: duplicates are resolved in ParserOutput
2592 $this->mOutput->addLanguageLink( $nt );
2598 $s = preg_replace( '/\n\s*$/
', '', $s . $prefix ) . $trail;
2602 if ( $ns === NS_FILE ) {
2604 # if no parameters were passed, $text
2605 # becomes something like "File:Foo.png",
2606 # which we don't want to pass on to the
2610 # recursively parse links inside the image caption
2611 # actually, this will parse them in any other parameters, too,
2612 # but it might be hard to fix that, and it doesn't matter ATM
2613 $text = $this->handleExternalLinks( $text );
2614 $holders->merge( $this->handleInternalLinks2( $text ) );
2616 # cloak any absolute URLs inside the image markup, so handleExternalLinks() won't touch them
2617 $s .= $prefix . $this->armorLinks(
2618 $this->makeImageInternal( $nt, $text, $holders ) ) . $trail;
2621 # Strip newlines from the left hand context of Category
2623 # See T2087, T87753, T174639, T359886
2624 $s = preg_replace(
'/\n\s*$/',
'', $s . $prefix ) . $trail;
2630 $this->mOutput->addCategory( $nt, $sortkey );
2636 # Self-link checking. For some languages, variants of the title are checked in
2637 # LinkHolderArray::doVariants() to allow batching the existence checks necessary
2638 # for linking to a different variant.
2639 if ( $ns !==
NS_SPECIAL && $nt->equals( $this->getTitle() ) ) {
2640 $s .= $prefix . Linker::makeSelfLinkObj( $nt, $text,
'', $trail,
'',
2641 Sanitizer::escapeIdForLink( $nt->getFragment() ) );
2645 # NS_MEDIA is a pseudo-namespace for linking directly to a file
2646 # @todo FIXME: Should do batch file existence checks, see comment below
2648 # Give extensions a chance to select the file revision for us
2651 $this->hookRunner->onBeforeParserFetchFileAndTitle(
2653 $this, $nt, $options, $descQuery
2655 # Fetch and register the file (file title may be different via hooks)
2656 [ $file, $nt ] = $this->fetchFileAndTitle( $nt, $options );
2657 # Cloak with NOPARSE to avoid replacement in handleExternalLinks
2658 $s .= $prefix . $this->armorLinks(
2659 Linker::makeMediaLinkFile( $nt, $file, $text ) ) . $trail;
2663 # Some titles, such as valid special pages or files in foreign repos, should
2664 # be shown as bluelinks even though they're not included in the page table
2665 # @todo FIXME: isAlwaysKnown() can be expensive for file links; we should really do
2666 # batch file existence checks for NS_FILE and NS_MEDIA
2667 if ( $iw ==
'' && $nt->isAlwaysKnown() ) {
2668 $this->mOutput->addLink( $nt );
2669 $s .= $this->makeKnownLinkHolder( $nt, $text, $trail, $prefix );
2671 # Links will be added to the output link list after checking
2672 $s .= $holders->makeHolder( $nt, $text, $trail, $prefix );
2691 private function makeKnownLinkHolder( LinkTarget $nt, $text =
'', $trail =
'', $prefix =
'' ) {
2692 [ $inside, $trail ] = Linker::splitTrail( $trail );
2694 if ( $text ==
'' ) {
2695 $text = htmlspecialchars( $this->titleFormatter->getPrefixedText( $nt ) );
2699 $nt,
new HtmlArmor(
"$prefix$text$inside" )
2702 return $this->armorLinks( $link ) . $trail;
2715 private function armorLinks( $text ) {
2716 return preg_replace(
'/\b((?i)' . $this->urlUtils->validProtocols() .
')/',
2717 self::MARKER_PREFIX .
"NOPARSE$1", $text );
2728 private function expandMagicVariable( $index, $frame =
false ) {
2733 if ( isset( $this->mVarCache[$index] ) ) {
2734 return $this->mVarCache[$index];
2737 $value = CoreMagicVariables::expand(
2738 $this, $index,
new MWTimestamp( $this->getParseTime() ),
2739 $this->svcOptions, $this->logger
2742 if ( $value ===
null ) {
2746 $this->hookRunner->onParserGetVariableValueSwitch(
2748 $this, $fakeCache, $index, $value, $frame
2752 '@phan-var string $value';
2755 $this->mVarCache[$index] = $value;
2764 private function initializeVariables() {
2765 $variableIDs = $this->magicWordFactory->getVariableIDs();
2767 $this->mVariables = $this->magicWordFactory->newArray( $variableIDs );
2768 $this->mSubstWords = $this->magicWordFactory->getSubstArray();
2790 return $this->getPreprocessor()->preprocessToObj( $text, $flags );
2821 $text, $frame =
false, $argsOnly =
false, array $options = []
2823 # Is there any text? Also, Prevent too big inclusions!
2824 $textSize = strlen( $text );
2825 if ( $textSize < 1 || $textSize > $this->mOptions->getMaxIncludeSize() ) {
2829 if ( $frame ===
false ) {
2830 $frame = $this->getPreprocessor()->newFrame();
2831 } elseif ( !( $frame instanceof
PPFrame ) ) {
2833 __METHOD__ .
" called using plain parameters instead of " .
2834 "a PPFrame instance. Creating custom frame.",
2837 $frame = $this->getPreprocessor()->newCustomFrame( $frame );
2841 if ( $options[
'parsoidTopLevelCall'] ??
false ) {
2842 $ppFlags |= Preprocessor::START_IN_SOL_STATE;
2844 $dom = $this->preprocessToDom( $text, $ppFlags );
2845 $flags = $argsOnly ? PPFrame::NO_TEMPLATES : 0;
2846 if ( $options[
'processNowiki'] ??
false ) {
2847 $flags |= PPFrame::PROCESS_NOWIKI;
2849 $text = $frame->expand( $dom, $flags );
2856 $this->mStripExtTags = $val;
2887 # does no harm if $current and $max are present but are unnecessary for the message
2888 # Not doing ->inLanguage( $this->mOptions->getUserLangObj() ), since this is shown
2889 # only during preview, and that would split the parser cache unnecessarily.
2890 $this->mOutput->addWarningMsg(
2891 "$limitationType-warning",
2892 Message::numParam( $current ),
2893 Message::numParam( $max )
2895 $this->addTrackingCategory(
"$limitationType-category" );
2926 $forceRawInterwiki =
false;
2928 $isChildObj =
false;
2930 $isLocalObj =
false;
2932 # Title object, where $text came from
2935 # $part1 is the bit before the first |, and must contain only title characters.
2936 # Various prefixes will be stripped from it later.
2937 $titleWithSpaces = $frame->
expand( $piece[
'title'] );
2938 $part1 = trim( $titleWithSpaces );
2941 # Original title text preserved for various purposes
2942 $originalTitle = $part1;
2944 # $args is a list of argument nodes, starting from index 0, not including $part1
2945 $args = $piece[
'parts'];
2947 $profileSection =
null;
2949 $sawDeprecatedTemplateEquals =
false;
2951 $isParsoid = $this->mOptions->getUseParsoid();
2956 $substMatch = $this->mSubstWords->matchStartAndRemove( $part1 );
2957 $part1 = trim( $part1 );
2959 # Possibilities for substMatch: "subst", "safesubst" or FALSE
2960 # Decide whether to expand template or keep wikitext as-is.
2961 if ( $this->ot[
'wiki'] ) {
2962 if ( $substMatch ===
false ) {
2963 $literal =
true; # literal when in PST with no prefix
2965 $literal =
false; # expand when in PST with subst: or safesubst:
2968 if ( $substMatch ==
'subst' ) {
2969 $literal =
true; # literal when not in PST with plain subst:
2971 $literal =
false; # expand when not in PST with safesubst: or no prefix
2982 if ( !$found && $args->getLength() == 0 ) {
2983 $id = $this->mVariables->matchStartToEnd( $part1 );
2984 if ( $id !==
false ) {
2985 if ( str_contains( $part1,
':' ) ) {
2987 'Registering a magic variable with a name including a colon',
2988 '1.39',
false,
false
2991 $text = $this->expandMagicVariable( $id, $frame );
2996 # MSG, MSGNW and RAW
2999 $mwMsgnw = $this->magicWordFactory->get(
'msgnw' );
3000 if ( $mwMsgnw->matchStartAndRemove( $part1 ) ) {
3003 # Remove obsolete MSG:
3004 $mwMsg = $this->magicWordFactory->get(
'msg' );
3005 $mwMsg->matchStartAndRemove( $part1 );
3009 $mwRaw = $this->magicWordFactory->get(
'raw' );
3010 if ( $mwRaw->matchStartAndRemove( $part1 ) ) {
3011 $forceRawInterwiki =
true;
3018 if ( preg_match(
'/[::]/u', $part1, $colonMatches, PREG_OFFSET_CAPTURE ) ) {
3019 [ $colonStr, $colonPos ] = $colonMatches[0];
3020 $func = substr( $part1, 0, $colonPos );
3021 $funcArgs = [ trim( substr( $part1, $colonPos + strlen( $colonStr ) ) ) ];
3022 $argsLength = $args->getLength();
3023 for ( $i = 0; $i < $argsLength; $i++ ) {
3024 $funcArgs[] = $args->item( $i );
3027 $result = $this->callParserFunction(
3028 $frame, $func, $funcArgs, $isParsoid && $piece[
'lineStart']
3032 if ( isset( $result[
'title'] ) ) {
3033 $title = $result[
'title'];
3035 if ( isset( $result[
'found'] ) ) {
3036 $found = $result[
'found'];
3038 if ( array_key_exists(
'text', $result ) ) {
3040 $text = $result[
'text'];
3042 if ( isset( $result[
'nowiki'] ) ) {
3043 $nowiki = $result[
'nowiki'];
3045 if ( isset( $result[
'isHTML'] ) ) {
3046 $isHTML = $result[
'isHTML'];
3048 if ( isset( $result[
'isRawHTML'] ) ) {
3049 $isRawHTML = $result[
'isRawHTML'];
3051 if ( isset( $result[
'forceRawInterwiki'] ) ) {
3052 $forceRawInterwiki = $result[
'forceRawInterwiki'];
3054 if ( isset( $result[
'isChildObj'] ) ) {
3055 $isChildObj = $result[
'isChildObj'];
3057 if ( isset( $result[
'isLocalObj'] ) ) {
3058 $isLocalObj = $result[
'isLocalObj'];
3063 # Finish mangling title and then check for loops.
3064 # Set $title to a Title object and $titleText to the PDBK
3067 # Split the title into page and subpage
3069 $relative = Linker::normalizeSubpageLink(
3070 $this->getTitle(), $part1, $subpage
3072 if ( $part1 !== $relative ) {
3074 $ns = $this->getTitle()->getNamespace();
3076 $title = Title::newFromText( $part1, $ns );
3078 $titleText = $title->getPrefixedText();
3079 # Check for language variants if the template is not found
3080 if ( $this->getTargetLanguageConverter()->hasVariants() && $title->getArticleID() == 0 ) {
3081 $this->getTargetLanguageConverter()->findVariantLink( $part1, $title,
true );
3083 # Do recursion depth check
3084 $limit = $this->mOptions->getMaxTemplateDepth();
3085 if ( $frame->depth >= $limit ) {
3087 $text =
'<span class="error">'
3088 .
wfMessage(
'parser-template-recursion-depth-warning' )
3089 ->numParams( $limit )->inContentLanguage()->text()
3095 # Load from database
3096 if ( !$found && $title ) {
3097 $profileSection = $this->mProfiler->scopedProfileIn( $title->getPrefixedDBkey() );
3098 if ( !$title->isExternal() ) {
3099 if ( $title->isSpecialPage()
3100 && $this->mOptions->getAllowSpecialInclusion()
3101 && ( $this->ot[
'html'] ||
3103 ( !$this->mStripExtTags && $this->ot[
'pre'] ) )
3105 $specialPage = $this->specialPageFactory->getPage( $title->getDBkey() );
3110 $argsLength = $args->getLength();
3111 for ( $i = 0; $i < $argsLength; $i++ ) {
3112 $bits = $args->item( $i )->splitArg();
3113 if ( strval( $bits[
'index'] ) ===
'' ) {
3114 $name = trim( $frame->
expand( $bits[
'name'], PPFrame::STRIP_COMMENTS ) );
3115 $value = trim( $frame->
expand( $bits[
'value'] ) );
3116 $pageArgs[$name] = $value;
3121 if ( $this->incrementExpensiveFunctionCount() ) {
3124 $context->setRequest(
new FauxRequest( $pageArgs ) );
3125 if ( $specialPage && $specialPage->maxIncludeCacheTime() === 0 ) {
3126 $context->setUser( $this->userFactory->newFromUserIdentity( $this->getUserIdentity() ) );
3129 $context->setUser( User::newFromName(
'127.0.0.1',
false ) );
3131 $context->setLanguage( $this->mOptions->getUserLangObj() );
3132 $ret = $this->specialPageFactory->capturePath( $title, $context, $this->getLinkRenderer() );
3134 $text = $context->getOutput()->getHTML();
3135 $this->mOutput->addOutputPageMetadata( $context->getOutput() );
3138 if ( $specialPage && $specialPage->maxIncludeCacheTime() !==
false ) {
3139 $this->mOutput->updateRuntimeAdaptiveExpiry(
3140 $specialPage->maxIncludeCacheTime()
3145 } elseif ( $this->nsInfo->isNonincludable( $title->getNamespace() ) ) {
3146 $found =
false; # access denied
3147 $this->logger->debug(
3149 ": template inclusion denied for " . $title->getPrefixedDBkey()
3152 [ $text, $title ] = $this->getTemplateDom( $title, $isParsoid && $piece[
'lineStart'] );
3153 if ( $text !==
false ) {
3158 $title->getDBkey() ===
'=' &&
3159 $originalTitle ===
'='
3165 $sawDeprecatedTemplateEquals =
true;
3170 # If the title is valid but undisplayable, make a link to it
3171 if ( !$found && ( $this->ot[
'html'] || $this->ot[
'pre'] ) ) {
3172 $text =
"[[:$titleText]]";
3175 } elseif ( $title->isTrans() ) {
3176 # Interwiki transclusion
3177 if ( $this->ot[
'html'] && !$forceRawInterwiki ) {
3178 $text = $this->interwikiTransclude( $title,
'render' );
3181 $text = $this->interwikiTransclude( $title,
'raw' );
3182 # Preprocess it like a template
3183 $sol = ( $isParsoid && $piece[
'lineStart'] ) ? Preprocessor::START_IN_SOL_STATE : 0;
3184 $text = $this->preprocessToDom( $text, Preprocessor::DOM_FOR_INCLUSION | $sol );
3190 # Do infinite loop check
3191 # This has to be done after redirect resolution to avoid infinite loops via redirects
3194 $text =
'<span class="error">'
3195 .
wfMessage(
'parser-template-loop-warning', $titleText )->inContentLanguage()->text()
3197 $this->addTrackingCategory(
'template-loop-category' );
3198 $this->mOutput->addWarningMsg(
3199 'template-loop-warning',
3202 $this->logger->debug( __METHOD__ .
": template loop broken at '$titleText'" );
3206 # If we haven't found text to substitute by now, we're done
3207 # Recover the source wikitext and return it
3210 if ( $profileSection ) {
3211 $this->mProfiler->scopedProfileOut( $profileSection );
3213 return [
'object' => $text ];
3216 # Expand DOM-style return values in a child frame
3217 if ( $isChildObj ) {
3218 # Clean up argument array
3219 $newFrame = $frame->
newChild( $args, $title );
3222 $text = $newFrame->expand( $text, PPFrame::RECOVER_ORIG );
3223 } elseif ( $titleText !==
false && $newFrame->isEmpty() ) {
3224 # Expansion is eligible for the empty-frame cache
3225 $text = $newFrame->cachedExpand( $titleText, $text );
3227 # Uncached expansion
3228 $text = $newFrame->expand( $text );
3231 if ( $isLocalObj && $nowiki ) {
3232 $text = $frame->
expand( $text, PPFrame::RECOVER_ORIG );
3233 $isLocalObj =
false;
3236 if ( $profileSection ) {
3237 $this->mProfiler->scopedProfileOut( $profileSection );
3240 $sawDeprecatedTemplateEquals &&
3241 $this->mStripState->unstripBoth( $text ) !==
'='
3245 $this->addTrackingCategory(
'template-equals-category' );
3246 $this->mOutput->addWarningMsg(
'template-equals-warning' );
3249 # Replace raw HTML by a placeholder
3252 $text = $this->insertStripItem( $text );
3253 } elseif ( $isRawHTML ) {
3254 $marker = self::MARKER_PREFIX .
"-pf-"
3255 . sprintf(
'%08X', $this->mMarkerIndex++ ) . self::MARKER_SUFFIX;
3259 $this->mStripState->addNoWiki( $marker, $text );
3261 } elseif ( $nowiki && ( $this->ot[
'html'] || $this->ot[
'pre'] ) ) {
3262 # Escape nowiki-style return values
3265 } elseif ( is_string( $text )
3266 && !$piece[
'lineStart']
3267 && preg_match(
'/^(?:{\\||:|;|#|\*)/', $text )
3275 $text =
"\n" . $text;
3278 if ( is_string( $text ) && !$this->incrementIncludeSize(
'post-expand', strlen( $text ) ) ) {
3279 # Error, oversize inclusion
3280 if ( $titleText !==
false ) {
3281 # Make a working, properly escaped link if possible (T25588)
3282 $text =
"[[:$titleText]]";
3284 # This will probably not be a working link, but at least it may
3285 # provide some hint of where the problem is
3286 $originalTitle = preg_replace(
'/^:/',
'', $originalTitle );
3287 $text =
"[[:$originalTitle]]";
3289 $text .= $this->insertStripItem(
'<!-- WARNING: template omitted, '
3290 .
'post-expand include size too large -->' );
3291 $this->limitationWarn(
'post-expand-template-inclusion' );
3294 if ( $isLocalObj ) {
3295 $ret = [
'object' => $text ];
3297 $ret = [
'text' => $text ];
3329 # Case sensitive functions
3330 if ( isset( $this->mFunctionSynonyms[1][$function] ) ) {
3331 $function = $this->mFunctionSynonyms[1][$function];
3333 # Case insensitive functions
3334 $function = $this->contLang->lc( $function );
3335 if ( isset( $this->mFunctionSynonyms[0][$function] ) ) {
3336 $function = $this->mFunctionSynonyms[0][$function];
3338 return [
'found' => false ];
3342 [ $callback, $flags ] = $this->mFunctionHooks[$function];
3344 $allArgs = [ $this ];
3345 if ( $flags & self::SFH_OBJECT_ARGS ) {
3346 # Convert arguments to PPNodes and collect for appending to $allArgs
3348 foreach ( $args as $k => $v ) {
3349 if ( $v instanceof
PPNode || $k === 0 ) {
3352 $funcArgs[] = $this->mPreprocessor->newPartNodeArray( [ $k => $v ] )->item( 0 );
3356 # Add a frame parameter, and pass the arguments as an array
3357 $allArgs[] = $frame;
3358 $allArgs[] = $funcArgs;
3360 # Convert arguments to plain text and append to $allArgs
3361 foreach ( $args as $k => $v ) {
3362 if ( $v instanceof
PPNode ) {
3363 $allArgs[] = trim( $frame->
expand( $v ) );
3364 } elseif ( is_int( $k ) && $k >= 0 ) {
3365 $allArgs[] = trim( $v );
3367 $allArgs[] = trim(
"$k=$v" );
3372 $result = $callback( ...$allArgs );
3374 # The interface for function hooks allows them to return a wikitext
3375 # string or an array containing the string and any flags. This mungs
3376 # things around to match what this method should return.
3377 if ( !is_array( $result ) ) {
3383 if ( isset( $result[0] ) && !isset( $result[
'text'] ) ) {
3384 $result[
'text'] = $result[0];
3386 unset( $result[0] );
3392 $noparse = $result[
'noparse'] ??
true;
3394 $preprocessFlags = $result[
'preprocessFlags'] ?? 0;
3395 if ( $inSolState ) {
3396 $preprocessFlags |= Preprocessor::START_IN_SOL_STATE;
3398 $result[
'text'] = $this->preprocessToDom( $result[
'text'], $preprocessFlags );
3399 $result[
'isChildObj'] =
true;
3424 $cacheTitle = $title;
3425 $titleKey = CacheKeyHelper::getKeyForPage( $title );
3427 if ( isset( $this->mTplRedirCache[$titleKey] ) ) {
3428 [ $ns, $dbk ] = $this->mTplRedirCache[$titleKey];
3429 $title = Title::makeTitle( $ns, $dbk );
3430 $titleKey = CacheKeyHelper::getKeyForPage( $title );
3434 $titleKey =
"$titleKey:sol=" . ( $inSolState ?
"0" :
"1" );
3435 if ( isset( $this->mTplDomCache[$titleKey] ) ) {
3436 return [ $this->mTplDomCache[$titleKey], $title ];
3439 # Cache miss, go to the database
3442 [ $text, $title ] = $this->fetchTemplateAndTitle( $title );
3444 if ( $text ===
false ) {
3445 $this->mTplDomCache[$titleKey] =
false;
3446 return [
false, $title ];
3449 $flags = Preprocessor::DOM_FOR_INCLUSION | ( $inSolState ? Preprocessor::START_IN_SOL_STATE : 0 );
3450 $dom = $this->preprocessToDom( $text, $flags );
3451 $this->mTplDomCache[$titleKey] = $dom;
3453 if ( !$title->isSameLinkAs( $cacheTitle ) ) {
3454 $this->mTplRedirCache[ CacheKeyHelper::getKeyForPage( $cacheTitle ) ] =
3455 [ $title->getNamespace(), $title->getDBkey() ];
3458 return [ $dom, $title ];
3475 $cacheKey = CacheKeyHelper::getKeyForPage( $link );
3476 if ( !$this->currentRevisionCache ) {
3477 $this->currentRevisionCache =
new MapCacheLRU( 100 );
3479 if ( !$this->currentRevisionCache->has( $cacheKey ) ) {
3480 $title = Title::newFromLinkTarget( $link );
3483 $this->mOptions->getCurrentRevisionRecordCallback()(
3487 if ( $revisionRecord ===
false ) {
3490 $revisionRecord =
null;
3492 $this->currentRevisionCache->set( $cacheKey, $revisionRecord );
3494 return $this->currentRevisionCache->get( $cacheKey );
3504 $key = CacheKeyHelper::getKeyForPage( $link );
3506 $this->currentRevisionCache &&
3507 $this->currentRevisionCache->has( $key )
3527 $page = Title::newFromLinkTarget( $link );
3530 $revRecord = MediaWikiServices::getInstance()
3531 ->getRevisionLookup()
3532 ->getKnownCurrentRevision( $page );
3544 $title = Title::newFromLinkTarget( $link );
3547 $templateCb = $this->mOptions->getTemplateCallback();
3548 $stuff = $templateCb( $title, $this );
3549 $revRecord = $stuff[
'revision-record'] ??
null;
3551 $text = $stuff[
'text'];
3552 if ( is_string( $stuff[
'text'] ) ) {
3554 $text = strtr( $text,
"\x7f",
"?" );
3556 $finalTitle = $stuff[
'finalTitle'] ?? $title;
3557 foreach ( ( $stuff[
'deps'] ?? [] ) as $dep ) {
3558 $this->mOutput->addTemplate( $dep[
'title'], $dep[
'page_id'], $dep[
'rev_id'] );
3559 if ( $dep[
'title']->equals( $this->getTitle() ) && $revRecord instanceof
RevisionRecord ) {
3562 $sha1 = $revRecord->getSha1();
3566 $this->setOutputFlag( ParserOutputFlags::VARY_REVISION_SHA1,
'Self transclusion' );
3567 $this->getOutput()->setRevisionUsedSha1Base36( $sha1 );
3571 return [ $text, $finalTitle ];
3585 $title = Title::castFromLinkTarget( $page );
3586 $text = $skip =
false;
3587 $finalTitle = $title;
3590 $contextTitle = $parser ? $parser->getTitle() :
null;
3592 # Loop to fetch the article, with up to 2 redirects
3594 # Note that $title (including redirect targets) could be
3595 # external; we do allow hooks a chance to redirect the
3596 # external title to a local one (which might be useful), but
3597 # are careful not to add external titles to the dependency
3600 $services = MediaWikiServices::getInstance();
3601 $revLookup = $services->getRevisionLookup();
3602 $hookRunner =
new HookRunner( $services->getHookContainer() );
3603 for ( $i = 0; $i < 3 && is_object( $title ); $i++ ) {
3604 # Give extensions a chance to select the revision instead
3605 $revRecord =
null; # Assume no hook
3606 $origTitle = $title;
3607 $titleChanged =
false;
3610 # contain fragments or even represent an attempt to transclude
3611 # a broken or otherwise-missing
Title, which the hook may
3612 # fix up. Similarly, the $contextTitle may represent a special
3613 # page or other page which
"exists" as a parsing context but
3615 $contextTitle, $title,
3621 if ( !$title->isExternal() ) {
3624 'page_id' => $title->getArticleID(),
3631 if ( !$revRecord ) {
3633 $revRecord = $parser->fetchCurrentRevisionRecordOfTitle( $title );
3635 $revRecord = $revLookup->getRevisionByTitle( $title );
3639 # Update title, as $revRecord may have been changed by hook
3640 $title = Title::newFromPageIdentity( $revRecord->getPage() );
3644 'page_id' => $revRecord->getPageId(),
3645 'rev_id' => $revRecord->getId(),
3647 } elseif ( !$title->isExternal() ) {
3650 'page_id' => $title->getArticleID(),
3654 if ( !$title->equals( $origTitle ) ) {
3655 # If we fetched a rev from a different title, register
3656 # the original title too...
3657 if ( !$origTitle->isExternal() ) {
3659 'title' => $origTitle,
3660 'page_id' => $origTitle->getArticleID(),
3664 $titleChanged =
true;
3666 # If there is no current revision, there is no page
3667 if ( $revRecord ===
null || $revRecord->getId() ===
null ) {
3668 $linkCache = $services->getLinkCache();
3669 $linkCache->addBadLinkObj( $title );
3672 if ( $titleChanged && !$revRecord->hasSlot( SlotRecord::MAIN ) ) {
3677 $finalTitle = $title;
3680 if ( $revRecord->hasSlot( SlotRecord::MAIN ) ) {
3681 $content = $revRecord->getContent( SlotRecord::MAIN );
3682 $text = $content ? $content->getWikitextForTransclusion() :
null;
3687 if ( $text ===
false || $text ===
null ) {
3692 $message =
wfMessage( $services->getContentLanguage()->
3693 lcfirst( $title->getText() ) )->inContentLanguage();
3694 if ( !$message->exists() ) {
3698 $text = $message->plain();
3708 $finalTitle = $title;
3709 $title = $content->getRedirectTarget();
3718 'revision-record' => $revRecord ?:
false,
3720 'finalTitle' => $finalTitle,
3735 $file = $this->fetchFileNoRegister( $link, $options );
3737 $time = $file ? $file->getTimestamp() :
false;
3738 $sha1 = $file ? $file->getSha1() :
false;
3739 # Register the file as a dependency...
3740 $this->mOutput->addImage( $link, $time, $sha1 );
3741 if ( $file && !$link->isSameLinkAs( $file->getTitle() ) ) {
3742 # Update fetched file title after resolving redirects, etc.
3743 $link = $file->getTitle();
3744 $this->mOutput->addImage( $link, $time, $sha1 );
3747 $title = Title::newFromLinkTarget( $link );
3748 return [ $file, $title ];
3762 if ( isset( $options[
'broken'] ) ) {
3765 $repoGroup = MediaWikiServices::getInstance()->getRepoGroup();
3766 if ( isset( $options[
'sha1'] ) ) {
3767 $file = $repoGroup->findFileFromKey( $options[
'sha1'], $options );
3769 $link = TitleValue::newFromLinkTarget( $link );
3770 $file = $repoGroup->findFile( $link, $options );
3786 if ( !$this->svcOptions->get( MainConfigNames::EnableScaryTranscluding ) ) {
3787 return wfMessage(
'scarytranscludedisabled' )->inContentLanguage()->text();
3791 $title = Title::newFromLinkTarget( $link );
3793 $url = $title->getFullURL( [
'action' => $action ] );
3794 if ( strlen(
$url ) > 1024 ) {
3795 return wfMessage(
'scarytranscludetoolong' )->inContentLanguage()->text();
3798 $wikiId = $title->getTransWikiID();
3800 $fname = __METHOD__;
3802 $cache = $this->wanCache;
3803 $data = $cache->getWithSetCallback(
3804 $cache->makeGlobalKey(
3805 'interwiki-transclude',
3806 ( $wikiId !==
false ) ? $wikiId :
'external',
3809 $this->svcOptions->get( MainConfigNames::TranscludeCacheExpiry ),
3810 function ( $oldValue, &$ttl ) use (
$url, $fname, $cache ) {
3811 $req = $this->httpRequestFactory->create(
$url, [], $fname );
3813 $status = $req->execute();
3814 if ( !$status->isOK() ) {
3815 $ttl = $cache::TTL_UNCACHEABLE;
3816 } elseif ( $req->getResponseHeader(
'X-Database-Lagged' ) !==
null ) {
3817 $ttl = min( $cache::TTL_LAGGED, $ttl );
3821 'text' => $status->isOK() ? $req->getContent() :
null,
3822 'code' => $req->getStatus()
3826 'checkKeys' => ( $wikiId !== false )
3827 ? [ $cache->makeGlobalKey(
'interwiki-page', $wikiId, $title->getDBkey() ) ]
3829 'pcGroup' =>
'interwiki-transclude:5',
3830 'pcTTL' => $cache::TTL_PROC_LONG
3834 if ( is_string( $data[
'text'] ) ) {
3835 $text = $data[
'text'];
3836 } elseif ( $data[
'code'] != 200 ) {
3838 $text =
wfMessage(
'scarytranscludefailed-httpstatus' )
3839 ->params(
$url, $data[
'code'] )->inContentLanguage()->text();
3841 $text =
wfMessage(
'scarytranscludefailed',
$url )->inContentLanguage()->text();
3858 $parts = $piece[
'parts'];
3859 $nameWithSpaces = $frame->
expand( $piece[
'title'] );
3860 $argName = trim( $nameWithSpaces );
3863 if ( $text ===
false && $parts->getLength() > 0
3864 && ( $this->ot[
'html']
3866 || ( $this->ot[
'wiki'] && $frame->
isTemplate() )
3869 # No match in frame, use the supplied default
3870 $object = $parts->item( 0 )->getChildren();
3872 if ( !$this->incrementIncludeSize(
'arg', strlen( $text ) ) ) {
3873 $error =
'<!-- WARNING: argument omitted, expansion size too large -->';
3874 $this->limitationWarn(
'post-expand-template-argument' );
3877 if ( $text ===
false && $object ===
false ) {
3881 if ( $error !==
false ) {
3884 if ( $object !==
false ) {
3885 $ret = [
'object' => $object ];
3887 $ret = [
'text' => $text ];
3895 return $parsoidSiteConfig->tagNeedsNowikiStrippedInTagPF( $lowerTagName );
3918 static $errorStr =
'<span class="error">';
3920 $name = $frame->
expand( $params[
'name'] );
3921 if ( str_starts_with( $name, $errorStr ) ) {
3928 $attrText = !isset( $params[
'attr'] ) ?
'' : $frame->
expand( $params[
'attr'] );
3929 if ( str_starts_with( $attrText, $errorStr ) ) {
3937 $content = !isset( $params[
'inner'] ) ? null : $frame->
expand( $params[
'inner'] );
3939 $marker = self::MARKER_PREFIX .
"-$name-"
3940 . sprintf(
'%08X', $this->mMarkerIndex++ ) . self::MARKER_SUFFIX;
3942 $normalizedName = strtolower( $name );
3943 $isNowiki = $normalizedName ===
'nowiki';
3944 $markerType = $isNowiki ?
'nowiki' :
'general';
3945 $extra = $isNowiki ?
'nowiki' :
null;
3946 if ( !$this->mStripExtTags ) {
3947 $processNowiki =
true;
3949 if ( $this->ot[
'html'] || ( $processNowiki && $isNowiki ) ) {
3950 $attributes = Sanitizer::decodeTagAttributes( $attrText );
3952 if ( isset( $params[
'attributes'] ) ) {
3953 $attributes += $params[
'attributes'];
3956 if ( isset( $this->mTagHooks[$normalizedName] ) ) {
3959 $output = $this->mTagHooks[$normalizedName]( $content, $attributes, $this, $frame );
3961 $output =
'<span class="error">Invalid tag extension name: ' .
3962 htmlspecialchars( $normalizedName ) .
'</span>';
3965 if ( is_array( $output ) ) {
3968 $output = $flags[0];
3969 if ( isset( $flags[
'isRawHTML'] ) ) {
3970 $markerType =
'nowiki';
3972 if ( isset( $flags[
'markerType'] ) ) {
3973 $markerType = $flags[
'markerType'];
3979 if ( isset( $params[
'attributes'] ) ) {
3980 foreach ( $params[
'attributes'] as $attrName => $attrValue ) {
3981 $attrText .=
' ' . htmlspecialchars( $attrName ) .
'="' .
3982 htmlspecialchars( $this->getStripState()->unstripBoth( $attrValue ), ENT_COMPAT ) .
'"';
3985 if ( $content ===
null ) {
3986 $output =
"<$name$attrText/>";
3988 $close = $params[
'close'] ===
null ?
'' : $frame->
expand( $params[
'close'] );
3989 if ( str_starts_with( $close, $errorStr ) ) {
3993 $output =
"<$name$attrText>$content$close";
3995 if ( !$this->mStripExtTags ) {
3996 $markerType =
'exttag';
4000 if ( $markerType ===
'none' ) {
4002 } elseif ( $markerType ===
'nowiki' ) {
4003 $this->mStripState->addNoWiki( $marker, $output, $extra );
4004 } elseif ( $markerType ===
'general' ) {
4005 $this->mStripState->addGeneral( $marker, $output );
4006 } elseif ( $markerType ===
'exttag' ) {
4007 $this->mStripState->addExtTag( $marker, $output );
4009 throw new UnexpectedValueException( __METHOD__ .
': invalid marker type' );
4021 private function incrementIncludeSize( $type, $size ) {
4022 if ( $this->mIncludeSizes[$type] + $size > $this->mOptions->getMaxIncludeSize() ) {
4025 $this->mIncludeSizes[$type] += $size;
4035 $this->mExpensiveFunctionCount++;
4036 return $this->mExpensiveFunctionCount <= $this->mOptions->getExpensiveParserFunctionLimit();
4046 private function handleDoubleUnderscore( $text ) {
4047 # The position of __TOC__ needs to be recorded
4048 $mw = $this->magicWordFactory->get(
'toc' );
4050 if ( $mw->match( $text ) ) {
4051 $this->mShowToc =
true;
4052 $this->mForceTocPosition =
true;
4053 # record the alias used
4054 preg_match( $mw->getRegex(), $text, $tocAlias );
4056 # Set a placeholder. At the end we'll fill it in with the TOC.
4057 $text = $mw->replace( self::TOC_PLACEHOLDER, $text, 1 );
4059 # Only keep the first one.
4060 $text = $mw->replace(
'', $text );
4063 # Now match and remove the rest of them
4064 $mwa = $this->magicWordFactory->getDoubleUnderscoreArray();
4065 $this->mDoubleUnderscores = $mwa->matchAndRemove( $text, returnAlias: true );
4067 # For consistency with all other double-underscores (see below)
4068 $this->mDoubleUnderscores[
'toc'] = $tocAlias[0];
4071 if ( isset( $this->mDoubleUnderscores[
'nogallery'] ) ) {
4072 $this->mOutput->setNoGallery(
true );
4074 if ( isset( $this->mDoubleUnderscores[
'notoc'] ) && !$this->mForceTocPosition ) {
4075 $this->mShowToc =
false;
4077 if ( isset( $this->mDoubleUnderscores[
'hiddencat'] )
4078 && $this->getTitle()->getNamespace() ===
NS_CATEGORY
4080 $this->addTrackingCategory(
'hidden-category-category' );
4082 # (T10068) Allow control over whether robots index a page.
4083 # __INDEX__ always overrides __NOINDEX__, see T16899
4084 if ( isset( $this->mDoubleUnderscores[
'noindex'] ) && $this->getTitle()->canUseNoindex() ) {
4085 $this->mOutput->setIndexPolicy(
'noindex' );
4086 $this->addTrackingCategory(
'noindex-category' );
4088 if ( isset( $this->mDoubleUnderscores[
'index'] ) && $this->getTitle()->canUseNoindex() ) {
4089 $this->mOutput->setIndexPolicy(
'index' );
4090 $this->addTrackingCategory(
'index-category' );
4093 foreach ( $this->mDoubleUnderscores as $key => $alias ) {
4094 # Cache all double underscores in the database
4095 $this->mOutput->setUnsortedPageProperty( $key );
4096 # Check for deprecated local aliases (T407289)
4097 $ascii = str_starts_with( $alias,
'__' ) && str_ends_with( $alias,
'__' );
4098 $wide = str_starts_with( $alias,
'__' ) && str_ends_with( $alias,
'__' );
4099 if ( !( $ascii || $wide ) ) {
4100 $this->addTrackingCategory(
'bad-double-underscore-category' );
4114 return $this->trackingCategories->addTrackingCategory(
4115 $this->mOutput, $msg, $this->getPage()
4136 ->inLanguage( $this->getTargetLanguage() )
4137 ->page( $this->getPage() );
4140 private function cleanUpTocLine( Node $container ) {
4141 '@phan-var Element|DocumentFragment $container';
4144 # * <sup> and <sub> (T10393)
4148 # * <span dir="rtl"> and <span dir="ltr"> (T37167)
4149 # * <s> and <strike> (T35715)
4151 # We strip any parameter from accepted tags, except dir="rtl|ltr" from <span>,
4152 # to allow setting directionality in toc items.
4153 $allowedTags = [
'span',
'sup',
'sub',
'bdi',
'i',
'b',
's',
'strike',
'q' ];
4154 $node = $container->firstChild;
4155 while ( $node !==
null ) {
4156 $next = $node->nextSibling;
4157 if ( $node instanceof Element ) {
4158 $nodeName = DOMUtils::nodeName( $node );
4159 if ( in_array( $nodeName, [
'style',
'script' ],
true ) ) {
4160 # Remove any <style> or <script> tags (T198618)
4161 DOMCompat::remove( $node );
4162 } elseif ( in_array( $nodeName, $allowedTags,
true ) ) {
4165 foreach ( $node->attributes as $attr ) {
4167 $nodeName ===
'span' && $attr->name ===
'dir'
4168 && ( $attr->value ===
'rtl' || $attr->value ===
'ltr' )
4173 $removeAttrs[] = $attr;
4175 foreach ( $removeAttrs as $attr ) {
4176 $node->removeAttributeNode( $attr );
4178 $this->cleanUpTocLine( $node );
4179 # Strip '<span></span>', which is the result from the above if
4180 # <span id="foo"></span> is used to produce an additional anchor
4182 if ( $nodeName ===
'span' && !$node->hasChildNodes() ) {
4183 DOMCompat::remove( $node );
4187 if ( $node->firstChild !==
null ) {
4188 $next = $node->firstChild;
4190 while ( $childNode = $node->firstChild ) {
4191 $node->parentNode->insertBefore( $childNode, $node );
4194 DOMCompat::remove( $node );
4196 } elseif ( $node instanceof Comment ) {
4199 DOMCompat::remove( $node );
4220 private function finalizeHeadings( $text, $origText, $isMain =
true ) {
4221 # Inhibit editsection links if requested in the page
4222 if ( isset( $this->mDoubleUnderscores[
'noeditsection'] ) ) {
4223 $maybeShowEditLink =
false;
4225 $maybeShowEditLink =
true;
4228 # Get all headlines for numbering them and adding funky stuff like [edit]
4229 # links - this is for later, but we need the number of headlines right now
4230 # NOTE: white space in headings have been trimmed in handleHeadings. They shouldn't
4231 # be trimmed here since whitespace in HTML headings is significant.
4233 $numMatches = preg_match_all(
4234 '/<H(?P<level>[1-6])(?P<attrib>.*?>)(?P<header>[\s\S]*?)<\/H[1-6] *>/i',
4239 # if there are fewer than 4 headlines in the article, do not show TOC
4240 # unless it's been explicitly enabled.
4241 $enoughToc = $this->mShowToc &&
4242 ( ( $numMatches >= 4 ) || $this->mForceTocPosition );
4244 # Allow user to stipulate that a page should have a "new section"
4245 # link added via __NEWSECTIONLINK__
4246 if ( isset( $this->mDoubleUnderscores[
'newsectionlink'] ) ) {
4247 $this->mOutput->setNewSection(
true );
4250 # Allow user to remove the "new section"
4251 # link via __NONEWSECTIONLINK__
4252 if ( isset( $this->mDoubleUnderscores[
'nonewsectionlink'] ) ) {
4253 $this->mOutput->setHideNewSection(
true );
4256 # if the string __FORCETOC__ (not case-sensitive) occurs in the HTML,
4257 # override above conditions and always show TOC above first header
4258 if ( isset( $this->mDoubleUnderscores[
'forcetoc'] ) ) {
4259 $this->mShowToc =
true;
4263 if ( !$numMatches ) {
4269 $haveTocEntries =
false;
4271 # Ugh .. the TOC should have neat indentation levels which can be
4272 # passed to the skin functions. These are determined here
4275 $tocData =
new TOCData();
4276 $baseTitleText = $this->getTitle()->getPrefixedDBkey();
4277 $oldType = $this->mOutputType;
4278 $this->setOutputType( self::OT_WIKI );
4279 $frame = $this->getPreprocessor()->newFrame();
4280 $root = $this->preprocessToDom( $origText );
4281 $node = $root->getFirstChild();
4285 $maxTocLevel = $this->svcOptions->get( MainConfigNames::MaxTocLevel );
4286 $domDocument = DOMCompat::newDocument();
4287 foreach (
$matches[3] as $headline ) {
4289 $isTemplate =
false;
4291 $sectionIndex =
false;
4292 if ( preg_match( self::HEADLINE_MARKER_REGEX, $headline, $markerMatches ) ) {
4293 $serial = (int)$markerMatches[1];
4294 [ $titleText, $sectionIndex ] = $this->mHeadings[$serial];
4295 $isTemplate = ( $titleText != $baseTitleText );
4296 $headline = ltrim( substr( $headline, strlen( $markerMatches[0] ) ) );
4299 $sectionMetadata = SectionMetadata::fromLegacy( [
4300 "fromtitle" => $titleText ?: null,
4301 "index" => $sectionIndex === false
4302 ?
'' : ( ( $isTemplate ?
'T-' :
'' ) . $sectionIndex )
4304 $tocData->addSection( $sectionMetadata );
4307 $level = (int)
$matches[1][$headlineCount];
4308 $tocData->processHeading( $oldLevel, $level, $sectionMetadata );
4310 if ( $tocData->getCurrentTOCLevel() < $maxTocLevel ) {
4311 $haveTocEntries =
true;
4314 # Remove link placeholders by the link text.
4315 # <!--LINK number-->
4317 # link text with suffix
4318 # Do this before unstrip since link text can contain strip markers
4319 $fullyParsedHeadline = $this->replaceLinkHoldersText( $headline );
4321 # Avoid insertion of weird stuff like <math> by expanding the relevant sections
4322 $fullyParsedHeadline = $this->mStripState->unstripBoth( $fullyParsedHeadline );
4326 $fullyParsedHeadline = $this->tidy->tidy( $fullyParsedHeadline, Sanitizer::armorFrenchSpaces( ... ) );
4331 $wrappedHeadline =
"<h$level" .
$matches[
'attrib'][$headlineCount] . $fullyParsedHeadline .
"</h$level>";
4335 $headlineDom = DOMUtils::parseHTMLToFragment( $domDocument, $wrappedHeadline );
4339 $h = $headlineDom->firstChild;
4340 $headingId = ( $h instanceof Element && DOMUtils::isHeading( $h ) ) ?
4341 DOMCompat::getAttribute( $h,
'id' ) :
null;
4343 $this->cleanUpTocLine( $headlineDom );
4347 $tocline = trim( DOMUtils::getFragmentInnerHTML( $headlineDom ) );
4350 $headlineText = trim( $headlineDom->textContent );
4352 if ( $headingId ===
null || $headingId ===
'' ) {
4353 $headingId = Sanitizer::normalizeSectionNameWhitespace( $headlineText );
4354 $headingId = self::normalizeSectionName( $headingId );
4357 # Create the anchor for linking from the TOC to the section
4358 $fallbackAnchor = Sanitizer::escapeIdForAttribute( $headingId, Sanitizer::ID_FALLBACK );
4359 $linkAnchor = Sanitizer::escapeIdForLink( $headingId );
4360 $anchor = Sanitizer::escapeIdForAttribute( $headingId, Sanitizer::ID_PRIMARY );
4361 if ( $fallbackAnchor === $anchor ) {
4362 # No reason to have both (in fact, we can't)
4363 $fallbackAnchor =
false;
4366 # HTML IDs must be case-insensitively unique for IE compatibility (T12721).
4367 $arrayKey = strtolower( $anchor );
4368 if ( $fallbackAnchor ===
false ) {
4369 $fallbackArrayKey =
false;
4371 $fallbackArrayKey = strtolower( $fallbackAnchor );
4374 if ( isset( $refers[$arrayKey] ) ) {
4375 for ( $i = 2; isset( $refers[
"{$arrayKey}_$i"] ); ++$i );
4377 $linkAnchor .=
"_$i";
4378 $refers[
"{$arrayKey}_$i"] =
true;
4380 $refers[$arrayKey] =
true;
4382 if ( $fallbackAnchor !==
false && isset( $refers[$fallbackArrayKey] ) ) {
4383 for ( $i = 2; isset( $refers[
"{$fallbackArrayKey}_$i"] ); ++$i );
4384 $fallbackAnchor .=
"_$i";
4385 $refers[
"{$fallbackArrayKey}_$i"] =
true;
4387 $refers[$fallbackArrayKey] =
true;
4390 # Add the section to the section tree
4391 # Find the DOM node for this header
4392 $noOffset = ( $isTemplate || $sectionIndex === false );
4393 while ( $node && !$noOffset ) {
4394 if ( $node->getName() ===
'h' ) {
4395 $bits = $node->splitHeading();
4396 if ( $bits[
'i'] == $sectionIndex ) {
4400 $cpOffset += mb_strlen(
4401 $this->mStripState->unstripBoth(
4402 $frame->expand( $node, PPFrame::RECOVER_ORIG )
4405 $node = $node->getNextSibling();
4407 $sectionMetadata->line = $tocline;
4408 $sectionMetadata->codepointOffset = ( $noOffset ? null : $cpOffset );
4409 $sectionMetadata->anchor = $anchor;
4410 $sectionMetadata->linkAnchor = $linkAnchor;
4412 if ( $maybeShowEditLink && $sectionIndex !==
false ) {
4414 if ( $isTemplate ) {
4415 # Put a T flag in the section identifier, to indicate to extractSections()
4416 # that sections inside <includeonly> should be counted.
4417 $editsectionPage = $titleText;
4418 $editsectionSection =
"T-$sectionIndex";
4420 $editsectionPage = $this->getTitle()->getPrefixedText();
4421 $editsectionSection = $sectionIndex;
4432 $editlink =
'<mw:editsection page="' . htmlspecialchars( $editsectionPage, ENT_COMPAT );
4433 $editlink .=
'" section="' . htmlspecialchars( $editsectionSection, ENT_COMPAT ) .
'"';
4434 $editlink .=
'>' . htmlspecialchars( $headlineText ) .
'</mw:editsection>';
4445 $head[$headlineCount] =
"<h$level" . Html::expandAttributes( [
4446 'data-mw-anchor' => $anchor,
4447 'data-mw-fallback-anchor' => $fallbackAnchor,
4448 ] ) .
$matches[
'attrib'][$headlineCount] . $headline . $editlink .
"</h$level>";
4453 $this->setOutputType( $oldType );
4455 # Never ever show TOC if no headers (or suppressed)
4456 $suppressToc = $this->mOptions->getSuppressTOC();
4457 if ( !$haveTocEntries ) {
4460 $addTOCPlaceholder =
false;
4462 if ( $isMain && !$suppressToc ) {
4470 $this->mOutput->setTOCData( $tocData );
4476 $this->mOutput->setOutputFlag( ParserOutputFlags::SHOW_TOC );
4477 if ( !$this->mForceTocPosition ) {
4478 $addTOCPlaceholder =
true;
4487 if ( !$this->mShowToc ) {
4488 $this->mOutput->setOutputFlag( ParserOutputFlags::NO_TOC );
4492 # split up and insert constructed headlines
4493 $blocks = preg_split(
'/<h[1-6]\b[^>]*>.*?<\/h[1-6]>/is', $text );
4498 foreach ( $blocks as $block ) {
4500 if ( empty( $head[$i - 1] ) ) {
4501 $sections[$i] = $block;
4503 $sections[$i] = $head[$i - 1] . $block;
4509 if ( $addTOCPlaceholder ) {
4513 $sections[0] .= self::TOC_PLACEHOLDER .
"\n";
4516 return implode(
'', $sections );
4531 if ( $tocData ===
null ) {
4534 foreach ( $tocData->getSections() as $s ) {
4545 $pieces = explode( $dot, $s->number );
4547 foreach ( $pieces as $i => $p ) {
4553 $s->number = $numbering;
4576 if ( $clearState ) {
4577 $magicScopeVariable = $this->lock();
4579 $this->startParse( $page, $options, self::OT_WIKI, $clearState );
4580 $this->setUser( $user );
4583 $text = str_replace(
"\000",
'', $text );
4588 $text = TextContent::normalizeLineEndings( $text );
4591 $text = $this->pstPass2( $text, $user );
4593 $text = $this->mStripState->unstripBoth( $text );
4596 $text = rtrim( $text );
4598 $this->hookRunner->onParserPreSaveTransformComplete( $this, $text );
4600 $this->setUser(
null ); # Reset
4613 private function pstPass2( $text,
UserIdentity $user ) {
4614 # Note: This is the timestamp saved as hardcoded wikitext to the database, we use
4615 # $this->contLang here in order to give everyone the same signature and use the default one
4616 # rather than the one selected in each user's preferences. (see also T14815)
4617 $ts = $this->mOptions->getTimestamp();
4618 $timestamp = MWTimestamp::getLocalInstance( $ts );
4619 $ts = $timestamp->format(
'YmdHis' );
4620 $tzMsg = $timestamp->getTimezoneMessage()->inContentLanguage()->text();
4622 $d = $this->contLang->timeanddate( $ts,
false,
false ) .
" ($tzMsg)";
4624 # Variable replacement
4625 # Because mOutputType is OT_WIKI, this will only process {{subst:xxx}} type tags
4626 $text = $this->replaceVariables( $text );
4628 # This works almost by chance, as the replaceVariables are done before the getUserSig(),
4629 # which may corrupt this parser instance via its wfMessage()->text() call-
4632 if ( str_contains( $text,
'~~~' ) ) {
4633 $sigText = $this->getUserSig( $user );
4634 $text = strtr( $text, [
4636 '~~~~' =>
"$sigText $d",
4639 # The main two signature forms used above are time-sensitive
4640 $this->setOutputFlag( ParserOutputFlags::USER_SIGNATURE,
'User signature detected' );
4643 # Context links ("pipe tricks"): [[|name]] and [[name (context)|]]
4644 $tc =
'[' . Title::legalChars() .
']';
4645 $nc =
'[ _0-9A-Za-z\x80-\xff-]'; # Namespaces can use non-ascii!
4648 $p1 =
"/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\))\\|]]/";
4650 $p4 =
"/\[\[(:?$nc+:|:|)($tc+?)( ?($tc+))\\|]]/";
4652 $p3 =
"/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\)|)((?:, |,|، )$tc+|)\\|]]/";
4654 $p2 =
"/\[\[\\|($tc+)]]/";
4656 # try $p1 first, to turn "[[A, B (C)|]]" into "[[A, B (C)|A, B]]"
4657 $text = preg_replace( $p1,
'[[\\1\\2\\3|\\2]]', $text );
4658 $text = preg_replace( $p4,
'[[\\1\\2\\3|\\2]]', $text );
4659 $text = preg_replace( $p3,
'[[\\1\\2\\3\\4|\\2]]', $text );
4661 $t = $this->getTitle()->getText();
4663 if ( preg_match(
"/^($nc+:|)$tc+?( \\($tc+\\))$/", $t, $m ) ) {
4664 $text = preg_replace( $p2,
"[[$m[1]\\1$m[2]|\\1]]", $text );
4665 } elseif ( preg_match(
"/^($nc+:|)$tc+?(, $tc+|)$/", $t, $m ) &&
"$m[1]$m[2]" !=
'' ) {
4666 $text = preg_replace( $p2,
"[[$m[1]\\1$m[2]|\\1]]", $text );
4668 # if there's no context, don't bother duplicating the title
4669 $text = preg_replace( $p2,
'[[\\1]]', $text );
4693 # If not given, retrieve from the user object.
4694 if ( $nickname ===
false ) {
4695 $nickname = $this->userOptionsLookup->getOption( $user,
'nickname' );
4698 $fancySig ??= $this->userOptionsLookup->getBoolOption( $user,
'fancysig' );
4700 if ( $nickname ===
null || $nickname ===
'' ) {
4702 $nickname = $username;
4703 } elseif ( mb_strlen( $nickname ) > $this->svcOptions->get( MainConfigNames::MaxSigChars ) ) {
4704 $nickname = $username;
4705 $this->logger->debug( __METHOD__ .
": $username has overlong signature." );
4706 } elseif ( $fancySig !==
false ) {
4707 # Sig. might contain markup; validate this
4708 $isValid = $this->validateSig( $nickname ) !==
false;
4711 $sigValidation = $this->svcOptions->get( MainConfigNames::SignatureValidation );
4712 if ( $isValid && $sigValidation ===
'disallow' ) {
4714 $this->mOptions->getUserIdentity(),
4717 $validator = $this->signatureValidatorFactory
4718 ->newSignatureValidator( $user,
null, $parserOpts );
4719 $isValid = !$validator->validateSignature( $nickname );
4723 # Validated; clean up (if needed) and return it
4724 return $this->cleanSig( $nickname,
true );
4726 # Failed to validate; fall back to the default
4727 $nickname = $username;
4728 $this->logger->debug( __METHOD__ .
": $username has invalid signature." );
4732 # Make sure nickname doesnt get a sig in a sig
4733 $nickname = self::cleanSigInSig( $nickname );
4735 # If we're still here, make it a link to the user page
4738 if ( $this->userNameUtils->isTemp( $username ) ) {
4739 $msgName =
'signature-temp';
4741 $msgName =
'signature';
4743 $msgName =
'signature-anon';
4746 return wfMessage( $msgName, $userText, $nickText )->inContentLanguage()
4747 ->page( $this->getPage() )->text();
4758 return Xml::isWellFormedXmlFragment( $text ) ? $text :
false;
4774 $magicScopeVariable = $this->lock();
4777 ParserOptions::newFromUser( RequestContext::getMain()->getUser() ),
4778 self::OT_PREPROCESS,
4783 # Option to disable this feature
4784 if ( !$this->mOptions->getCleanSignatures() ) {
4788 # @todo FIXME: Regex doesn't respect extension tags or nowiki
4789 # => Move this logic to braceSubstitution()
4790 $substWord = $this->magicWordFactory->get(
'subst' );
4791 $substRegex =
'/\{\{(?!(?:' . $substWord->getBaseRegex() .
'))/x' . $substWord->getRegexCase();
4792 $substText =
'{{' . $substWord->getSynonym( 0 );
4794 $text = preg_replace( $substRegex, $substText, $text );
4795 $text = self::cleanSigInSig( $text );
4796 $dom = $this->preprocessToDom( $text );
4797 $frame = $this->getPreprocessor()->newFrame();
4798 $text = $frame->expand( $dom );
4801 $text = $this->mStripState->unstripBoth( $text );
4815 $text = preg_replace(
'/~{3,5}/',
'', $text );
4839 if ( !str_contains( $text,
'mw:PageProp/toc' ) ) {
4844 return HtmlHelper::modifyElements(
4846 static function ( SerializerNode $node ):
bool {
4847 $prop = $node->attrs[
'property'] ??
'';
4848 return $node->name ===
'meta' && $prop ===
'mw:PageProp/toc';
4850 static function ( SerializerNode $node ) use ( &$replaced, $toc ) {
4876 $outputType, $clearState =
true, $revId =
null
4878 $this->startParse( $page, $options, $outputType, $clearState );
4879 if ( $revId !==
null ) {
4880 $this->mRevisionId = $revId;
4891 $outputType, $clearState =
true
4893 $this->setPage( $page );
4894 $this->mOptions = $options;
4895 $this->setOutputType( $outputType );
4896 if ( $clearState ) {
4897 $this->clearState();
4911 static $executing =
false;
4913 # Guard against infinite recursion
4919 $text = $this->preprocess( $text, $page ?? $this->mTitle, $options );
4944 public function setHook( $tag, callable $callback ) {
4945 $tag = strtolower( $tag );
4946 if ( preg_match(
'/[<>\r\n]/', $tag, $m ) ) {
4947 throw new InvalidArgumentException(
"Invalid character {$m[0]} in setHook('$tag', ...) call" );
4949 $oldVal = $this->mTagHooks[$tag] ??
null;
4950 $this->mTagHooks[$tag] = $callback;
4951 if ( !in_array( $tag, $this->mStripList ) ) {
4952 $this->mStripList[] = $tag;
4963 $this->mTagHooks = [];
4964 $this->mStripList = [];
5016 $oldVal = $this->mFunctionHooks[$id][0] ??
null;
5017 $this->mFunctionHooks[$id] = [ $callback, $flags ];
5019 # Add to function cache
5020 $mw = $this->magicWordFactory->get( $id );
5022 $synonyms = $mw->getSynonyms();
5023 $sensitive = intval( $mw->isCaseSensitive() );
5025 foreach ( $synonyms as $syn ) {
5027 if ( !$sensitive ) {
5028 $syn = $this->contLang->lc( $syn );
5031 if ( !( $flags & self::SFH_NO_HASH ) ) {
5034 # Remove trailing colon (or Japanese double-width colon)
5035 if ( str_ends_with( $syn,
':' ) || str_ends_with( $syn,
':' ) ) {
5036 $syn = mb_substr( $syn, 0, -1 );
5038 $this->mFunctionSynonyms[$sensitive][$syn] = $id;
5050 return array_keys( $this->mFunctionHooks );
5062 $this->replaceLinkHoldersPrivate( $text );
5071 private function replaceLinkHoldersPrivate( &$text ) {
5072 $this->mLinkHolders->replace( $text );
5082 private function replaceLinkHoldersText( $text ) {
5083 return $this->mLinkHolders->replaceText( $text );
5101 $mode = $params[
'mode'] ??
false;
5104 $ig = ImageGalleryBase::factory( $mode );
5107 $ig = ImageGalleryBase::factory();
5110 $ig->setContextTitle( $this->getTitle() );
5111 $ig->setShowBytes(
false );
5112 $ig->setShowDimensions(
false );
5113 $ig->setParser( $this );
5114 $ig->setHideBadImages();
5115 $ig->setAttributes( Sanitizer::validateTagAttributes( $params,
'ul' ) );
5117 $ig->setShowFilename( isset( $params[
'showfilename'] ) );
5118 if ( isset( $params[
'caption'] ) ) {
5122 $caption = $this->recursiveTagParse( $params[
'caption'] );
5123 $ig->setCaptionHtml( $caption );
5125 if ( isset( $params[
'perrow'] ) ) {
5126 $ig->setPerRow( $params[
'perrow'] );
5128 if ( isset( $params[
'widths'] ) ) {
5129 $ig->setWidths( $params[
'widths'] );
5131 if ( isset( $params[
'heights'] ) ) {
5132 $ig->setHeights( $params[
'heights'] );
5134 $ig->setAdditionalOptions( $params );
5136 $lines = StringUtils::explode(
"\n", $text );
5137 foreach ( $lines as $line ) {
5138 # match lines like these:
5139 # Image:someimage.jpg|This is some image
5141 preg_match(
"/^([^|]+)(\\|(.*))?$/", $line,
$matches );
5147 if ( str_contains(
$matches[0],
'%' ) ) {
5151 if ( $title ===
null ) {
5152 # Bogus title. Ignore these so we don't bomb out later.
5156 # We need to get what handler the file uses, to figure out parameters.
5157 # Note, a hook can override the file name, and chose an entirely different
5158 # file (which potentially could be of a different type and have different handler).
5161 $this->hookRunner->onBeforeParserFetchFileAndTitle(
5163 $this, $title, $options, $descQuery
5165 # Don't register it now, as TraditionalImageGallery does that later.
5166 $file = $this->fetchFileNoRegister( $title, $options );
5167 $handler = $file ? $file->getHandler() :
false;
5170 'img_alt' =>
'gallery-internal-alt',
5171 'img_link' =>
'gallery-internal-link',
5174 $paramMap += $handler->getParamMap();
5177 unset( $paramMap[
'img_width'] );
5180 $mwArray = $this->magicWordFactory->newArray( array_keys( $paramMap ) );
5184 $handlerOptions = [];
5197 $parameterMatches = StringUtils::delimiterExplode(
5204 foreach ( $parameterMatches as $parameterMatch ) {
5205 [ $magicName, $match ] = $mwArray->matchVariableStartToEnd( trim( $parameterMatch ) );
5206 if ( !$magicName ) {
5208 $label = $parameterMatch;
5212 $paramName = $paramMap[$magicName];
5213 switch ( $paramName ) {
5214 case 'gallery-internal-alt':
5216 $alt = $this->stripAltText( $match );
5218 case 'gallery-internal-link':
5219 $linkValue = $this->stripAltText( $match );
5220 if ( preg_match(
'/^-{R\|(.*)}-$/', $linkValue ) ) {
5223 $linkValue = substr( $linkValue, 4, -2 );
5225 [ $type, $target ] = $this->parseLinkParameter( $linkValue );
5227 if ( $type ===
'no-link' ) {
5230 $imageOptions[$type] = $target;
5235 if ( $handler->validateParam( $paramName, $match ) ) {
5236 $handlerOptions[$paramName] = $match;
5239 $this->logger->debug(
5240 "$parameterMatch failed parameter validation" );
5241 $label = $parameterMatch;
5248 if ( !$hasAlt && $label !==
'' ) {
5249 $alt = $this->stripAltText( $label );
5251 $imageOptions[
'title'] = $this->stripAltText( $label );
5254 $handlerOptions[
'targetlang'] = $this->getTargetLanguage()->getCode();
5257 $title, $label, $alt,
'', $handlerOptions,
5258 ImageGalleryBase::LOADING_DEFAULT, $imageOptions
5261 $html = $ig->toHTML();
5262 $this->hookRunner->onAfterParserFetchFileAndTitle( $this, $ig, $html );
5270 private function getImageParams( $handler ) {
5271 $handlerClass = $handler ? get_class( $handler ) :
'';
5272 if ( !isset( $this->mImageParams[$handlerClass] ) ) {
5273 # Initialise static lists
5274 static $internalParamNames = [
5275 'horizAlign' => [
'left',
'right',
'center',
'none' ],
5276 'vertAlign' => [
'baseline',
'sub',
'super',
'top',
'text-top',
'middle',
5277 'bottom',
'text-bottom' ],
5278 'frame' => [
'thumbnail',
'framed',
'frameless',
'border',
5281 'manualthumb',
'upright',
'link',
'alt',
'class' ],
5283 static $internalParamMap;
5284 if ( !$internalParamMap ) {
5285 $internalParamMap = [];
5286 foreach ( $internalParamNames as $type => $names ) {
5287 foreach ( $names as $name ) {
5293 $magicName = str_replace(
'-',
'_',
"img_$name" );
5294 $internalParamMap[$magicName] = [ $type, $name ];
5299 # Add handler params
5300 # Since img_width is one of these, it is important it is listed
5301 # *after* the literal parameter names above (T372935).
5302 $paramMap = $internalParamMap;
5304 $handlerParamMap = $handler->getParamMap();
5305 foreach ( $handlerParamMap as $magic => $paramName ) {
5306 $paramMap[$magic] = [
'handler', $paramName ];
5310 $paramMap[
'img_width' ] = [
'handler',
'width' ];
5312 $this->mImageParams[$handlerClass] = $paramMap;
5313 $this->mImageParamsMagicArray[$handlerClass] =
5314 $this->magicWordFactory->newArray( array_keys( $paramMap ) );
5316 return [ $this->mImageParams[$handlerClass], $this->mImageParamsMagicArray[$handlerClass] ];
5328 return $this->makeImageInternal(
5329 $link, $options, shouldReplaceLinkHolders: true
5347 public function makeImage( LinkTarget $link, $options, $holders =
false ) {
5349 return $this->makeImageInternal(
5350 $link, $options, $holders ?:
null, shouldReplaceLinkHolders: false
5363 private function makeImageInternal(
5367 bool $shouldReplaceLinkHolders =
false
5369 # Check
if the options text is of the form
"options|alt text"
5371 # * thumbnail make a thumbnail with enlarge-icon and caption, alignment depends on lang
5372 # * left no resizing, just left align. label is used for alt= only
5373 # * right same, but right aligned
5374 # * none same, but not aligned
5375 # * ___px scale to ___ pixels width, no aligning. e.g. use in taxobox
5376 # * center center the image
5377 # * framed Keep original image size, no magnify-button.
5378 # * frameless like
'thumb' but without a frame. Keeps user preferences for width
5379 # * upright reduce width for upright images, rounded to full __0 px
5380 # * border draw a 1px border around the image
5381 # * alt Text for HTML alt attribute (defaults to empty)
5382 # * class Set a class for img node
5383 # * link Set the target of the image link. Can be external, interwiki, or local
5384 # vertical-align values (no % or length right now):
5394 #
Protect LanguageConverter markup when splitting into parts
5396 '-{',
'}-',
'|', $options, true
5399 # Give extensions a chance to select the file revision for us
5402 $title = Title::castFromLinkTarget( $link );
5403 $this->hookRunner->onBeforeParserFetchFileAndTitle(
5405 $this, $title, $options, $descQuery
5407 # Fetch and register the file (file title may be different via hooks)
5408 [ $file, $link ] = $this->fetchFileAndTitle( $link, $options );
5411 $handler = $file ? $file->getHandler() :
false;
5413 [ $paramMap, $mwArray ] = $this->getImageParams( $handler );
5416 $this->addTrackingCategory(
'broken-file-category' );
5419 # Process the input parameters
5421 $params = [
'frame' => [],
'handler' => [],
5422 'horizAlign' => [],
'vertAlign' => [] ];
5423 $seenformat =
false;
5424 foreach ( $parts as $part ) {
5425 [ $magicName, $value ] = $mwArray->matchVariableStartToEnd( trim( $part ) );
5427 if ( isset( $paramMap[$magicName] ) ) {
5428 [ $type, $paramName ] = $paramMap[$magicName];
5430 # Special case; width and height come in one variable together
5431 if ( $type ===
'handler' && $paramName ===
'width' ) {
5433 $parsedWidthParam = $this->parseWidthParam( $value,
true,
true );
5436 $validateFunc =
static function ( $name, $value ) use ( $handler ) {
5438 ? $handler->validateParam( $name, $value )
5441 if ( isset( $parsedWidthParam[
'width'] ) ) {
5442 $width = $parsedWidthParam[
'width'];
5443 if ( $validateFunc(
'width', $width ) ) {
5444 $params[$type][
'width'] = $width;
5448 if ( isset( $parsedWidthParam[
'height'] ) ) {
5449 $height = $parsedWidthParam[
'height'];
5450 if ( $validateFunc(
'height', $height ) ) {
5451 $params[$type][
'height'] = $height;
5455 # else no validation -- T15436
5457 if ( $type ===
'handler' ) {
5458 # Validate handler parameter
5459 $validated = $handler->validateParam( $paramName, $value );
5461 # Validate internal parameters
5462 switch ( $paramName ) {
5466 $value = $this->stripAltText( $value, $holders );
5469 [ $paramName, $value ] =
5470 $this->parseLinkParameter(
5471 $this->stripAltText( $value, $holders )
5475 if ( $paramName ===
'no-link' ) {
5481 # @todo FIXME: Possibly check validity here for
5482 # manualthumb? downstream behavior seems odd with
5483 # missing manual thumbs.
5484 $value = $this->stripAltText( $value, $holders );
5490 $validated = !$seenformat;
5494 # Most other things appear to be empty or numeric...
5495 $validated = ( $value ===
false || is_numeric( trim( $value ) ) );
5500 $params[$type][$paramName] = $value;
5504 if ( !$validated ) {
5509 # Process alignment parameters
5510 if ( $params[
'horizAlign'] !== [] ) {
5511 $params[
'frame'][
'align'] = array_key_first( $params[
'horizAlign'] );
5513 if ( $params[
'vertAlign'] !== [] ) {
5514 $params[
'frame'][
'valign'] = array_key_first( $params[
'vertAlign'] );
5517 $params[
'frame'][
'caption'] = $caption;
5519 # Will the image be presented in a frame, with the caption below?
5521 $hasVisibleCaption = isset( $params[
'frame'][
'framed'] )
5522 || isset( $params[
'frame'][
'thumbnail'] )
5523 || isset( $params[
'frame'][
'manualthumb'] );
5525 # In the old days, [[Image:Foo|text...]] would set alt text. Later it
5526 # came to also set the caption, ordinary text after the image -- which
5527 # makes no sense, because that just repeats the text multiple times in
5528 # screen readers. It *also* came to set the title attribute.
5529 # Now that we have an alt attribute, we should not set the alt text to
5530 # equal the caption: that's worse than useless, it just repeats the
5531 # text. This is the framed/thumbnail case. If there's no caption, we
5532 # use the unnamed parameter for alt text as well, just for the time be-
5533 # ing, if the unnamed param is set and the alt param is not.
5534 # For the future, we need to figure out if we want to tweak this more,
5535 # e.g., introducing a title= parameter for the title; ignoring the un-
5536 # named parameter entirely for images without a caption; adding an ex-
5537 # plicit caption= parameter and preserving the old magic unnamed para-
5540 if ( !$hasVisibleCaption ) {
5542 if ( !isset( $params[
'frame'][
'alt'] ) && $caption !==
'' ) {
5543 # No alt text, use the "caption" for the alt text
5544 $params[
'frame'][
'alt'] = $this->stripAltText( $caption, $holders );
5546 # Use the "caption" for the tooltip text
5547 $params[
'frame'][
'title'] = $this->stripAltText( $caption, $holders );
5549 $params[
'handler'][
'targetlang'] = $this->getTargetLanguage()->getCode();
5552 $title = Title::castFromLinkTarget( $link );
5553 $this->hookRunner->onParserMakeImageParams( $title, $file, $params, $this );
5555 # Linker does the rest
5556 $time = $options[
'time'] ??
false;
5557 $ret = Linker::makeImageLink( $this, $link, $file, $params[
'frame'], $params[
'handler'],
5558 $time, $descQuery, $this->mOptions->getThumbSize() );
5560 # Give the handler a chance to modify the parser object
5562 $handler->parserTransformHook( $this, $file );
5565 $this->modifyImageHtml( $file, $params, $ret );
5567 if ( $shouldReplaceLinkHolders ) {
5568 $this->replaceLinkHoldersPrivate( $ret );
5592 private function parseLinkParameter( $value ) {
5593 $chars = self::EXT_LINK_URL_CLASS;
5594 $addr = self::EXT_LINK_ADDR;
5595 $prots = $this->urlUtils->validProtocols();
5598 if ( $value ===
'' ) {
5600 } elseif ( preg_match(
"/^((?i)$prots)/", $value ) ) {
5601 if ( preg_match(
"/^((?i)$prots)$addr$chars*$/u", $value ) ) {
5602 $this->mOutput->addExternalLink( $value );
5632 if ( str_contains( $value,
'%' ) ) {
5633 $value = rawurldecode( $value );
5635 $linkTitle = Title::newFromText( $value );
5637 $this->mOutput->addLink( $linkTitle );
5638 $type =
'link-title';
5639 $target = $linkTitle;
5642 return [ $type, $target ];
5653 $this->hookRunner->onParserModifyImageHTML( $this, $file, $params, $html );
5656 private function stripAltText(
string $caption, ?
LinkHolderArray $holders =
null ): string {
5657 # Strip bad stuff out of the title (tooltip). We can
't just use
5658 # replaceLinkHoldersText() here, because if this function is called
5659 # from handleInternalLinks2(), mLinkHolders won't be up-to-date.
5660 if ( $holders !== null ) {
5661 $tooltip = $holders->replaceText( $caption );
5663 $tooltip = $this->replaceLinkHoldersText( $caption );
5666 # make sure there are no placeholders in thumbnail attributes
5667 # that are later expanded to html- so expand them now and
5669 $tooltip = $this->mStripState->unstripBoth( $tooltip );
5670 # Compatibility hack! In HTML certain entity references not terminated
5671 # by a semicolon are decoded (but not if we're in an attribute; that's
5672 # how link URLs get away without properly escaping & in queries).
5673 # But wikitext has always required semicolon-termination of entities,
5674 # so encode & where needed to avoid decode of semicolon-less entities.
5677 # T210437 discusses moving this workaround to Sanitizer::stripAllTags.
5678 $tooltip = preg_replace(
"/
5679 & # 1. entity prefix
5680 (?= # 2. followed by:
5681 (?: # a. one of the legacy semicolon-less named entities
5682 A(?:Elig|MP|acute|circ|grave|ring|tilde|uml)|
5683 C(?:OPY|cedil)|E(?:TH|acute|circ|grave|uml)|
5684 GT|I(?:acute|circ|grave|uml)|LT|Ntilde|
5685 O(?:acute|circ|grave|slash|tilde|uml)|QUOT|REG|THORN|
5686 U(?:acute|circ|grave|uml)|Yacute|
5687 a(?:acute|c(?:irc|ute)|elig|grave|mp|ring|tilde|uml)|brvbar|
5688 c(?:cedil|edil|urren)|cent(?!erdot;)|copy(?!sr;)|deg|
5689 divide(?!ontimes;)|e(?:acute|circ|grave|th|uml)|
5690 frac(?:1(?:2|4)|34)|
5691 gt(?!c(?:c|ir)|dot|lPar|quest|r(?:a(?:pprox|rr)|dot|eq(?:less|qless)|less|sim);)|
5692 i(?:acute|circ|excl|grave|quest|uml)|laquo|
5693 lt(?!c(?:c|ir)|dot|hree|imes|larr|quest|r(?:Par|i(?:e|f|));)|
5694 m(?:acr|i(?:cro|ddot))|n(?:bsp|tilde)|
5695 not(?!in(?:E|dot|v(?:a|b|c)|)|ni(?:v(?:a|b|c)|);)|
5696 o(?:acute|circ|grave|rd(?:f|m)|slash|tilde|uml)|
5697 p(?:lusmn|ound)|para(?!llel;)|quot|r(?:aquo|eg)|
5698 s(?:ect|hy|up(?:1|2|3)|zlig)|thorn|times(?!b(?:ar|)|d;)|
5699 u(?:acute|circ|grave|ml|uml)|y(?:acute|en|uml)
5701 (?:[^;]|$)) # b. and not followed by a semicolon
5702 # S = study, for efficiency
5703 /Sx",
'&', $tooltip );
5704 $tooltip = Sanitizer::stripAllTags( $tooltip );
5720 $text = $this->replaceVariables( $text, $frame );
5721 $text = $this->mStripState->unstripBoth( $text );
5732 return array_keys( $this->mTagHooks );
5740 return $this->mFunctionSynonyms;
5748 return $this->urlUtils->validProtocols();
5781 private function extractSections( $text, $sectionId, $mode, $newText, ?
PageReference $page =
null ) {
5782 $magicScopeVariable = $this->lock();
5785 ParserOptions::newFromUser( RequestContext::getMain()->getUser() ),
5790 $frame = $this->getPreprocessor()->newFrame();
5792 # Process section extraction flags
5794 $sectionParts = explode(
'-', $sectionId );
5798 $sectionIndex = (int)array_pop( $sectionParts );
5799 foreach ( $sectionParts as $part ) {
5800 if ( $part ===
'T' ) {
5801 $flags |= Preprocessor::DOM_FOR_INCLUSION;
5805 # Check for empty input
5806 if ( strval( $text ) ===
'' ) {
5807 # Only sections 0 and T-0 exist in an empty document
5808 if ( $sectionIndex === 0 ) {
5809 return $mode ===
'get' ?
'' : $newText;
5811 return $mode ===
'get' ? $newText : $text;
5815 # Preprocess the text
5816 $root = $this->preprocessToDom( $text, $flags );
5818 # <h> nodes indicate section breaks
5819 # They can only occur at the top level, so we can find them by iterating the root's children
5820 $node = $root->getFirstChild();
5822 # Find the target section
5823 if ( $sectionIndex === 0 ) {
5824 # Section zero doesn't nest, level=big
5825 $targetLevel = 1000;
5828 if ( $node->getName() ===
'h' ) {
5829 $bits = $node->splitHeading();
5830 if ( $bits[
'i'] == $sectionIndex ) {
5831 $targetLevel = $bits[
'level'];
5835 if ( $mode ===
'replace' ) {
5836 $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5838 $node = $node->getNextSibling();
5844 return $mode ===
'get' ? $newText : $text;
5847 # Find the end of the section, including nested sections
5849 if ( $node->getName() ===
'h' ) {
5850 $bits = $node->splitHeading();
5851 $curLevel = $bits[
'level'];
5853 if ( $bits[
'i'] != $sectionIndex && $curLevel <= $targetLevel ) {
5857 if ( $mode ===
'get' ) {
5858 $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5860 $node = $node->getNextSibling();
5863 # Write out the remainder (in replace mode only)
5864 if ( $mode ===
'replace' ) {
5865 # Output the replacement text
5866 # Add two newlines on -- trailing whitespace in $newText is conventionally
5867 # stripped by the editor, so we need both newlines to restore the paragraph gap
5868 # Only add trailing whitespace if there is newText
5869 if ( $newText !=
"" ) {
5870 $outText .= $newText .
"\n\n";
5874 $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5875 $node = $node->getNextSibling();
5879 # Re-insert stripped tags
5880 $outText = rtrim( $this->mStripState->unstripBoth( $outText ) );
5900 public function getSection( $text, $sectionId, $defaultText =
'' ) {
5901 return $this->extractSections( $text, $sectionId,
'get', $defaultText );
5918 return $this->extractSections( $oldText, $sectionId,
'replace', $newText );
5951 $magicScopeVariable = $this->lock();
5954 ParserOptions::newFromUser( RequestContext::getMain()->getUser() ),
5958 $frame = $this->getPreprocessor()->newFrame();
5959 $root = $this->preprocessToDom( $text, 0 );
5960 $node = $root->getFirstChild();
5972 $nodeText = $frame->expand( $node, PPFrame::RECOVER_ORIG );
5973 if ( $node->getName() ===
'h' ) {
5974 $bits = $node->splitHeading();
5975 $sections[] = $currentSection;
5977 'index' => $bits[
'i'],
5978 'level' => $bits[
'level'],
5979 'offset' => $offset,
5980 'heading' => $nodeText,
5984 $currentSection[
'text'] .= $nodeText;
5986 $offset += strlen( $nodeText );
5987 $node = $node->getNextSibling();
5989 $sections[] = $currentSection;
6005 return $this->mRevisionId;
6015 if ( $this->mRevisionRecordObject ) {
6016 return $this->mRevisionRecordObject;
6018 if ( $this->mOptions->isMessage() ) {
6029 $rev = $this->mOptions->getCurrentRevisionRecordCallback()(
6042 if ( $this->mRevisionId ===
null && $rev->getId() ) {
6054 if ( $this->mRevisionId && $rev->getId() != $this->mRevisionId ) {
6055 $rev = MediaWikiServices::getInstance()
6056 ->getRevisionLookup()
6057 ->getRevisionById( $this->mRevisionId );
6060 $this->mRevisionRecordObject = $rev;
6062 return $this->mRevisionRecordObject;
6072 if ( $this->mRevisionTimestamp !==
null ) {
6073 return $this->mRevisionTimestamp;
6076 # Use specified revision timestamp, falling back to the current timestamp
6077 $revObject = $this->getRevisionRecordObject();
6078 $timestamp = $revObject && $revObject->getTimestamp()
6079 ? $revObject->getTimestamp()
6080 : $this->mOptions->getTimestamp();
6081 $this->mOutput->setRevisionTimestampUsed( $timestamp );
6083 # The cryptic '' timezone parameter tells to use the site-default
6084 # timezone offset instead of the user settings.
6085 # Since this value will be saved into the parser cache, served
6086 # to other users, and potentially even used inside links and such,
6087 # it needs to be consistent for all visitors.
6088 $this->mRevisionTimestamp = $this->contLang->userAdjust( $timestamp,
'' );
6090 return $this->mRevisionTimestamp;
6100 if ( $this->mRevisionUser === null ) {
6101 $revObject = $this->getRevisionRecordObject();
6103 # if this template is subst: the revision id will be blank,
6104 # so just use the current user's name
6105 if ( $revObject && $revObject->getUser() ) {
6106 $this->mRevisionUser = $revObject->getUser()->getName();
6107 } elseif ( $this->ot[
'wiki'] || $this->mOptions->getIsPreview() ) {
6108 $this->mRevisionUser = $this->getUserIdentity()->getName();
6110 # Note that we fall through here with
6111 # $this->mRevisionUser still null
6114 return $this->mRevisionUser;
6124 if ( $this->mRevisionSize ===
null ) {
6125 $revObject = $this->getRevisionRecordObject();
6127 # if this variable is subst: the revision id will be blank,
6128 # so just use the parser input size, because the own substitution
6129 # will change the size.
6130 $this->mRevisionSize = $revObject ? $revObject->getSize() : $this->mInputSize;
6132 return $this->mRevisionSize;
6135 private static function getSectionNameFromStrippedText(
string $text ): string {
6136 $text =
Sanitizer::normalizeSectionNameWhitespace( $text );
6137 $text = Sanitizer::decodeCharReferences( $text );
6138 $text = self::normalizeSectionName( $text );
6142 private static function makeAnchor(
string $sectionName ): string {
6143 return '#' . Sanitizer::escapeIdForLink( $sectionName );
6146 private function makeLegacyAnchor(
string $sectionName ): string {
6147 $fragmentMode = $this->svcOptions->get( MainConfigNames::FragmentMode );
6148 if ( isset( $fragmentMode[1] ) && $fragmentMode[1] ===
'legacy' ) {
6150 $id = Sanitizer::escapeIdForAttribute( $sectionName, Sanitizer::ID_FALLBACK );
6152 $id = Sanitizer::escapeIdForLink( $sectionName );
6168 # Strip out wikitext links(they break the anchor)
6169 $text = $this->stripSectionName( $text );
6170 $sectionName = self::getSectionNameFromStrippedText( $text );
6171 return self::makeAnchor( $sectionName );
6187 # Strip out wikitext links(they break the anchor)
6188 $text = $this->stripSectionName( $text );
6189 $sectionName = self::getSectionNameFromStrippedText( $text );
6190 return $this->makeLegacyAnchor( $sectionName );
6200 $sectionName = self::getSectionNameFromStrippedText( $text );
6201 return self::makeAnchor( $sectionName );
6210 private static function normalizeSectionName( $text ) {
6211 # T90902: ensure the same normalization is applied for IDs as to links
6212 $titleParser = MediaWikiServices::getInstance()->getTitleParser();
6214 $parts = $titleParser->splitTitleString(
"#$text" );
6215 }
catch ( MalformedTitleException ) {
6218 return $parts[
'fragment'];
6237 # Strip internal link markup
6238 $text = preg_replace(
'/\[\[:?([^[|]+)\|([^[]+)\]\]/',
'$2', $text );
6239 $text = preg_replace(
'/\[\[:?([^[]+)\|?\]\]/',
'$1', $text );
6241 # Strip external link markup
6242 # @todo FIXME: Not tolerant to blank link text
6244 # on how many empty links there are on the page - need to figure that out.
6245 $text = preg_replace(
6246 '/\[(?i:' . $this->urlUtils->validProtocols() .
')([^ ]+?) ([^[]+)\]/',
'$2', $text );
6248 # Parse wikitext quotes (italics & bold)
6249 $text = $this->doQuotes( $text );
6252 $text = StringUtils::delimiterReplace(
'<',
'>',
'', $text );
6277 while ( $i < strlen( $s ) ) {
6278 $markerStart = strpos( $s, self::MARKER_PREFIX, $i );
6279 if ( $markerStart ===
false ) {
6280 $out .= $callback( substr( $s, $i ) );
6283 $out .= $callback( substr( $s, $i, $markerStart - $i ) );
6284 $markerEnd = strpos( $s, self::MARKER_SUFFIX, $markerStart );
6285 if ( $markerEnd ===
false ) {
6286 $out .= substr( $s, $markerStart );
6289 $markerEnd += strlen( self::MARKER_SUFFIX );
6290 $out .= substr( $s, $markerStart, $markerEnd - $markerStart );
6306 return $this->mStripState->killMarkers( $text );
6323 $parsedWidthParam = [];
6324 if ( $value ===
'' ) {
6325 return $parsedWidthParam;
6328 if ( !$localized ) {
6330 $mwArray = $this->magicWordFactory->newArray( [
'img_width' ] );
6331 [ $magicWord, $newValue ] = $mwArray->matchVariableStartToEnd( $value );
6332 $value = $magicWord ? $newValue : $value;
6335 # (T15500) In both cases (width/height and width only),
6336 # permit trailing "px" for backward compatibility.
6337 if ( $parseHeight && preg_match(
'/^([0-9]*)x([0-9]*)\s*(px)?\s*$/', $value, $m ) ) {
6338 $parsedWidthParam[
'width'] = intval( $m[1] );
6339 $parsedWidthParam[
'height'] = intval( $m[2] );
6340 if ( $m[3] ??
false ) {
6341 $this->addTrackingCategory(
'double-px-category' );
6343 } elseif ( preg_match(
'/^([0-9]*)\s*(px)?\s*$/', $value, $m ) ) {
6344 $parsedWidthParam[
'width'] = intval( $m[1] );
6345 if ( $m[2] ??
false ) {
6346 $this->addTrackingCategory(
'double-px-category' );
6349 return $parsedWidthParam;
6359 protected function lock(): ScopedCallback {
6360 if ( $this->mInParse ) {
6361 throw new LogicException(
"Parser state cleared while parsing. "
6362 .
"Did you call Parser::parse recursively? Lock is held by: " . $this->mInParse );
6367 $e =
new RuntimeException;
6368 $this->mInParse = $e->getTraceAsString();
6370 $recursiveCheck =
new ScopedCallback(
function () {
6371 $this->mInParse =
false;
6374 return $recursiveCheck;
6385 return (
bool)$this->mInParse;
6400 if ( preg_match(
'/^<p>(.*)\n?<\/p>\n?$/sU', $html, $m ) && !str_contains( $m[1],
'</p>' ) ) {
6421 if ( $nsText !==
'' ) {
6422 $html .=
'<span class="mw-page-title-namespace">' . HtmlArmor::getHtml( $nsText ) .
'</span>';
6423 $html .=
'<span class="mw-page-title-separator">' . HtmlArmor::getHtml( $nsSeparator ) .
'</span>';
6425 $html .=
'<span class="mw-page-title-main">' . HtmlArmor::getHtml( $mainText ) .
'</span>';
6426 if ( $titleLang !==
null ) {
6427 $html = Html::rawElement(
'span', [
6428 'lang' => $titleLang->getHtmlCode(),
6429 'dir' => $titleLang->getDir(),
6442 $posStart = strpos( $text,
'<body' );
6443 if ( $posStart ===
false ) {
6446 $posStart = strpos( $text,
'>', $posStart );
6447 if ( $posStart ===
false ) {
6452 $posEnd = strrpos( $text,
'</body>', $posStart );
6453 if ( $posEnd ===
false ) {
6455 return substr( $text, $posStart );
6457 return substr( $text, $posStart, $posEnd - $posStart );
6470 OutputPage::setupOOUI();
6471 $this->mOutput->setEnableOOUI(
true );
6480 private function setOutputFlag(
ParserOutputFlags|
string $flag,
string $reason ): void {
6481 $this->mOutput->setOutputFlag( $flag );
6484 $flag = $flag->value;
6486 $name = $this->getTitle()->getPrefixedText();
6487 $this->logger->debug( __METHOD__ .
": set $flag flag on '$name'; $reason" );
return[ 'config-schema-inverse'=>['default'=>['ConfigRegistry'=>['main'=> 'MediaWiki\\Config\\GlobalVarConfig::newInstance',], 'Sitename'=> 'MediaWiki', 'Server'=> false, 'CanonicalServer'=> false, 'ServerName'=> false, 'AssumeProxiesUseDefaultProtocolPorts'=> true, 'HttpsPort'=> 443, 'ForceHTTPS'=> false, 'ScriptPath'=> '/wiki', 'UsePathInfo'=> null, 'Script'=> false, 'LoadScript'=> false, 'RestPath'=> false, 'StylePath'=> false, 'LocalStylePath'=> false, 'ExtensionAssetsPath'=> false, 'ExtensionDirectory'=> null, 'StyleDirectory'=> null, 'ArticlePath'=> false, 'UploadPath'=> false, 'ImgAuthPath'=> false, 'ThumbPath'=> false, 'UploadDirectory'=> false, 'FileCacheDirectory'=> false, 'Logo'=> false, 'Logos'=> false, 'Favicon'=> '/favicon.ico', 'AppleTouchIcon'=> false, 'ReferrerPolicy'=> false, 'TmpDirectory'=> false, 'UploadBaseUrl'=> '', 'UploadStashScalerBaseUrl'=> false, 'ActionPaths'=>[], 'MainPageIsDomainRoot'=> false, 'EnableUploads'=> false, 'UploadStashMaxAge'=> 21600, 'EnableAsyncUploads'=> false, 'EnableAsyncUploadsByURL'=> false, 'UploadMaintenance'=> false, 'IllegalFileChars'=> ':\\/\\\\', 'DeletedDirectory'=> false, 'ImgAuthDetails'=> false, 'ImgAuthUrlPathMap'=>[], 'LocalFileRepo'=>['class'=> 'MediaWiki\\FileRepo\\LocalRepo', 'name'=> 'local', 'directory'=> null, 'scriptDirUrl'=> null, 'favicon'=> null, 'url'=> null, 'hashLevels'=> null, 'thumbScriptUrl'=> null, 'transformVia404'=> null, 'deletedDir'=> null, 'deletedHashLevels'=> null, 'updateCompatibleMetadata'=> null, 'reserializeMetadata'=> null,], 'ForeignFileRepos'=>[], 'UseInstantCommons'=> false, 'UseSharedUploads'=> false, 'SharedUploadDirectory'=> null, 'SharedUploadPath'=> null, 'HashedSharedUploadDirectory'=> true, 'RepositoryBaseUrl'=> 'https:'FetchCommonsDescriptions'=> false, 'SharedUploadDBname'=> false, 'SharedUploadDBprefix'=> '', 'CacheSharedUploads'=> true, 'ForeignUploadTargets'=>['local',], 'UploadDialog'=>['fields'=>['description'=> true, 'date'=> false, 'categories'=> false,], 'licensemessages'=>['local'=> 'generic-local', 'foreign'=> 'generic-foreign',], 'comment'=>['local'=> '', 'foreign'=> '',], 'format'=>['filepage'=> ' $DESCRIPTION', 'description'=> ' $TEXT', 'ownwork'=> '', 'license'=> '', 'uncategorized'=> '',],], 'FileBackends'=>[], 'LockManagers'=>[], 'ShowEXIF'=> null, 'UpdateCompatibleMetadata'=> false, 'AllowCopyUploads'=> false, 'CopyUploadsDomains'=>[], 'CopyUploadsFromSpecialUpload'=> false, 'CopyUploadProxy'=> false, 'CopyUploadTimeout'=> false, 'CopyUploadAllowOnWikiDomainConfig'=> false, 'MaxUploadSize'=> 104857600, 'MinUploadChunkSize'=> 1024, 'UploadNavigationUrl'=> false, 'UploadMissingFileUrl'=> false, 'ThumbnailScriptPath'=> false, 'SharedThumbnailScriptPath'=> false, 'HashedUploadDirectory'=> true, 'CSPUploadEntryPoint'=> true, 'FileExtensions'=>['png', 'gif', 'jpg', 'jpeg', 'webp',], 'ProhibitedFileExtensions'=>['html', 'htm', 'js', 'jsb', 'mhtml', 'mht', 'xhtml', 'xht', 'php', 'phtml', 'php3', 'php4', 'php5', 'phps', 'phar', 'shtml', 'jhtml', 'pl', 'py', 'cgi', 'exe', 'scr', 'dll', 'msi', 'vbs', 'bat', 'com', 'pif', 'cmd', 'vxd', 'cpl', 'xml',], 'MimeTypeExclusions'=>['text/html', 'application/javascript', 'text/javascript', 'text/x-javascript', 'application/x-shellscript', 'application/x-php', 'text/x-php', 'text/x-python', 'text/x-perl', 'text/x-bash', 'text/x-sh', 'text/x-csh', 'text/scriptlet', 'application/x-msdownload', 'application/x-msmetafile', 'application/java', 'application/xml', 'text/xml',], 'CheckFileExtensions'=> true, 'StrictFileExtensions'=> true, 'DisableUploadScriptChecks'=> false, 'UploadSizeWarning'=> false, 'TrustedMediaFormats'=>['BITMAP', 'AUDIO', 'VIDEO', 'image/svg+xml', 'application/pdf',], 'MediaHandlers'=>[], 'NativeImageLazyLoading'=> false, 'ParserTestMediaHandlers'=>['image/jpeg'=> 'MockBitmapHandler', 'image/png'=> 'MockBitmapHandler', 'image/gif'=> 'MockBitmapHandler', 'image/tiff'=> 'MockBitmapHandler', 'image/webp'=> 'MockBitmapHandler', 'image/x-ms-bmp'=> 'MockBitmapHandler', 'image/x-bmp'=> 'MockBitmapHandler', 'image/x-xcf'=> 'MockBitmapHandler', 'image/svg+xml'=> 'MockSvgHandler', 'image/vnd.djvu'=> 'MockDjVuHandler',], 'UseImageResize'=> true, 'UseImageMagick'=> false, 'ImageMagickConvertCommand'=> '/usr/bin/convert', 'MaxInterlacingAreas'=>[], 'SharpenParameter'=> '0x0.4', 'SharpenReductionThreshold'=> 0.85, 'ImageMagickTempDir'=> false, 'CustomConvertCommand'=> false, 'JpegTran'=> '/usr/bin/jpegtran', 'JpegPixelFormat'=> 'yuv420', 'JpegQuality'=> 80, 'Exiv2Command'=> '/usr/bin/exiv2', 'Exiftool'=> '/usr/bin/exiftool', 'SVGConverters'=>['ImageMagick'=> ' $path/convert -background "#ffffff00" -thumbnail $widthx$height\\! $input PNG:$output', 'inkscape'=> ' $path/inkscape -w $width -o $output $input', 'batik'=> 'java -Djava.awt.headless=true -jar $path/batik-rasterizer.jar -w $width -d $output $input', 'rsvg'=> ' $path/rsvg-convert -w $width -h $height -o $output $input', 'ImagickExt'=>['SvgHandler::rasterizeImagickExt',],], 'SVGConverter'=> 'ImageMagick', 'SVGConverterPath'=> '', 'SVGMaxSize'=> 5120, 'SVGMetadataCutoff'=> 5242880, 'SVGNativeRendering'=> true, 'SVGNativeRenderingSizeLimit'=> 51200, 'MediaInTargetLanguage'=> true, 'MaxImageArea'=> 12500000, 'MaxAnimatedGifArea'=> 12500000, 'TiffThumbnailType'=>[], 'ThumbnailEpoch'=> '20030516000000', 'AttemptFailureEpoch'=> 1, 'IgnoreImageErrors'=> false, 'GenerateThumbnailOnParse'=> true, 'ShowArchiveThumbnails'=> true, 'EnableAutoRotation'=> null, 'Antivirus'=> null, 'AntivirusSetup'=>['clamav'=>['command'=> 'clamscan --no-summary ', 'codemap'=>[0=> 0, 1=> 1, 52=> -1, ' *'=> false,], 'messagepattern'=> '/.*?:(.*)/sim',],], 'AntivirusRequired'=> true, 'VerifyMimeType'=> true, 'MimeTypeFile'=> 'internal', 'MimeInfoFile'=> 'internal', 'MimeDetectorCommand'=> null, 'TrivialMimeDetection'=> false, 'XMLMimeTypes'=>['http:'svg'=> 'image/svg+xml', 'http:'http:'html'=> 'text/html',], 'ImageLimits'=>[[320, 240,], [640, 480,], [800, 600,], [1024, 768,], [1280, 1024,], [2560, 2048,],], 'ThumbLimits'=>[120, 150, 180, 200, 250, 300,], 'ThumbnailNamespaces'=>[6,], 'ThumbnailSteps'=> null, 'ThumbnailStepsRatio'=> null, 'ThumbnailBuckets'=> null, 'ThumbnailMinimumBucketDistance'=> 50, 'UploadThumbnailRenderMap'=>[], 'UploadThumbnailRenderMethod'=> 'jobqueue', 'UploadThumbnailRenderHttpCustomHost'=> false, 'UploadThumbnailRenderHttpCustomDomain'=> false, 'UseTinyRGBForJPGThumbnails'=> false, 'GalleryOptions'=>[], 'ThumbUpright'=> 0.75, 'DirectoryMode'=> 511, 'ResponsiveImages'=> true, 'ImagePreconnect'=> false, 'DjvuUseBoxedCommand'=> false, 'DjvuDump'=> null, 'DjvuRenderer'=> null, 'DjvuTxt'=> null, 'DjvuPostProcessor'=> 'pnmtojpeg', 'DjvuOutputExtension'=> 'jpg', 'EmergencyContact'=> false, 'PasswordSender'=> false, 'NoReplyAddress'=> false, 'EnableEmail'=> true, 'EnableUserEmail'=> true, 'UserEmailUseReplyTo'=> true, 'PasswordReminderResendTime'=> 24, 'NewPasswordExpiry'=> 604800, 'UserEmailConfirmationTokenExpiry'=> 604800, 'UserEmailConfirmationUseHTML'=> false, 'PasswordExpirationDays'=> false, 'PasswordExpireGrace'=> 604800, 'SMTP'=> false, 'AdditionalMailParams'=> null, 'AllowHTMLEmail'=> false, 'EnotifFromEditor'=> false, 'EmailAuthentication'=> true, 'EnotifWatchlist'=> false, 'EnotifUserTalk'=> false, 'EnotifRevealEditorAddress'=> false, 'EnotifMinorEdits'=> true, 'EnotifUseRealName'=> false, 'UsersNotifiedOnAllChanges'=>[], 'DBname'=> 'my_wiki', 'DBmwschema'=> null, 'DBprefix'=> '', 'DBserver'=> 'localhost', 'DBport'=> 5432, 'DBuser'=> 'wikiuser', 'DBpassword'=> '', 'DBtype'=> 'mysql', 'DBssl'=> false, 'DBcompress'=> false, 'DBStrictWarnings'=> false, 'DBadminuser'=> null, 'DBadminpassword'=> null, 'SearchType'=> null, 'SearchTypeAlternatives'=> null, 'DBTableOptions'=> 'ENGINE=InnoDB, DEFAULT CHARSET=binary', 'SQLMode'=> '', 'SQLiteDataDir'=> '', 'SharedDB'=> null, 'SharedPrefix'=> false, 'SharedTables'=>['user', 'user_properties', 'user_autocreate_serial',], 'SharedSchema'=> false, 'DBservers'=> false, 'LBFactoryConf'=>['class'=> 'Wikimedia\\Rdbms\\LBFactorySimple',], 'DataCenterUpdateStickTTL'=> 10, 'DBerrorLog'=> false, 'DBerrorLogTZ'=> false, 'LocalDatabases'=>[], 'DatabaseReplicaLagWarning'=> 10, 'DatabaseReplicaLagCritical'=> 30, 'MaxExecutionTimeForExpensiveQueries'=> 0, 'VirtualDomainsMapping'=>[], 'FileSchemaMigrationStage'=> 3, 'ImageLinksSchemaMigrationStage'=> 769, 'ExternalLinksDomainGaps'=>[], 'ContentHandlers'=>['wikitext'=>['class'=> 'MediaWiki\\Content\\WikitextContentHandler', 'services'=>['TitleFactory', 'ParserFactory', 'GlobalIdGenerator', 'LanguageNameUtils', 'LinkRenderer', 'MagicWordFactory', 'ParsoidParserFactory',],], 'javascript'=>['class'=> 'MediaWiki\\Content\\JavaScriptContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup',],], 'json'=>['class'=> 'MediaWiki\\Content\\JsonContentHandler', 'services'=>['ParsoidParserFactory', 'TitleFactory',],], 'css'=>['class'=> 'MediaWiki\\Content\\CssContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup',],], 'vue'=>['class'=> 'MediaWiki\\Content\\VueContentHandler', 'services'=>['MainConfig', 'ParserFactory',],], 'text'=> 'MediaWiki\\Content\\TextContentHandler', 'unknown'=> 'MediaWiki\\Content\\FallbackContentHandler',], 'NamespaceContentModels'=>[], 'TextModelsToParse'=>['wikitext', 'javascript', 'css',], 'CompressRevisions'=> false, 'ExternalStores'=>[], 'ExternalServers'=>[], 'DefaultExternalStore'=> false, 'RevisionCacheExpiry'=> 604800, 'PageLanguageUseDB'=> false, 'DiffEngine'=> null, 'ExternalDiffEngine'=> false, 'Wikidiff2Options'=>[], 'RequestTimeLimit'=> null, 'TransactionalTimeLimit'=> 120, 'CriticalSectionTimeLimit'=> 180.0, 'MiserMode'=> false, 'DisableQueryPages'=> false, 'QueryCacheLimit'=> 1000, 'WantedPagesThreshold'=> 1, 'AllowSlowParserFunctions'=> false, 'AllowSchemaUpdates'=> true, 'MaxArticleSize'=> 2048, 'MemoryLimit'=> '50M', 'PoolCounterConf'=> null, 'PoolCountClientConf'=>['servers'=>['127.0.0.1',], 'timeout'=> 0.1,], 'MaxUserDBWriteDuration'=> false, 'MaxJobDBWriteDuration'=> false, 'LinkHolderBatchSize'=> 1000, 'MaximumMovedPages'=> 100, 'ForceDeferredUpdatesPreSend'=> false, 'MultiShardSiteStats'=> false, 'CacheDirectory'=> false, 'MainCacheType'=> 0, 'MessageCacheType'=> -1, 'ParserCacheType'=> -1, 'SessionCacheType'=> -1, 'AnonSessionCacheType'=> false, 'LanguageConverterCacheType'=> -1, 'ObjectCaches'=>[0=>['class'=> 'Wikimedia\\ObjectCache\\EmptyBagOStuff', 'reportDupes'=> false,], 1=>['class'=> 'MediaWiki\\ObjectCache\\SqlBagOStuff', 'loggroup'=> 'SQLBagOStuff',], 'memcached-php'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPhpBagOStuff', 'loggroup'=> 'memcached',], 'memcached-pecl'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPeclBagOStuff', 'loggroup'=> 'memcached',], 'hash'=>['class'=> 'Wikimedia\\ObjectCache\\HashBagOStuff', 'reportDupes'=> false,], 'apc'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,], 'apcu'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,],], 'WANObjectCache'=>[], 'MicroStashType'=> -1, 'MainStash'=> 1, 'ParsoidCacheConfig'=>['StashType'=> null, 'StashDuration'=> 86400, 'WarmParsoidParserCache'=> false,], 'ParsoidSelectiveUpdateSampleRate'=> 0, 'ParserCacheFilterConfig'=>['pcache'=>['default'=>['minCpuTime'=> 0,],], 'parsoid-pcache'=>['default'=>['minCpuTime'=> 0,],], 'postproc-pcache'=>['default'=>['minCpuTime'=> 9223372036854775807,],], 'postproc-parsoid-pcache'=>['default'=>['minCpuTime'=> 9223372036854775807,],],], 'ChronologyProtectorSecret'=> '', 'ParserCacheExpireTime'=> 86400, 'ParserCacheAsyncExpireTime'=> 60, 'ParserCacheAsyncRefreshJobs'=> true, 'OldRevisionParserCacheExpireTime'=> 3600, 'ObjectCacheSessionExpiry'=> 3600, 'PHPSessionHandling'=> 'warn', 'SuspiciousIpExpiry'=> false, 'SessionPbkdf2Iterations'=> 10001, 'UseSessionCookieJwt'=> false, 'MemCachedServers'=>['127.0.0.1:11211',], 'MemCachedPersistent'=> false, 'MemCachedTimeout'=> 500000, 'UseLocalMessageCache'=> false, 'AdaptiveMessageCache'=> false, 'LocalisationCacheConf'=>['class'=> 'MediaWiki\\Language\\LocalisationCache', 'store'=> 'detect', 'storeClass'=> false, 'storeDirectory'=> false, 'storeServer'=>[], 'forceRecache'=> false, 'manualRecache'=> false,], 'CachePages'=> true, 'CacheEpoch'=> '20030516000000', 'GitInfoCacheDirectory'=> false, 'UseFileCache'=> false, 'FileCacheDepth'=> 2, 'RenderHashAppend'=> '', 'EnableSidebarCache'=> false, 'SidebarCacheExpiry'=> 86400, 'UseGzip'=> false, 'InvalidateCacheOnLocalSettingsChange'=> true, 'ExtensionInfoMTime'=> false, 'EnableRemoteBagOStuffTests'=> false, 'UseCdn'=> false, 'VaryOnXFP'=> false, 'InternalServer'=> false, 'CdnMaxAge'=> 18000, 'CdnMaxageLagged'=> 30, 'CdnMaxageStale'=> 10, 'CdnReboundPurgeDelay'=> 0, 'CdnMaxageSubstitute'=> 60, 'ForcedRawSMaxage'=> 300, 'CdnServers'=>[], 'CdnServersNoPurge'=>[], 'HTCPRouting'=>[], 'HTCPMulticastTTL'=> 1, 'UsePrivateIPs'=> false, 'CdnMatchParameterOrder'=> true, 'LanguageCode'=> 'en', 'GrammarForms'=>[], 'InterwikiMagic'=> true, 'HideInterlanguageLinks'=> false, 'ExtraInterlanguageLinkPrefixes'=>[], 'InterlanguageLinkCodeMap'=>[], 'ExtraLanguageNames'=>[], 'ExtraLanguageCodes'=>['bh'=> 'bho', 'no'=> 'nb', 'simple'=> 'en',], 'DummyLanguageCodes'=>[], 'AllUnicodeFixes'=> false, 'LegacyEncoding'=> false, 'AmericanDates'=> false, 'TranslateNumerals'=> true, 'UseDatabaseMessages'=> true, 'MaxMsgCacheEntrySize'=> 10000, 'DisableLangConversion'=> false, 'DisableTitleConversion'=> false, 'DefaultLanguageVariant'=> false, 'UsePigLatinVariant'=> false, 'DisabledVariants'=>[], 'VariantArticlePath'=> false, 'UseXssLanguage'=> false, 'LoginLanguageSelector'=> false, 'ForceUIMsgAsContentMsg'=>[], 'RawHtmlMessages'=>[], 'Localtimezone'=> null, 'LocalTZoffset'=> null, 'OverrideUcfirstCharacters'=>[], 'MimeType'=> 'text/html', 'Html5Version'=> null, 'EditSubmitButtonLabelPublish'=> false, 'XhtmlNamespaces'=>[], 'SiteNotice'=> '', 'BrowserFormatDetection'=> 'telephone=no', 'SkinMetaTags'=>[], 'DefaultSkin'=> 'vector-2022', 'FallbackSkin'=> 'fallback', 'SkipSkins'=>[], 'DisableOutputCompression'=> false, 'FragmentMode'=>['html5', 'legacy',], 'ExternalInterwikiFragmentMode'=> 'legacy', 'FooterIcons'=>['copyright'=>['copyright'=>[],], 'poweredby'=>['mediawiki'=>['src'=> null, 'url'=> 'https:'alt'=> 'Powered by MediaWiki', 'lang'=> 'en',],],], 'UseCombinedLoginLink'=> false, 'Edititis'=> false, 'Send404Code'=> true, 'ShowRollbackEditCount'=> 10, 'EnableCanonicalServerLink'=> false, 'InterwikiLogoOverride'=>[], 'ResourceModules'=>[], 'ResourceModuleSkinStyles'=>[], 'ResourceLoaderSources'=>[], 'ResourceBasePath'=> null, 'ResourceLoaderMaxage'=>[], 'ResourceLoaderDebug'=> false, 'ResourceLoaderMaxQueryLength'=> false, 'ResourceLoaderValidateJS'=> true, 'ResourceLoaderEnableJSProfiler'=> false, 'ResourceLoaderStorageEnabled'=> true, 'ResourceLoaderStorageVersion'=> 1, 'ResourceLoaderEnableSourceMapLinks'=> true, 'AllowSiteCSSOnRestrictedPages'=> false, 'VueDevelopmentMode'=> false, 'CodexDevelopmentDir'=> null, 'MetaNamespace'=> false, 'MetaNamespaceTalk'=> false, 'CanonicalNamespaceNames'=>[-2=> 'Media', -1=> 'Special', 0=> '', 1=> 'Talk', 2=> 'User', 3=> 'User_talk', 4=> 'Project', 5=> 'Project_talk', 6=> 'File', 7=> 'File_talk', 8=> 'MediaWiki', 9=> 'MediaWiki_talk', 10=> 'Template', 11=> 'Template_talk', 12=> 'Help', 13=> 'Help_talk', 14=> 'Category', 15=> 'Category_talk',], 'ExtraNamespaces'=>[], 'ExtraGenderNamespaces'=>[], 'NamespaceAliases'=>[], 'LegalTitleChars'=> ' %!"$&\'()*,\\-.\\/0-9:;=?@A-Z\\\\^_`a-z~\\x80-\\xFF+', 'CapitalLinks' => true, 'CapitalLinkOverrides' => [ ], 'NamespacesWithSubpages' => [ 1 => true, 2 => true, 3 => true, 4 => true, 5 => true, 7 => true, 8 => true, 9 => true, 10 => true, 11 => true, 12 => true, 13 => true, 15 => true, ], 'ContentNamespaces' => [ 0, ], 'ShortPagesNamespaceExclusions' => [ ], 'ExtraSignatureNamespaces' => [ ], 'InvalidRedirectTargets' => [ 'Filepath', 'Mypage', 'Mytalk', 'Redirect', 'Mylog', ], 'DisableHardRedirects' => false, 'FixDoubleRedirects' => false, 'LocalInterwikis' => [ ], 'InterwikiExpiry' => 10800, 'InterwikiCache' => false, 'InterwikiScopes' => 3, 'InterwikiFallbackSite' => 'wiki', 'RedirectSources' => false, 'SiteTypes' => [ 'mediawiki' => 'MediaWiki\\Site\\MediaWikiSite', ], 'MaxTocLevel' => 999, 'MaxPPNodeCount' => 1000000, 'MaxTemplateDepth' => 100, 'MaxPPExpandDepth' => 100, 'UrlProtocols' => [ 'bitcoin:', 'ftp: 'ftps: 'geo:', 'git: 'gopher: 'http: 'https: 'irc: 'ircs: 'magnet:', 'mailto:', 'matrix:', 'mms: 'news:', 'nntp: 'redis: 'sftp: 'sip:', 'sips:', 'sms:', 'ssh: 'svn: 'tel:', 'telnet: 'urn:', 'wikipedia: 'worldwind: 'xmpp:', ' ], 'CleanSignatures' => true, 'AllowExternalImages' => false, 'AllowExternalImagesFrom' => '', 'EnableImageWhitelist' => false, 'TidyConfig' => [ ], 'ParsoidSettings' => [ 'useSelser' => true, ], 'ParsoidExperimentalParserFunctionOutput' => false, 'UseLegacyMediaStyles' => false, 'RawHtml' => false, 'ExternalLinkTarget' => false, 'NoFollowLinks' => true, 'NoFollowNsExceptions' => [ ], 'NoFollowDomainExceptions' => [ 'mediawiki.org', ], 'RegisterInternalExternals' => false, 'ExternalLinksIgnoreDomains' => [ ], 'AllowDisplayTitle' => true, 'RestrictDisplayTitle' => true, 'ExpensiveParserFunctionLimit' => 100, 'PreprocessorCacheThreshold' => 1000, 'EnableScaryTranscluding' => false, 'TranscludeCacheExpiry' => 3600, 'EnableMagicLinks' => [ 'ISBN' => false, 'PMID' => false, 'RFC' => false, ], 'ParserEnableUserLanguage' => false, 'ArticleCountMethod' => 'link', 'ActiveUserDays' => 30, 'LearnerEdits' => 10, 'LearnerMemberSince' => 4, 'ExperiencedUserEdits' => 500, 'ExperiencedUserMemberSince' => 30, 'ManualRevertSearchRadius' => 15, 'RevertedTagMaxDepth' => 15, 'CentralIdLookupProviders' => [ 'local' => [ 'class' => 'MediaWiki\\User\\CentralId\\LocalIdLookup', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', 'HideUserUtils', ], ], ], 'CentralIdLookupProvider' => 'local', 'UserRegistrationProviders' => [ 'local' => [ 'class' => 'MediaWiki\\User\\Registration\\LocalUserRegistrationProvider', 'services' => [ 'ConnectionProvider', ], ], ], 'PasswordPolicy' => [ 'policies' => [ 'bureaucrat' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'sysop' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'interface-admin' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'bot' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'default' => [ 'MinimalPasswordLength' => [ 'value' => 8, 'suggestChangeOnLogin' => true, ], 'PasswordCannotBeSubstringInUsername' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], 'PasswordCannotMatchDefaults' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], 'MaximalPasswordLength' => [ 'value' => 4096, 'suggestChangeOnLogin' => true, ], 'PasswordNotInCommonList' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], ], ], 'checks' => [ 'MinimalPasswordLength' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMinimalPasswordLength', ], 'MinimumPasswordLengthToLogin' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMinimumPasswordLengthToLogin', ], 'PasswordCannotBeSubstringInUsername' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordCannotBeSubstringInUsername', ], 'PasswordCannotMatchDefaults' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordCannotMatchDefaults', ], 'MaximalPasswordLength' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMaximalPasswordLength', ], 'PasswordNotInCommonList' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordNotInCommonList', ], ], ], 'AuthManagerConfig' => null, 'AuthManagerAutoConfig' => [ 'preauth' => [ 'MediaWiki\\Auth\\ThrottlePreAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\ThrottlePreAuthenticationProvider', 'sort' => 0, ], ], 'primaryauth' => [ 'MediaWiki\\Auth\\TemporaryPasswordPrimaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\TemporaryPasswordPrimaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', 'UserOptionsLookup', ], 'args' => [ [ 'authoritative' => false, ], ], 'sort' => 0, ], 'MediaWiki\\Auth\\LocalPasswordPrimaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\LocalPasswordPrimaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', ], 'args' => [ [ 'authoritative' => true, ], ], 'sort' => 100, ], ], 'secondaryauth' => [ 'MediaWiki\\Auth\\CheckBlocksSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\CheckBlocksSecondaryAuthenticationProvider', 'sort' => 0, ], 'MediaWiki\\Auth\\ResetPasswordSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\ResetPasswordSecondaryAuthenticationProvider', 'sort' => 100, ], 'MediaWiki\\Auth\\EmailNotificationSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\EmailNotificationSecondaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', ], 'sort' => 200, ], ], ], 'RememberMe' => 'choose', 'ReauthenticateTime' => [ 'default' => 3600, ], 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => [ 'default' => true, ], 'ChangeCredentialsBlacklist' => [ 'MediaWiki\\Auth\\TemporaryPasswordAuthenticationRequest', ], 'RemoveCredentialsBlacklist' => [ 'MediaWiki\\Auth\\PasswordAuthenticationRequest', ], 'InvalidPasswordReset' => true, 'PasswordDefault' => 'pbkdf2', 'PasswordConfig' => [ 'A' => [ 'class' => 'MediaWiki\\Password\\MWOldPassword', ], 'B' => [ 'class' => 'MediaWiki\\Password\\MWSaltedPassword', ], 'pbkdf2-legacyA' => [ 'class' => 'MediaWiki\\Password\\LayeredParameterizedPassword', 'types' => [ 'A', 'pbkdf2', ], ], 'pbkdf2-legacyB' => [ 'class' => 'MediaWiki\\Password\\LayeredParameterizedPassword', 'types' => [ 'B', 'pbkdf2', ], ], 'bcrypt' => [ 'class' => 'MediaWiki\\Password\\BcryptPassword', 'cost' => 9, ], 'pbkdf2' => [ 'class' => 'MediaWiki\\Password\\Pbkdf2PasswordUsingOpenSSL', 'algo' => 'sha512', 'cost' => '30000', 'length' => '64', ], 'argon2' => [ 'class' => 'MediaWiki\\Password\\Argon2Password', 'algo' => 'auto', ], ], 'PasswordResetRoutes' => [ 'username' => true, 'email' => true, ], 'MaxSigChars' => 255, 'SignatureValidation' => 'warning', 'SignatureAllowedLintErrors' => [ 'obsolete-tag', ], 'MaxNameChars' => 255, 'ReservedUsernames' => [ 'MediaWiki default', 'Conversion script', 'Maintenance script', 'Template namespace initialisation script', 'ScriptImporter', 'Delete page script', 'Move page script', 'Command line script', 'Unknown user', 'msg:double-redirect-fixer', 'msg:usermessage-editor', 'msg:proxyblocker', 'msg:sorbs', 'msg:spambot_username', 'msg:autochange-username', ], 'DefaultUserOptions' => [ 'ccmeonemails' => 0, 'date' => 'default', 'diffonly' => 0, 'diff-type' => 'table', 'disablemail' => 0, 'editfont' => 'monospace', 'editondblclick' => 0, 'editrecovery' => 0, 'editsectiononrightclick' => 0, 'email-allow-new-users' => 1, 'enotifminoredits' => 0, 'enotifrevealaddr' => 0, 'enotifusertalkpages' => 1, 'enotifwatchlistpages' => 1, 'extendwatchlist' => 1, 'fancysig' => 0, 'forceeditsummary' => 0, 'forcesafemode' => 0, 'gender' => 'unknown', 'hidecategorization' => 1, 'hideminor' => 0, 'hidepatrolled' => 0, 'imagesize' => 2, 'minordefault' => 0, 'newpageshidepatrolled' => 0, 'nickname' => '', 'norollbackdiff' => 0, 'prefershttps' => 1, 'previewonfirst' => 0, 'previewontop' => 1, 'pst-cssjs' => 1, 'rcdays' => 7, 'rcenhancedfilters-disable' => 0, 'rclimit' => 50, 'requireemail' => 0, 'search-match-redirect' => true, 'search-special-page' => 'Search', 'search-thumbnail-extra-namespaces' => true, 'searchlimit' => 20, 'showhiddencats' => 0, 'shownumberswatching' => 1, 'showrollbackconfirmation' => 0, 'skin' => false, 'skin-responsive' => 1, 'thumbsize' => 5, 'underline' => 2, 'useeditwarning' => 1, 'uselivepreview' => 0, 'usenewrc' => 1, 'watchcreations' => 1, 'watchcreations-expiry' => 'infinite', 'watchdefault' => 1, 'watchdefault-expiry' => 'infinite', 'watchdeletion' => 0, 'watchlistdays' => 7, 'watchlisthideanons' => 0, 'watchlisthidebots' => 0, 'watchlisthidecategorization' => 1, 'watchlisthideliu' => 0, 'watchlisthideminor' => 0, 'watchlisthideown' => 0, 'watchlisthidepatrolled' => 0, 'watchlistreloadautomatically' => 0, 'watchlistunwatchlinks' => 0, 'watchmoves' => 0, 'watchrollback' => 0, 'watchuploads' => 1, 'watchrollback-expiry' => 'infinite', 'watchstar-expiry' => 'infinite', 'wlenhancedfilters-disable' => 0, 'wllimit' => 250, ], 'ConditionalUserOptions' => [ ], 'HiddenPrefs' => [ ], 'UserJsPrefLimit' => 100, 'InvalidUsernameCharacters' => '@:>=', 'UserrightsInterwikiDelimiter' => '@', 'SecureLogin' => false, 'AuthenticationTokenVersion' => null, 'SessionProviders' => [ 'MediaWiki\\Session\\CookieSessionProvider' => [ 'class' => 'MediaWiki\\Session\\CookieSessionProvider', 'args' => [ [ 'priority' => 30, ], ], 'services' => [ 'JwtCodec', 'UrlUtils', ], ], 'MediaWiki\\Session\\BotPasswordSessionProvider' => [ 'class' => 'MediaWiki\\Session\\BotPasswordSessionProvider', 'args' => [ [ 'priority' => 75, ], ], 'services' => [ 'GrantsInfo', ], ], ], 'AutoCreateTempUser' => [ 'known' => false, 'enabled' => false, 'actions' => [ 'edit', ], 'genPattern' => '~$1', 'matchPattern' => null, 'reservedPattern' => '~$1', 'serialProvider' => [ 'type' => 'local', 'useYear' => true, ], 'serialMapping' => [ 'type' => 'readable-numeric', ], 'expireAfterDays' => 90, 'notifyBeforeExpirationDays' => 10, ], 'AutoblockExemptions' => [ ], 'AutoblockExpiry' => 86400, 'BlockAllowsUTEdit' => true, 'BlockCIDRLimit' => [ 'IPv4' => 16, 'IPv6' => 19, ], 'BlockDisablesLogin' => false, 'EnableMultiBlocks' => false, 'WhitelistRead' => false, 'WhitelistReadRegexp' => false, 'EmailConfirmToEdit' => false, 'HideIdentifiableRedirects' => true, 'GroupPermissions' => [ '*' => [ 'createaccount' => true, 'read' => true, 'edit' => true, 'createpage' => true, 'createtalk' => true, 'viewmyprivateinfo' => true, 'editmyprivateinfo' => true, 'editmyoptions' => true, ], 'user' => [ 'move' => true, 'move-subpages' => true, 'move-rootuserpages' => true, 'move-categorypages' => true, 'movefile' => true, 'read' => true, 'edit' => true, 'createpage' => true, 'createtalk' => true, 'upload' => true, 'reupload' => true, 'reupload-shared' => true, 'minoredit' => true, 'editmyusercss' => true, 'editmyuserjson' => true, 'editmyuserjs' => true, 'editmyuserjsredirect' => true, 'sendemail' => true, 'applychangetags' => true, 'changetags' => true, 'viewmywatchlist' => true, 'editmywatchlist' => true, ], 'autoconfirmed' => [ 'autoconfirmed' => true, 'editsemiprotected' => true, ], 'bot' => [ 'bot' => true, 'autoconfirmed' => true, 'editsemiprotected' => true, 'nominornewtalk' => true, 'autopatrol' => true, 'suppressredirect' => true, 'apihighlimits' => true, ], 'sysop' => [ 'block' => true, 'createaccount' => true, 'delete' => true, 'bigdelete' => true, 'deletedhistory' => true, 'deletedtext' => true, 'undelete' => true, 'editcontentmodel' => true, 'editinterface' => true, 'editsitejson' => true, 'edituserjson' => true, 'import' => true, 'importupload' => true, 'move' => true, 'move-subpages' => true, 'move-rootuserpages' => true, 'move-categorypages' => true, 'patrol' => true, 'autopatrol' => true, 'protect' => true, 'editprotected' => true, 'rollback' => true, 'upload' => true, 'reupload' => true, 'reupload-shared' => true, 'unwatchedpages' => true, 'autoconfirmed' => true, 'editsemiprotected' => true, 'ipblock-exempt' => true, 'blockemail' => true, 'markbotedits' => true, 'apihighlimits' => true, 'browsearchive' => true, 'noratelimit' => true, 'movefile' => true, 'unblockself' => true, 'suppressredirect' => true, 'mergehistory' => true, 'managechangetags' => true, 'deletechangetags' => true, ], 'interface-admin' => [ 'editinterface' => true, 'editsitecss' => true, 'editsitejson' => true, 'editsitejs' => true, 'editusercss' => true, 'edituserjson' => true, 'edituserjs' => true, ], 'bureaucrat' => [ 'userrights' => true, 'noratelimit' => true, 'renameuser' => true, ], 'suppress' => [ 'hideuser' => true, 'suppressrevision' => true, 'viewsuppressed' => true, 'suppressionlog' => true, 'deleterevision' => true, 'deletelogentry' => true, ], ], 'PrivilegedGroups' => [ 'bureaucrat', 'interface-admin', 'suppress', 'sysop', ], 'RevokePermissions' => [ ], 'GroupInheritsPermissions' => [ ], 'ImplicitGroups' => [ '*', 'user', 'autoconfirmed', ], 'GroupsAddToSelf' => [ ], 'GroupsRemoveFromSelf' => [ ], 'RestrictedGroups' => [ ], 'UserRequirementsPrivateConditions' => [ ], 'RestrictionTypes' => [ 'create', 'edit', 'move', 'upload', ], 'RestrictionLevels' => [ '', 'autoconfirmed', 'sysop', ], 'CascadingRestrictionLevels' => [ 'sysop', ], 'SemiprotectedRestrictionLevels' => [ 'autoconfirmed', ], 'NamespaceProtection' => [ ], 'NonincludableNamespaces' => [ ], 'AutoConfirmAge' => 0, 'AutoConfirmCount' => 0, 'Autopromote' => [ 'autoconfirmed' => [ '&', [ 1, null, ], [ 2, null, ], ], ], 'AutopromoteOnce' => [ 'onEdit' => [ ], ], 'AutopromoteOnceLogInRC' => true, 'AutopromoteOnceRCExcludedGroups' => [ ], 'AddGroups' => [ ], 'RemoveGroups' => [ ], 'AvailableRights' => [ ], 'ImplicitRights' => [ ], 'DeleteRevisionsLimit' => 0, 'DeleteRevisionsBatchSize' => 1000, 'HideUserContribLimit' => 1000, 'AccountCreationThrottle' => [ [ 'count' => 0, 'seconds' => 86400, ], ], 'TempAccountCreationThrottle' => [ [ 'count' => 1, 'seconds' => 600, ], [ 'count' => 6, 'seconds' => 86400, ], ], 'TempAccountNameAcquisitionThrottle' => [ [ 'count' => 60, 'seconds' => 86400, ], ], 'SpamRegex' => [ ], 'SummarySpamRegex' => [ ], 'EnableDnsBlacklist' => false, 'DnsBlacklistUrls' => [ ], 'ProxyList' => [ ], 'ProxyWhitelist' => [ ], 'SoftBlockRanges' => [ ], 'ApplyIpBlocksToXff' => false, 'RateLimits' => [ 'edit' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], 'user' => [ 90, 60, ], ], 'move' => [ 'newbie' => [ 2, 120, ], 'user' => [ 8, 60, ], ], 'upload' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], ], 'rollback' => [ 'user' => [ 10, 60, ], 'newbie' => [ 5, 120, ], ], 'mailpassword' => [ 'ip' => [ 5, 3600, ], ], 'sendemail' => [ 'ip' => [ 5, 86400, ], 'newbie' => [ 5, 86400, ], 'user' => [ 20, 86400, ], ], 'changeemail' => [ 'ip-all' => [ 10, 3600, ], 'user' => [ 4, 86400, ], ], 'confirmemail' => [ 'ip-all' => [ 10, 3600, ], 'user' => [ 4, 86400, ], ], 'purge' => [ 'ip' => [ 30, 60, ], 'user' => [ 30, 60, ], ], 'linkpurge' => [ 'ip' => [ 30, 60, ], 'user' => [ 30, 60, ], ], 'renderfile' => [ 'ip' => [ 700, 30, ], 'user' => [ 700, 30, ], ], 'renderfile-nonstandard' => [ 'ip' => [ 70, 30, ], 'user' => [ 70, 30, ], ], 'stashedit' => [ 'ip' => [ 30, 60, ], 'newbie' => [ 30, 60, ], ], 'stashbasehtml' => [ 'ip' => [ 5, 60, ], 'newbie' => [ 5, 60, ], ], 'changetags' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], ], 'editcontentmodel' => [ 'newbie' => [ 2, 120, ], 'user' => [ 8, 60, ], ], ], 'RateLimitsExcludedIPs' => [ ], 'PutIPinRC' => true, 'QueryPageDefaultLimit' => 50, 'ExternalQuerySources' => [ ], 'PasswordAttemptThrottle' => [ [ 'count' => 5, 'seconds' => 300, ], [ 'count' => 150, 'seconds' => 172800, ], ], 'GrantPermissions' => [ 'basic' => [ 'autocreateaccount' => true, 'autoconfirmed' => true, 'autopatrol' => true, 'editsemiprotected' => true, 'ipblock-exempt' => true, 'nominornewtalk' => true, 'patrolmarks' => true, 'read' => true, 'unwatchedpages' => true, ], 'highvolume' => [ 'bot' => true, 'apihighlimits' => true, 'noratelimit' => true, 'markbotedits' => true, ], 'import' => [ 'import' => true, 'importupload' => true, ], 'editpage' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'pagelang' => true, ], 'editprotected' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editprotected' => true, ], 'editmycssjs' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editmyusercss' => true, 'editmyuserjson' => true, 'editmyuserjs' => true, ], 'editmyoptions' => [ 'editmyoptions' => true, 'editmyuserjson' => true, ], 'editinterface' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editinterface' => true, 'edituserjson' => true, 'editsitejson' => true, ], 'editsiteconfig' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editinterface' => true, 'edituserjson' => true, 'editsitejson' => true, 'editusercss' => true, 'edituserjs' => true, 'editsitecss' => true, 'editsitejs' => true, ], 'createeditmovepage' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createpage' => true, 'createtalk' => true, 'delete-redirect' => true, 'move' => true, 'move-rootuserpages' => true, 'move-subpages' => true, 'move-categorypages' => true, 'suppressredirect' => true, ], 'uploadfile' => [ 'upload' => true, 'reupload-own' => true, ], 'uploadeditmovefile' => [ 'upload' => true, 'reupload-own' => true, 'reupload' => true, 'reupload-shared' => true, 'upload_by_url' => true, 'movefile' => true, 'suppressredirect' => true, ], 'patrol' => [ 'patrol' => true, ], 'rollback' => [ 'rollback' => true, ], 'blockusers' => [ 'block' => true, 'blockemail' => true, ], 'viewdeleted' => [ 'browsearchive' => true, 'deletedhistory' => true, 'deletedtext' => true, ], 'viewrestrictedlogs' => [ 'suppressionlog' => true, ], 'delete' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'browsearchive' => true, 'deletedhistory' => true, 'deletedtext' => true, 'delete' => true, 'bigdelete' => true, 'deletelogentry' => true, 'deleterevision' => true, 'undelete' => true, ], 'oversight' => [ 'suppressrevision' => true, 'viewsuppressed' => true, ], 'protect' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editprotected' => true, 'protect' => true, ], 'viewmywatchlist' => [ 'viewmywatchlist' => true, ], 'editmywatchlist' => [ 'editmywatchlist' => true, ], 'sendemail' => [ 'sendemail' => true, ], 'createaccount' => [ 'createaccount' => true, ], 'privateinfo' => [ 'viewmyprivateinfo' => true, ], 'mergehistory' => [ 'mergehistory' => true, ], ], 'GrantPermissionGroups' => [ 'basic' => 'hidden', 'editpage' => 'page-interaction', 'createeditmovepage' => 'page-interaction', 'editprotected' => 'page-interaction', 'patrol' => 'page-interaction', 'uploadfile' => 'file-interaction', 'uploadeditmovefile' => 'file-interaction', 'sendemail' => 'email', 'viewmywatchlist' => 'watchlist-interaction', 'editviewmywatchlist' => 'watchlist-interaction', 'editmycssjs' => 'customization', 'editmyoptions' => 'customization', 'editinterface' => 'administration', 'editsiteconfig' => 'administration', 'rollback' => 'administration', 'blockusers' => 'administration', 'delete' => 'administration', 'viewdeleted' => 'administration', 'viewrestrictedlogs' => 'administration', 'protect' => 'administration', 'oversight' => 'administration', 'createaccount' => 'administration', 'mergehistory' => 'administration', 'import' => 'administration', 'highvolume' => 'high-volume', 'privateinfo' => 'private-information', ], 'GrantRiskGroups' => [ 'basic' => 'low', 'editpage' => 'low', 'createeditmovepage' => 'low', 'editprotected' => 'vandalism', 'patrol' => 'low', 'uploadfile' => 'low', 'uploadeditmovefile' => 'low', 'sendemail' => 'security', 'viewmywatchlist' => 'low', 'editviewmywatchlist' => 'low', 'editmycssjs' => 'security', 'editmyoptions' => 'security', 'editinterface' => 'vandalism', 'editsiteconfig' => 'security', 'rollback' => 'low', 'blockusers' => 'vandalism', 'delete' => 'vandalism', 'viewdeleted' => 'vandalism', 'viewrestrictedlogs' => 'security', 'protect' => 'vandalism', 'oversight' => 'security', 'createaccount' => 'low', 'mergehistory' => 'vandalism', 'import' => 'security', 'highvolume' => 'low', 'privateinfo' => 'low', ], 'EnableBotPasswords' => true, 'BotPasswordsCluster' => false, 'BotPasswordsDatabase' => false, 'SecretKey' => false, 'JwtPrivateKey' => false, 'JwtPublicKey' => false, 'AllowUserJs' => false, 'AllowUserCss' => false, 'AllowUserCssPrefs' => true, 'UseSiteJs' => true, 'UseSiteCss' => true, 'BreakFrames' => false, 'EditPageFrameOptions' => 'DENY', 'ApiFrameOptions' => 'DENY', 'CSPHeader' => false, 'CSPReportOnlyHeader' => false, 'CSPFalsePositiveUrls' => [ 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'chrome-extension' => true, ], 'AllowCrossOrigin' => false, 'RestAllowCrossOriginCookieAuth' => false, 'SessionSecret' => false, 'CookieExpiration' => 2592000, 'ExtendedLoginCookieExpiration' => 15552000, 'SessionCookieJwtExpiration' => 14400, 'CookieDomain' => '', 'CookiePath' => '/', 'CookieSecure' => 'detect', 'CookiePrefix' => false, 'CookieHttpOnly' => true, 'CookieSameSite' => null, 'CacheVaryCookies' => [ ], 'SessionName' => false, 'CookieSetOnAutoblock' => true, 'CookieSetOnIpBlock' => true, 'DebugLogFile' => '', 'DebugLogPrefix' => '', 'DebugRedirects' => false, 'DebugRawPage' => false, 'DebugComments' => false, 'DebugDumpSql' => false, 'TrxProfilerLimits' => [ 'GET' => [ 'masterConns' => 0, 'writes' => 0, 'readQueryTime' => 5, 'readQueryRows' => 10000, ], 'POST' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 100000, 'maxAffected' => 1000, ], 'POST-nonwrite' => [ 'writes' => 0, 'readQueryTime' => 5, 'readQueryRows' => 10000, ], 'PostSend-GET' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 10000, 'maxAffected' => 1000, 'masterConns' => 0, 'writes' => 0, ], 'PostSend-POST' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 100000, 'maxAffected' => 1000, ], 'JobRunner' => [ 'readQueryTime' => 30, 'writeQueryTime' => 5, 'readQueryRows' => 100000, 'maxAffected' => 500, ], 'Maintenance' => [ 'writeQueryTime' => 5, 'maxAffected' => 1000, ], ], 'DebugLogGroups' => [ ], 'MWLoggerDefaultSpi' => [ 'class' => 'MediaWiki\\Logger\\LegacySpi', ], 'ShowDebug' => false, 'SpecialVersionShowHooks' => false, 'ShowExceptionDetails' => false, 'LogExceptionBacktrace' => true, 'PropagateErrors' => true, 'ShowHostnames' => false, 'OverrideHostname' => false, 'DevelopmentWarnings' => false, 'DeprecationReleaseLimit' => false, 'Profiler' => [ ], 'StatsdServer' => false, 'StatsdMetricPrefix' => 'MediaWiki', 'StatsTarget' => null, 'StatsFormat' => null, 'StatsPrefix' => 'mediawiki', 'OpenTelemetryConfig' => null, 'PageInfoTransclusionLimit' => 50, 'EnableJavaScriptTest' => false, 'CachePrefix' => false, 'DebugToolbar' => false, 'DisableTextSearch' => false, 'AdvancedSearchHighlighting' => false, 'SearchHighlightBoundaries' => '[\\p{Z}\\p{P}\\p{C}]', 'OpenSearchTemplates' => [ 'application/x-suggestions+json' => false, 'application/x-suggestions+xml' => false, ], 'OpenSearchDefaultLimit' => 10, 'OpenSearchDescriptionLength' => 100, 'SearchSuggestCacheExpiry' => 1200, 'DisableSearchUpdate' => false, 'NamespacesToBeSearchedDefault' => [ true, ], 'DisableInternalSearch' => false, 'SearchForwardUrl' => null, 'SitemapNamespaces' => false, 'SitemapNamespacesPriorities' => false, 'SitemapApiConfig' => [ ], 'SpecialSearchFormOptions' => [ ], 'SearchMatchRedirectPreference' => false, 'SearchRunSuggestedQuery' => true, 'Diff3' => '/usr/bin/diff3', 'Diff' => '/usr/bin/diff', 'PreviewOnOpenNamespaces' => [ 14 => true, ], 'UniversalEditButton' => true, 'UseAutomaticEditSummaries' => true, 'CommandLineDarkBg' => false, 'ReadOnly' => null, 'ReadOnlyWatchedItemStore' => false, 'ReadOnlyFile' => false, 'UpgradeKey' => false, 'GitBin' => '/usr/bin/git', 'GitRepositoryViewers' => [ 'https: 'ssh: ], 'InstallerInitialPages' => [ [ 'titlemsg' => 'mainpage', 'text' => '{{subst:int:mainpagetext}}{{subst:int:mainpagedocfooter}}', ], ], 'RCMaxAge' => 7776000, 'WatchersMaxAge' => 15552000, 'UnwatchedPageSecret' => 1, 'RCFilterByAge' => false, 'RCLinkLimits' => [ 50, 100, 250, 500, ], 'RCLinkDays' => [ 1, 3, 7, 14, 30, ], 'RCFeeds' => [ ], 'RCEngines' => [ 'redis' => 'MediaWiki\\RCFeed\\RedisPubSubFeedEngine', 'udp' => 'MediaWiki\\RCFeed\\UDPRCFeedEngine', ], 'RCWatchCategoryMembership' => false, 'UseRCPatrol' => true, 'StructuredChangeFiltersLiveUpdatePollingRate' => 3, 'UseNPPatrol' => true, 'UseFilePatrol' => true, 'Feed' => true, 'FeedLimit' => 50, 'FeedCacheTimeout' => 60, 'FeedDiffCutoff' => 32768, 'OverrideSiteFeed' => [ ], 'FeedClasses' => [ 'rss' => 'MediaWiki\\Feed\\RSSFeed', 'atom' => 'MediaWiki\\Feed\\AtomFeed', ], 'AdvertisedFeedTypes' => [ 'atom', ], 'RCShowWatchingUsers' => false, 'RCShowChangedSize' => true, 'RCChangedSizeThreshold' => 500, 'ShowUpdatedMarker' => true, 'DisableAnonTalk' => false, 'UseTagFilter' => true, 'SoftwareTags' => [ 'mw-contentmodelchange' => true, 'mw-new-redirect' => true, 'mw-removed-redirect' => true, 'mw-changed-redirect-target' => true, 'mw-blank' => true, 'mw-replace' => true, 'mw-recreated' => true, 'mw-rollback' => true, 'mw-undo' => true, 'mw-manual-revert' => true, 'mw-reverted' => true, 'mw-server-side-upload' => true, 'mw-ipblock-appeal' => true, ], 'UnwatchedPageThreshold' => false, 'RecentChangesFlags' => [ 'newpage' => [ 'letter' => 'newpageletter', 'title' => 'recentchanges-label-newpage', 'legend' => 'recentchanges-legend-newpage', 'grouping' => 'any', ], 'minor' => [ 'letter' => 'minoreditletter', 'title' => 'recentchanges-label-minor', 'legend' => 'recentchanges-legend-minor', 'class' => 'minoredit', 'grouping' => 'all', ], 'bot' => [ 'letter' => 'boteditletter', 'title' => 'recentchanges-label-bot', 'legend' => 'recentchanges-legend-bot', 'class' => 'botedit', 'grouping' => 'all', ], 'unpatrolled' => [ 'letter' => 'unpatrolledletter', 'title' => 'recentchanges-label-unpatrolled', 'legend' => 'recentchanges-legend-unpatrolled', 'grouping' => 'any', ], ], 'WatchlistExpiry' => false, 'EnableWatchlistLabels' => false, 'WatchlistLabelsMaxPerUser' => 100, 'WatchlistPurgeRate' => 0.1, 'WatchlistExpiryMaxDuration' => '1 year', 'EnableChangesListQueryPartitioning' => false, 'RightsPage' => null, 'RightsUrl' => null, 'RightsText' => null, 'RightsIcon' => null, 'UseCopyrightUpload' => false, 'MaxCredits' => 0, 'ShowCreditsIfMax' => true, 'ImportSources' => [ ], 'ImportTargetNamespace' => null, 'ExportAllowHistory' => true, 'ExportMaxHistory' => 0, 'ExportAllowListContributors' => false, 'ExportMaxLinkDepth' => 0, 'ExportFromNamespaces' => false, 'ExportAllowAll' => false, 'ExportPagelistLimit' => 5000, 'XmlDumpSchemaVersion' => '0.11', 'WikiFarmSettingsDirectory' => null, 'WikiFarmSettingsExtension' => 'yaml', 'ExtensionFunctions' => [ ], 'ExtensionMessagesFiles' => [ ], 'MessagesDirs' => [ ], 'TranslationAliasesDirs' => [ ], 'ExtensionEntryPointListFiles' => [ ], 'EnableParserLimitReporting' => true, 'ValidSkinNames' => [ ], 'SpecialPages' => [ ], 'ExtensionCredits' => [ ], 'Hooks' => [ ], 'ServiceWiringFiles' => [ ], 'JobClasses' => [ 'deletePage' => 'MediaWiki\\Page\\DeletePageJob', 'refreshLinks' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'deleteLinks' => 'MediaWiki\\Page\\DeleteLinksJob', 'htmlCacheUpdate' => 'MediaWiki\\JobQueue\\Jobs\\HTMLCacheUpdateJob', 'sendMail' => [ 'class' => 'MediaWiki\\Mail\\EmaillingJob', 'services' => [ 'Emailer', ], ], 'enotifNotify' => [ 'class' => 'MediaWiki\\RecentChanges\\RecentChangeNotifyJob', 'services' => [ 'RecentChangeLookup', ], ], 'fixDoubleRedirect' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\DoubleRedirectJob', 'services' => [ 'RevisionLookup', 'MagicWordFactory', 'WikiPageFactory', ], 'needsPage' => true, ], 'AssembleUploadChunks' => 'MediaWiki\\JobQueue\\Jobs\\AssembleUploadChunksJob', 'PublishStashedFile' => 'MediaWiki\\JobQueue\\Jobs\\PublishStashedFileJob', 'ThumbnailRender' => 'MediaWiki\\JobQueue\\Jobs\\ThumbnailRenderJob', 'UploadFromUrl' => 'MediaWiki\\JobQueue\\Jobs\\UploadFromUrlJob', 'recentChangesUpdate' => 'MediaWiki\\RecentChanges\\RecentChangesUpdateJob', 'refreshLinksPrioritized' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'refreshLinksDynamic' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'activityUpdateJob' => 'MediaWiki\\Watchlist\\ActivityUpdateJob', 'categoryMembershipChange' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\CategoryMembershipChangeJob', 'services' => [ 'RecentChangeFactory', ], ], 'CategoryCountUpdateJob' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\CategoryCountUpdateJob', 'services' => [ 'ConnectionProvider', 'NamespaceInfo', ], ], 'clearUserWatchlist' => 'MediaWiki\\Watchlist\\ClearUserWatchlistJob', 'watchlistExpiry' => 'MediaWiki\\Watchlist\\WatchlistExpiryJob', 'cdnPurge' => 'MediaWiki\\JobQueue\\Jobs\\CdnPurgeJob', 'userGroupExpiry' => 'MediaWiki\\User\\UserGroupExpiryJob', 'clearWatchlistNotifications' => 'MediaWiki\\Watchlist\\ClearWatchlistNotificationsJob', 'userOptionsUpdate' => 'MediaWiki\\User\\Options\\UserOptionsUpdateJob', 'revertedTagUpdate' => 'MediaWiki\\JobQueue\\Jobs\\RevertedTagUpdateJob', 'null' => 'MediaWiki\\JobQueue\\Jobs\\NullJob', 'userEditCountInit' => 'MediaWiki\\User\\UserEditCountInitJob', 'parsoidCachePrewarm' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\ParsoidCachePrewarmJob', 'services' => [ 'ParserOutputAccess', 'PageStore', 'RevisionLookup', 'ParsoidSiteConfig', ], 'needsPage' => false, ], 'renameUserTable' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserTableJob', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', ], ], 'renameUserDerived' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserDerivedJob', 'services' => [ 'RenameUserFactory', 'UserFactory', ], ], 'renameUser' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserTableJob', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', ], ], ], 'JobTypesExcludedFromDefaultQueue' => [ 'AssembleUploadChunks', 'PublishStashedFile', 'UploadFromUrl', ], 'JobBackoffThrottling' => [ ], 'JobTypeConf' => [ 'default' => [ 'class' => 'MediaWiki\\JobQueue\\JobQueueDB', 'order' => 'random', 'claimTTL' => 3600, ], ], 'JobQueueIncludeInMaxLagFactor' => false, 'SpecialPageCacheUpdates' => [ 'Statistics' => [ 'MediaWiki\\Deferred\\SiteStatsUpdate', 'cacheUpdate', ], ], 'PagePropLinkInvalidations' => [ 'hiddencat' => 'categorylinks', ], 'CategoryMagicGallery' => true, 'CategoryPagingLimit' => 200, 'CategoryCollation' => 'uppercase', 'TempCategoryCollations' => [ ], 'SortedCategories' => false, 'TrackingCategories' => [ ], 'LogTypes' => [ '', 'block', 'protect', 'rights', 'delete', 'upload', 'move', 'import', 'interwiki', 'patrol', 'merge', 'suppress', 'tag', 'managetags', 'contentmodel', 'renameuser', ], 'LogRestrictions' => [ 'suppress' => 'suppressionlog', ], 'FilterLogTypes' => [ 'patrol' => true, 'tag' => true, 'newusers' => false, ], 'LogNames' => [ '' => 'all-logs-page', 'block' => 'blocklogpage', 'protect' => 'protectlogpage', 'rights' => 'rightslog', 'delete' => 'dellogpage', 'upload' => 'uploadlogpage', 'move' => 'movelogpage', 'import' => 'importlogpage', 'patrol' => 'patrol-log-page', 'merge' => 'mergelog', 'suppress' => 'suppressionlog', ], 'LogHeaders' => [ '' => 'alllogstext', 'block' => 'blocklogtext', 'delete' => 'dellogpagetext', 'import' => 'importlogpagetext', 'merge' => 'mergelogpagetext', 'move' => 'movelogpagetext', 'patrol' => 'patrol-log-header', 'protect' => 'protectlogtext', 'rights' => 'rightslogtext', 'suppress' => 'suppressionlogtext', 'upload' => 'uploadlogpagetext', ], 'LogActions' => [ ], 'LogActionsHandlers' => [ 'block/block' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'block/reblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'block/unblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'contentmodel/change' => 'MediaWiki\\Logging\\ContentModelLogFormatter', 'contentmodel/new' => 'MediaWiki\\Logging\\ContentModelLogFormatter', 'delete/delete' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/delete_redir' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/delete_redir2' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/event' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/restore' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/revision' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'import/interwiki' => 'MediaWiki\\Logging\\ImportLogFormatter', 'import/upload' => 'MediaWiki\\Logging\\ImportLogFormatter', 'interwiki/iw_add' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'interwiki/iw_delete' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'interwiki/iw_edit' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'managetags/activate' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/create' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/deactivate' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/delete' => 'MediaWiki\\Logging\\LogFormatter', 'merge/merge' => [ 'class' => 'MediaWiki\\Logging\\MergeLogFormatter', 'services' => [ 'TitleParser', ], ], 'merge/merge-into' => [ 'class' => 'MediaWiki\\Logging\\MergeLogFormatter', 'services' => [ 'TitleParser', ], ], 'move/move' => [ 'class' => 'MediaWiki\\Logging\\MoveLogFormatter', 'services' => [ 'TitleParser', ], ], 'move/move_redir' => [ 'class' => 'MediaWiki\\Logging\\MoveLogFormatter', 'services' => [ 'TitleParser', ], ], 'patrol/patrol' => 'MediaWiki\\Logging\\PatrolLogFormatter', 'patrol/autopatrol' => 'MediaWiki\\Logging\\PatrolLogFormatter', 'protect/modify' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/move_prot' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/protect' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/unprotect' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'renameuser/renameuser' => [ 'class' => 'MediaWiki\\Logging\\RenameuserLogFormatter', 'services' => [ 'TitleParser', ], ], 'rights/autopromote' => 'MediaWiki\\Logging\\RightsLogFormatter', 'rights/rights' => 'MediaWiki\\Logging\\RightsLogFormatter', 'suppress/block' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'suppress/delete' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'suppress/event' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'suppress/reblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'suppress/revision' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'tag/update' => 'MediaWiki\\Logging\\TagLogFormatter', 'upload/overwrite' => 'MediaWiki\\Logging\\UploadLogFormatter', 'upload/revert' => 'MediaWiki\\Logging\\UploadLogFormatter', 'upload/upload' => 'MediaWiki\\Logging\\UploadLogFormatter', ], 'ActionFilteredLogs' => [ 'block' => [ 'block' => [ 'block', ], 'reblock' => [ 'reblock', ], 'unblock' => [ 'unblock', ], ], 'contentmodel' => [ 'change' => [ 'change', ], 'new' => [ 'new', ], ], 'delete' => [ 'delete' => [ 'delete', ], 'delete_redir' => [ 'delete_redir', 'delete_redir2', ], 'restore' => [ 'restore', ], 'event' => [ 'event', ], 'revision' => [ 'revision', ], ], 'import' => [ 'interwiki' => [ 'interwiki', ], 'upload' => [ 'upload', ], ], 'managetags' => [ 'create' => [ 'create', ], 'delete' => [ 'delete', ], 'activate' => [ 'activate', ], 'deactivate' => [ 'deactivate', ], ], 'move' => [ 'move' => [ 'move', ], 'move_redir' => [ 'move_redir', ], ], 'newusers' => [ 'create' => [ 'create', 'newusers', ], 'create2' => [ 'create2', ], 'autocreate' => [ 'autocreate', ], 'byemail' => [ 'byemail', ], ], 'protect' => [ 'protect' => [ 'protect', ], 'modify' => [ 'modify', ], 'unprotect' => [ 'unprotect', ], 'move_prot' => [ 'move_prot', ], ], 'rights' => [ 'rights' => [ 'rights', ], 'autopromote' => [ 'autopromote', ], ], 'suppress' => [ 'event' => [ 'event', ], 'revision' => [ 'revision', ], 'delete' => [ 'delete', ], 'block' => [ 'block', ], 'reblock' => [ 'reblock', ], ], 'upload' => [ 'upload' => [ 'upload', ], 'overwrite' => [ 'overwrite', ], 'revert' => [ 'revert', ], ], ], 'NewUserLog' => true, 'PageCreationLog' => true, 'AllowSpecialInclusion' => true, 'DisableQueryPageUpdate' => false, 'CountCategorizedImagesAsUsed' => false, 'MaxRedirectLinksRetrieved' => 500, 'RangeContributionsCIDRLimit' => [ 'IPv4' => 16, 'IPv6' => 32, ], 'Actions' => [ ], 'DefaultRobotPolicy' => 'index,follow', 'NamespaceRobotPolicies' => [ ], 'ArticleRobotPolicies' => [ ], 'ExemptFromUserRobotsControl' => null, 'DebugAPI' => false, 'APIModules' => [ ], 'APIFormatModules' => [ ], 'APIMetaModules' => [ ], 'APIPropModules' => [ ], 'APIListModules' => [ ], 'APIMaxDBRows' => 5000, 'APIMaxResultSize' => 8388608, 'APIMaxUncachedDiffs' => 1, 'APIMaxLagThreshold' => 7, 'APICacheHelpTimeout' => 3600, 'APIUselessQueryPages' => [ 'MIMEsearch', 'LinkSearch', ], 'AjaxLicensePreview' => true, 'CrossSiteAJAXdomains' => [ ], 'CrossSiteAJAXdomainExceptions' => [ ], 'AllowedCorsHeaders' => [ 'Accept', 'Accept-Language', 'Content-Language', 'Content-Type', 'Accept-Encoding', 'DNT', 'Origin', 'User-Agent', 'Api-User-Agent', 'Access-Control-Max-Age', 'Authorization', ], 'RestAPIAdditionalRouteFiles' => [ ], 'RestSandboxSpecs' => [ ], 'MaxShellMemory' => 307200, 'MaxShellFileSize' => 102400, 'MaxShellTime' => 180, 'MaxShellWallClockTime' => 180, 'ShellCgroup' => false, 'PhpCli' => '/usr/bin/php', 'ShellRestrictionMethod' => 'autodetect', 'ShellboxUrls' => [ 'default' => null, ], 'ShellboxSecretKey' => null, 'ShellboxShell' => '/bin/sh', 'HTTPTimeout' => 25, 'HTTPConnectTimeout' => 5.0, 'HTTPMaxTimeout' => 0, 'HTTPMaxConnectTimeout' => 0, 'HTTPImportTimeout' => 25, 'AsyncHTTPTimeout' => 25, 'HTTPProxy' => '', 'LocalVirtualHosts' => [ ], 'LocalHTTPProxy' => false, 'AllowExternalReqID' => false, 'JobRunRate' => 1, 'RunJobsAsync' => false, 'UpdateRowsPerJob' => 300, 'UpdateRowsPerQuery' => 100, 'RedirectOnLogin' => null, 'VirtualRestConfig' => [ 'paths' => [ ], 'modules' => [ ], 'global' => [ 'timeout' => 360, 'forwardCookies' => false, 'HTTPProxy' => null, ], ], 'EventRelayerConfig' => [ 'default' => [ 'class' => 'Wikimedia\\EventRelayer\\EventRelayerNull', ], ], 'Pingback' => false, 'OriginTrials' => [ ], 'ReportToExpiry' => 86400, 'ReportToEndpoints' => [ ], 'FeaturePolicyReportOnly' => [ ], 'SkinsPreferred' => [ 'vector-2022', 'vector', ], 'SpecialContributeSkinsEnabled' => [ ], 'SpecialContributeNewPageTarget' => null, 'EnableEditRecovery' => false, 'EditRecoveryExpiry' => 2592000, 'UseCodexSpecialBlock' => false, 'ShowLogoutConfirmation' => false, 'EnableProtectionIndicators' => true, 'OutputPipelineStages' => [ ], 'FeatureShutdown' => [ ], 'CloneArticleParserOutput' => true, 'UseLeximorph' => false, 'UsePostprocCache' => false, 'UsePostprocCacheLegacy' => false, 'UsePostprocCacheParsoid' => false, 'ParserOptionsLogUnsafeSampleRate' => 0, ], 'type' => [ 'ConfigRegistry' => 'object', 'AssumeProxiesUseDefaultProtocolPorts' => 'boolean', 'ForceHTTPS' => 'boolean', 'ExtensionDirectory' => [ 'string', 'null', ], 'StyleDirectory' => [ 'string', 'null', ], 'UploadDirectory' => [ 'string', 'boolean', 'null', ], 'Logos' => [ 'object', 'boolean', ], 'ReferrerPolicy' => [ 'array', 'string', 'boolean', ], 'ActionPaths' => 'object', 'MainPageIsDomainRoot' => 'boolean', 'ImgAuthUrlPathMap' => 'object', 'LocalFileRepo' => 'object', 'ForeignFileRepos' => 'array', 'UseSharedUploads' => 'boolean', 'SharedUploadDirectory' => [ 'string', 'null', ], 'SharedUploadPath' => [ 'string', 'null', ], 'HashedSharedUploadDirectory' => 'boolean', 'FetchCommonsDescriptions' => 'boolean', 'SharedUploadDBname' => [ 'boolean', 'string', ], 'SharedUploadDBprefix' => 'string', 'CacheSharedUploads' => 'boolean', 'ForeignUploadTargets' => 'array', 'UploadDialog' => 'object', 'FileBackends' => 'object', 'LockManagers' => 'array', 'CopyUploadsDomains' => 'array', 'CopyUploadTimeout' => [ 'boolean', 'integer', ], 'SharedThumbnailScriptPath' => [ 'string', 'boolean', ], 'HashedUploadDirectory' => 'boolean', 'CSPUploadEntryPoint' => 'boolean', 'FileExtensions' => 'array', 'ProhibitedFileExtensions' => 'array', 'MimeTypeExclusions' => 'array', 'TrustedMediaFormats' => 'array', 'MediaHandlers' => 'object', 'NativeImageLazyLoading' => 'boolean', 'ParserTestMediaHandlers' => 'object', 'MaxInterlacingAreas' => 'object', 'SVGConverters' => 'object', 'SVGNativeRendering' => [ 'string', 'boolean', ], 'MaxImageArea' => [ 'string', 'integer', 'boolean', ], 'TiffThumbnailType' => 'array', 'GenerateThumbnailOnParse' => 'boolean', 'EnableAutoRotation' => [ 'boolean', 'null', ], 'Antivirus' => [ 'string', 'null', ], 'AntivirusSetup' => 'object', 'MimeDetectorCommand' => [ 'string', 'null', ], 'XMLMimeTypes' => 'object', 'ImageLimits' => 'array', 'ThumbLimits' => 'array', 'ThumbnailNamespaces' => 'array', 'ThumbnailSteps' => [ 'array', 'null', ], 'ThumbnailStepsRatio' => [ 'number', 'null', ], 'ThumbnailBuckets' => [ 'array', 'null', ], 'UploadThumbnailRenderMap' => 'object', 'GalleryOptions' => 'object', 'DjvuDump' => [ 'string', 'null', ], 'DjvuRenderer' => [ 'string', 'null', ], 'DjvuTxt' => [ 'string', 'null', ], 'DjvuPostProcessor' => [ 'string', 'null', ], 'UserEmailConfirmationUseHTML' => 'boolean', 'SMTP' => [ 'boolean', 'object', ], 'EnotifFromEditor' => 'boolean', 'EnotifRevealEditorAddress' => 'boolean', 'UsersNotifiedOnAllChanges' => 'object', 'DBmwschema' => [ 'string', 'null', ], 'SharedTables' => 'array', 'DBservers' => [ 'boolean', 'array', ], 'LBFactoryConf' => 'object', 'LocalDatabases' => 'array', 'VirtualDomainsMapping' => 'object', 'FileSchemaMigrationStage' => 'integer', 'ImageLinksSchemaMigrationStage' => 'integer', 'ExternalLinksDomainGaps' => 'object', 'ContentHandlers' => 'object', 'NamespaceContentModels' => 'object', 'TextModelsToParse' => 'array', 'ExternalStores' => 'array', 'ExternalServers' => 'object', 'DefaultExternalStore' => [ 'array', 'boolean', ], 'RevisionCacheExpiry' => 'integer', 'PageLanguageUseDB' => 'boolean', 'DiffEngine' => [ 'string', 'null', ], 'ExternalDiffEngine' => [ 'string', 'boolean', ], 'Wikidiff2Options' => 'object', 'RequestTimeLimit' => [ 'integer', 'null', ], 'CriticalSectionTimeLimit' => 'number', 'PoolCounterConf' => [ 'object', 'null', ], 'PoolCountClientConf' => 'object', 'MaxUserDBWriteDuration' => [ 'integer', 'boolean', ], 'MaxJobDBWriteDuration' => [ 'integer', 'boolean', ], 'MultiShardSiteStats' => 'boolean', 'ObjectCaches' => 'object', 'WANObjectCache' => 'object', 'MicroStashType' => [ 'string', 'integer', ], 'ParsoidCacheConfig' => 'object', 'ParsoidSelectiveUpdateSampleRate' => 'integer', 'ParserCacheFilterConfig' => 'object', 'ChronologyProtectorSecret' => 'string', 'PHPSessionHandling' => 'string', 'SuspiciousIpExpiry' => [ 'integer', 'boolean', ], 'MemCachedServers' => 'array', 'LocalisationCacheConf' => 'object', 'ExtensionInfoMTime' => [ 'integer', 'boolean', ], 'CdnServers' => 'object', 'CdnServersNoPurge' => 'object', 'HTCPRouting' => 'object', 'GrammarForms' => 'object', 'ExtraInterlanguageLinkPrefixes' => 'array', 'InterlanguageLinkCodeMap' => 'object', 'ExtraLanguageNames' => 'object', 'ExtraLanguageCodes' => 'object', 'DummyLanguageCodes' => 'object', 'DisabledVariants' => 'object', 'ForceUIMsgAsContentMsg' => 'object', 'RawHtmlMessages' => 'array', 'OverrideUcfirstCharacters' => 'object', 'XhtmlNamespaces' => 'object', 'BrowserFormatDetection' => 'string', 'SkinMetaTags' => 'object', 'SkipSkins' => 'object', 'FragmentMode' => 'array', 'FooterIcons' => 'object', 'InterwikiLogoOverride' => 'array', 'ResourceModules' => 'object', 'ResourceModuleSkinStyles' => 'object', 'ResourceLoaderSources' => 'object', 'ResourceLoaderMaxage' => 'object', 'ResourceLoaderMaxQueryLength' => [ 'integer', 'boolean', ], 'CanonicalNamespaceNames' => 'object', 'ExtraNamespaces' => 'object', 'ExtraGenderNamespaces' => 'object', 'NamespaceAliases' => 'object', 'CapitalLinkOverrides' => 'object', 'NamespacesWithSubpages' => 'object', 'ContentNamespaces' => 'array', 'ShortPagesNamespaceExclusions' => 'array', 'ExtraSignatureNamespaces' => 'array', 'InvalidRedirectTargets' => 'array', 'LocalInterwikis' => 'array', 'InterwikiCache' => [ 'boolean', 'object', ], 'SiteTypes' => 'object', 'UrlProtocols' => 'array', 'TidyConfig' => 'object', 'ParsoidSettings' => 'object', 'ParsoidExperimentalParserFunctionOutput' => 'boolean', 'NoFollowNsExceptions' => 'array', 'NoFollowDomainExceptions' => 'array', 'ExternalLinksIgnoreDomains' => 'array', 'EnableMagicLinks' => 'object', 'ManualRevertSearchRadius' => 'integer', 'RevertedTagMaxDepth' => 'integer', 'CentralIdLookupProviders' => 'object', 'CentralIdLookupProvider' => 'string', 'UserRegistrationProviders' => 'object', 'PasswordPolicy' => 'object', 'AuthManagerConfig' => [ 'object', 'null', ], 'AuthManagerAutoConfig' => 'object', 'RememberMe' => 'string', 'ReauthenticateTime' => 'object', 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => 'object', 'ChangeCredentialsBlacklist' => 'array', 'RemoveCredentialsBlacklist' => 'array', 'PasswordConfig' => 'object', 'PasswordResetRoutes' => 'object', 'SignatureAllowedLintErrors' => 'array', 'ReservedUsernames' => 'array', 'DefaultUserOptions' => 'object', 'ConditionalUserOptions' => 'object', 'HiddenPrefs' => 'array', 'UserJsPrefLimit' => 'integer', 'AuthenticationTokenVersion' => [ 'string', 'null', ], 'SessionProviders' => 'object', 'AutoCreateTempUser' => 'object', 'AutoblockExemptions' => 'array', 'BlockCIDRLimit' => 'object', 'EnableMultiBlocks' => 'boolean', 'GroupPermissions' => 'object', 'PrivilegedGroups' => 'array', 'RevokePermissions' => 'object', 'GroupInheritsPermissions' => 'object', 'ImplicitGroups' => 'array', 'GroupsAddToSelf' => 'object', 'GroupsRemoveFromSelf' => 'object', 'RestrictedGroups' => 'object', 'UserRequirementsPrivateConditions' => 'array', 'RestrictionTypes' => 'array', 'RestrictionLevels' => 'array', 'CascadingRestrictionLevels' => 'array', 'SemiprotectedRestrictionLevels' => 'array', 'NamespaceProtection' => 'object', 'NonincludableNamespaces' => 'object', 'Autopromote' => 'object', 'AutopromoteOnce' => 'object', 'AutopromoteOnceRCExcludedGroups' => 'array', 'AddGroups' => 'object', 'RemoveGroups' => 'object', 'AvailableRights' => 'array', 'ImplicitRights' => 'array', 'AccountCreationThrottle' => [ 'integer', 'array', ], 'TempAccountCreationThrottle' => 'array', 'TempAccountNameAcquisitionThrottle' => 'array', 'SpamRegex' => 'array', 'SummarySpamRegex' => 'array', 'DnsBlacklistUrls' => 'array', 'ProxyList' => [ 'string', 'array', ], 'ProxyWhitelist' => 'array', 'SoftBlockRanges' => 'array', 'RateLimits' => 'object', 'RateLimitsExcludedIPs' => 'array', 'ExternalQuerySources' => 'object', 'PasswordAttemptThrottle' => 'array', 'GrantPermissions' => 'object', 'GrantPermissionGroups' => 'object', 'GrantRiskGroups' => 'object', 'EnableBotPasswords' => 'boolean', 'BotPasswordsCluster' => [ 'string', 'boolean', ], 'BotPasswordsDatabase' => [ 'string', 'boolean', ], 'CSPHeader' => [ 'boolean', 'object', ], 'CSPReportOnlyHeader' => [ 'boolean', 'object', ], 'CSPFalsePositiveUrls' => 'object', 'AllowCrossOrigin' => 'boolean', 'RestAllowCrossOriginCookieAuth' => 'boolean', 'CookieSameSite' => [ 'string', 'null', ], 'CacheVaryCookies' => 'array', 'TrxProfilerLimits' => 'object', 'DebugLogGroups' => 'object', 'MWLoggerDefaultSpi' => 'object', 'Profiler' => 'object', 'StatsTarget' => [ 'string', 'null', ], 'StatsFormat' => [ 'string', 'null', ], 'StatsPrefix' => 'string', 'OpenTelemetryConfig' => [ 'object', 'null', ], 'OpenSearchTemplates' => 'object', 'NamespacesToBeSearchedDefault' => 'object', 'SitemapNamespaces' => [ 'boolean', 'array', ], 'SitemapNamespacesPriorities' => [ 'boolean', 'object', ], 'SitemapApiConfig' => 'object', 'SpecialSearchFormOptions' => 'object', 'SearchMatchRedirectPreference' => 'boolean', 'SearchRunSuggestedQuery' => 'boolean', 'PreviewOnOpenNamespaces' => 'object', 'ReadOnlyWatchedItemStore' => 'boolean', 'GitRepositoryViewers' => 'object', 'InstallerInitialPages' => 'array', 'RCLinkLimits' => 'array', 'RCLinkDays' => 'array', 'RCFeeds' => 'object', 'RCEngines' => 'object', 'OverrideSiteFeed' => 'object', 'FeedClasses' => 'object', 'AdvertisedFeedTypes' => 'array', 'SoftwareTags' => 'object', 'RecentChangesFlags' => 'object', 'WatchlistExpiry' => 'boolean', 'EnableWatchlistLabels' => 'boolean', 'WatchlistLabelsMaxPerUser' => 'integer', 'WatchlistPurgeRate' => 'number', 'WatchlistExpiryMaxDuration' => [ 'string', 'null', ], 'EnableChangesListQueryPartitioning' => 'boolean', 'ImportSources' => 'object', 'ExtensionFunctions' => 'array', 'ExtensionMessagesFiles' => 'object', 'MessagesDirs' => 'object', 'TranslationAliasesDirs' => 'object', 'ExtensionEntryPointListFiles' => 'object', 'ValidSkinNames' => 'object', 'SpecialPages' => 'object', 'ExtensionCredits' => 'object', 'Hooks' => 'object', 'ServiceWiringFiles' => 'array', 'JobClasses' => 'object', 'JobTypesExcludedFromDefaultQueue' => 'array', 'JobBackoffThrottling' => 'object', 'JobTypeConf' => 'object', 'SpecialPageCacheUpdates' => 'object', 'PagePropLinkInvalidations' => 'object', 'TempCategoryCollations' => 'array', 'SortedCategories' => 'boolean', 'TrackingCategories' => 'array', 'LogTypes' => 'array', 'LogRestrictions' => 'object', 'FilterLogTypes' => 'object', 'LogNames' => 'object', 'LogHeaders' => 'object', 'LogActions' => 'object', 'LogActionsHandlers' => 'object', 'ActionFilteredLogs' => 'object', 'RangeContributionsCIDRLimit' => 'object', 'Actions' => 'object', 'NamespaceRobotPolicies' => 'object', 'ArticleRobotPolicies' => 'object', 'ExemptFromUserRobotsControl' => [ 'array', 'null', ], 'APIModules' => 'object', 'APIFormatModules' => 'object', 'APIMetaModules' => 'object', 'APIPropModules' => 'object', 'APIListModules' => 'object', 'APIUselessQueryPages' => 'array', 'CrossSiteAJAXdomains' => 'object', 'CrossSiteAJAXdomainExceptions' => 'object', 'AllowedCorsHeaders' => 'array', 'RestAPIAdditionalRouteFiles' => 'array', 'RestSandboxSpecs' => 'object', 'ShellRestrictionMethod' => [ 'string', 'boolean', ], 'ShellboxUrls' => 'object', 'ShellboxSecretKey' => [ 'string', 'null', ], 'ShellboxShell' => [ 'string', 'null', ], 'HTTPTimeout' => 'number', 'HTTPConnectTimeout' => 'number', 'HTTPMaxTimeout' => 'number', 'HTTPMaxConnectTimeout' => 'number', 'LocalVirtualHosts' => 'object', 'LocalHTTPProxy' => [ 'string', 'boolean', ], 'VirtualRestConfig' => 'object', 'EventRelayerConfig' => 'object', 'Pingback' => 'boolean', 'OriginTrials' => 'array', 'ReportToExpiry' => 'integer', 'ReportToEndpoints' => 'array', 'FeaturePolicyReportOnly' => 'array', 'SkinsPreferred' => 'array', 'SpecialContributeSkinsEnabled' => 'array', 'SpecialContributeNewPageTarget' => [ 'string', 'null', ], 'EnableEditRecovery' => 'boolean', 'EditRecoveryExpiry' => 'integer', 'UseCodexSpecialBlock' => 'boolean', 'ShowLogoutConfirmation' => 'boolean', 'EnableProtectionIndicators' => 'boolean', 'OutputPipelineStages' => 'object', 'FeatureShutdown' => 'array', 'CloneArticleParserOutput' => 'boolean', 'UseLeximorph' => 'boolean', 'UsePostprocCache' => 'boolean', 'UsePostprocCacheLegacy' => 'boolean', 'UsePostprocCacheParsoid' => 'boolean', 'ParserOptionsLogUnsafeSampleRate' => 'integer', ], 'mergeStrategy' => [ 'TiffThumbnailType' => 'replace', 'LBFactoryConf' => 'replace', 'InterwikiCache' => 'replace', 'PasswordPolicy' => 'array_replace_recursive', 'AuthManagerAutoConfig' => 'array_plus_2d', 'GroupPermissions' => 'array_plus_2d', 'RevokePermissions' => 'array_plus_2d', 'AddGroups' => 'array_merge_recursive', 'RemoveGroups' => 'array_merge_recursive', 'RateLimits' => 'array_plus_2d', 'GrantPermissions' => 'array_plus_2d', 'MWLoggerDefaultSpi' => 'replace', 'Profiler' => 'replace', 'Hooks' => 'array_merge_recursive', 'VirtualRestConfig' => 'array_plus_2d', ], 'dynamicDefault' => [ 'UsePathInfo' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultUsePathInfo', ], ], 'Script' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultScript', ], ], 'LoadScript' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLoadScript', ], ], 'RestPath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultRestPath', ], ], 'StylePath' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultStylePath', ], ], 'LocalStylePath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocalStylePath', ], ], 'ExtensionAssetsPath' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultExtensionAssetsPath', ], ], 'ArticlePath' => [ 'use' => [ 'Script', 'UsePathInfo', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultArticlePath', ], ], 'UploadPath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultUploadPath', ], ], 'FileCacheDirectory' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultFileCacheDirectory', ], ], 'Logo' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLogo', ], ], 'DeletedDirectory' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultDeletedDirectory', ], ], 'ShowEXIF' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultShowEXIF', ], ], 'SharedPrefix' => [ 'use' => [ 'DBprefix', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultSharedPrefix', ], ], 'SharedSchema' => [ 'use' => [ 'DBmwschema', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultSharedSchema', ], ], 'DBerrorLogTZ' => [ 'use' => [ 'Localtimezone', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultDBerrorLogTZ', ], ], 'Localtimezone' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocaltimezone', ], ], 'LocalTZoffset' => [ 'use' => [ 'Localtimezone', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocalTZoffset', ], ], 'ResourceBasePath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultResourceBasePath', ], ], 'MetaNamespace' => [ 'use' => [ 'Sitename', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultMetaNamespace', ], ], 'CookieSecure' => [ 'use' => [ 'ForceHTTPS', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultCookieSecure', ], ], 'CookiePrefix' => [ 'use' => [ 'SharedDB', 'SharedPrefix', 'SharedTables', 'DBname', 'DBprefix', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultCookiePrefix', ], ], 'ReadOnlyFile' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultReadOnlyFile', ], ], ], ], 'config-schema' => [ 'UploadStashScalerBaseUrl' => [ 'deprecated' => 'since 1.36 Use thumbProxyUrl in $wgLocalFileRepo', ], 'IllegalFileChars' => [ 'deprecated' => 'since 1.41; no longer customizable', ], 'ThumbnailNamespaces' => [ 'items' => [ 'type' => 'integer', ], ], 'LocalDatabases' => [ 'items' => [ 'type' => 'string', ], ], 'ParserCacheFilterConfig' => [ 'additionalProperties' => [ 'type' => 'object', 'description' => 'A map of namespace IDs to filter definitions.', 'additionalProperties' => [ 'type' => 'object', 'description' => 'A map of filter names to values.', 'properties' => [ 'minCpuTime' => [ 'type' => 'number', ], ], ], ], ], 'PHPSessionHandling' => [ 'deprecated' => 'since 1.45 Integration with PHP session handling will be removed in the future', ], 'RawHtmlMessages' => [ 'items' => [ 'type' => 'string', ], ], 'InterwikiLogoOverride' => [ 'items' => [ 'type' => 'string', ], ], 'LegalTitleChars' => [ 'deprecated' => 'since 1.41; use Extension:TitleBlacklist to customize', ], 'ReauthenticateTime' => [ 'additionalProperties' => [ 'type' => 'integer', ], ], 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => [ 'additionalProperties' => [ 'type' => 'boolean', ], ], 'ChangeCredentialsBlacklist' => [ 'items' => [ 'type' => 'string', ], ], 'RemoveCredentialsBlacklist' => [ 'items' => [ 'type' => 'string', ], ], 'GroupPermissions' => [ 'additionalProperties' => [ 'type' => 'object', 'additionalProperties' => [ 'type' => 'boolean', ], ], ], 'GroupInheritsPermissions' => [ 'additionalProperties' => [ 'type' => 'string', ], ], 'AvailableRights' => [ 'items' => [ 'type' => 'string', ], ], 'ImplicitRights' => [ 'items' => [ 'type' => 'string', ], ], 'SoftBlockRanges' => [ 'items' => [ 'type' => 'string', ], ], 'ExternalQuerySources' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'enabled' => [ 'type' => 'boolean', 'default' => false, ], 'url' => [ 'type' => 'string', 'format' => 'uri', ], 'timeout' => [ 'type' => 'integer', 'default' => 10, ], ], 'required' => [ 'enabled', 'url', ], 'additionalProperties' => false, ], ], 'GrantPermissions' => [ 'additionalProperties' => [ 'type' => 'object', 'additionalProperties' => [ 'type' => 'boolean', ], ], ], 'GrantPermissionGroups' => [ 'additionalProperties' => [ 'type' => 'string', ], ], 'SitemapNamespacesPriorities' => [ 'deprecated' => 'since 1.45 and ignored', ], 'SitemapApiConfig' => [ 'additionalProperties' => [ 'enabled' => [ 'type' => 'bool', ], 'sitemapsPerIndex' => [ 'type' => 'int', ], 'pagesPerSitemap' => [ 'type' => 'int', ], 'expiry' => [ 'type' => 'int', ], ], ], 'SoftwareTags' => [ 'additionalProperties' => [ 'type' => 'boolean', ], ], 'JobBackoffThrottling' => [ 'additionalProperties' => [ 'type' => 'number', ], ], 'JobTypeConf' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'class' => [ 'type' => 'string', ], 'order' => [ 'type' => 'string', ], 'claimTTL' => [ 'type' => 'integer', ], ], ], ], 'TrackingCategories' => [ 'deprecated' => 'since 1.25 Extensions should now register tracking categories using the new extension registration system.', ], 'RangeContributionsCIDRLimit' => [ 'additionalProperties' => [ 'type' => 'integer', ], ], 'RestSandboxSpecs' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'url' => [ 'type' => 'string', 'format' => 'url', ], 'name' => [ 'type' => 'string', ], 'file' => [ 'type' => 'string', ], 'msg' => [ 'type' => 'string', 'description' => 'a message key', ], ], ], ], 'ShellboxUrls' => [ 'additionalProperties' => [ 'type' => [ 'string', 'boolean', 'null', ], ], ], ], 'obsolete-config' => [ 'MangleFlashPolicy' => 'Since 1.39; no longer has any effect.', 'EnableOpenSearchSuggest' => 'Since 1.35, no longer used', 'AutoloadAttemptLowercase' => 'Since 1.40; no longer has any effect.', ],]